Heading image for post: Taking Advantage of the Polyglot Lifestyle

Ruby

Taking Advantage of the Polyglot Lifestyle

Profile picture of Johnny Winn

If you haven't heard yet or you've simply been under a rock for the past several months, there is a new functional language getting quite a bit of attention. Elixir is the brainchild of José Valim and it's built to run on the Erlang Virtual Machine (EVM). It's a highly meta-programmable, dynamic language with an elegant and flexible syntax. Since Elixir compiles to the same bytecode as Erlang, you can make calls directly to Erlang from Elixir. It also leverages the power of Erlang to build concurrent, distributed, and fault-tolerant applications. At best it could drive the next big functional revolution and at worst it is a fun language to work with.

Now that you have had the Elixir elevator pitch, it's on to the business at hand...

Learning a variety of languages is a great way to enhance your skill set and bring experiences back to your primary language. Not only that, but learning different programming paradigms can allow you to look at problems from new angles. To quote Abraham Maslow, "if you only have a hammer, you tend to see every problem as a nail". However, learning a "new" language can be cumbersome or intimidating when comfortable features aren't available. Never fear because that gives you the chance to take advantage of the Polyglot Lifestyle.

What's that? You haven't heard about the Polyglot Lifestyle?

A polyglot is simply someone that knows and can use several languages. To make it a lifestyle you need to exercise those languages and what better way then to use one language to learn another? The dynamics of Ruby provide a great springboard for working with other languages. Since there are many pieces to the puzzle already build you can focus on learning without distractions. That's not to say that a little challenge isn't expected or even necessary, but if you can focus on what the language offers without concerning yourself with what it doesn't, you are free to explore without the distraction of incompleteness. Keep in mind, learning a new language doesn't mean that it will be replacing your current language but you may find new was to utilize the tools that it offers.

I tend to follow a progression when learning a new language. Starting out with the basics and moving into more complicated applications. Inevitably, I create a blog web application. This isn't a unique pattern but that's because blogs offer a chance to explore several aspects of how the language behaves within the confines of the web. Also, for the most part, I'm a web developer so the most common way in which I would use a language would be building web applications. My experience learning Elixir was no different.

Elixir is still pre 1.0 so this post isn't going to teach you Elixir. I would suggest you check out the Programming Elixir book by Dave Thomas. It's also not an authoritative tutorial for Dynamo, Elixir's web framework, or Ecto, its data query library for postgres. That would also be a bit premature. However, this will show you how to use Ruby to supplement missing features while learning Elixir.

First Things First, but Not in that Order

The first thing would be to setup your Elixir environment with the correct versions of Erlang, Elixir and Ruby. This post assumes the following versions: Erlang R16B01, Elixir 0.10.1-dev and Ruby 2.0. Dynamo and Etco both require the latest versions of Elixir so staying current isn't only a nicety, it is also a requirement. To find the details on installation and project setup review the latest documentation. Ruby is added to handle ancillary support for data migrations and behavior-driven development. Data migrations are great not only for keeping the database up-to-date but we can use them to populate and clean our test data. Behavior-driven development is just something we do.

And Now We Begin

Although setting up a Dynamo project is straight forward we need to add the Ecto dependency to query the database. Start by opening the mix.exs file and adding the following:

defp deps do
  [ { :cowboy, github: "extend/cowboy" },
    { :dynamo, github: "elixir-lang/dynamo" },
    { :ecto, github: "elixir-lang/ecto" },
    { :pgsql, github: "semiocast/pgsql" } ]
end

The pgsql dependency is an Erlang library that is required by Ecto. This also illustrates the seamless integration between Elixir and Erlang. Calls to the Erlang library can be made directly from your Elixir code.

After adding the dependencies to the mix file, we need to run mix deps.get from our shell to pull the dependencies and compile. Additional information on setting up the Ecto.Repo and connection is beyond the scope of this post but can be found in the Ecto README on github. Finally, to test that our server is running, we can run mix server and navigate to localhost:4000 to see the default Dynamo page.

Now with a Dynamo project up and running with database support, we can begin incorporating Ruby into our project. Because we are simply adding Ruby we will create a .ruby-version file in the project root and specify the version. Next we add a Gemfile along with our first gems.

ruby '2.0.0'

group :test do
  gem 'cucumber', require: false
  gem 'selenium-driver'
  gem 'capybara'
  gem 'rspec'
end

Configuring Capybara for Cucumber

Our cucumber steps will use capybara to simulate the user interactions but we need to give it some direction. Create a support directory in the test\features folder and add a cucumber.rb file. Open the file and add the following requirements:

require 'capybara'
require 'capybara/cucumber'
require 'capybara/rspec'

This allows us to call capybara methods from our step definitions. However, this only supplies part of the solution because capybara will be looking for a server that doesn't exist. We can prevent capybara's default behavior by changing the default settings.

require 'capybara'
require 'capybara/cucumber'
require 'capybara/rspec'

Capybara.run_server = false
Capybara.app_host = 'http://localhost:8888'
Capybara.default_driver = :selenium

You'll notice that we set the app_host port to 8888. When Mix runs in the test MIX_ENV, the server is actually spun up on port 8888 so we want capybara to be looking at the correct environment. Just directing capybara to the correct server isn't enough to start the test environment. We will need to add a Mix test to initiate cucumber within the context of mix.

Add a file to the test directory named cucumber_test.exs and the following code:

Code.require_file "../test_helper.exs", __FILE__

defmodule CucumberTests do
  use Blog.TestCase

  setup do
    Blog.Dynamo.run
    :ok
  end

  test :run_cucumber_features do
    Mix.shell.cmd("cucumber test/features --format progress")
  end
end

This is a fairly basic ExUnit test but lets take a second to review what's actually happening. First, the setup macro will start our server with Blog.Dynamo.run which will spin up to listen on port 8888. Then we define our cucumber call out in the test macro. In this scenario, I prefer to format my cucumber output as progress to reduce the noise when running the test suite.

Note here that the test macro will take either a string or an atom when defining a test. We could just as easily said:

test "Run Cucumber Features" do
  ...
end

Moving on to Migrations

With our test suite set to run, the next hurdle is handling the migrations. Assuming that you have created two databases, one for development and one for testing, we can add a Rakefile to the root and define our migration tasks. Since our migration solution is temporary, having the tasks in a single file will suffice. However, we do want to consider where we are storing the migration files. Create the directories db/migrations in the project root and add the following to the Rakefile to require any migrations within the path:

#!/usr/bin/env rake

require 'active_record'

Dir['db/migrate/*'].each do |file|
  require File.expand_path(file)
end

The rake tasks will be defined within the db namespace but we have multiple tasks to run based on the environment. For example, whether we are in development or test, we may need to migrate or rollback and we will need to establish a connection. Both establishing the connection and running the migrations can be DRY'd up into two methods:

namespace :db do
  def connection(config)
    ActiveRecord::Base.establish_connection(config)
    ActiveRecord::Migration.verbose = false
  end

  def run_migrations(direction, config)
    connection(config)
    ActiveRecord::Migration.descendants.each { |m| m.send(direction) }
  end
end

The connection method takes a configuration hash and establishes the connection through ActiveRecord. The run_migrations method opens a connection and then iterates through the descendants of ActiveRecord::Migration to call a directional method, i.e. up or down. Here is an example of the configuration and both a migrate and rollback rake tasks:

namespace :db do

  # code removed

  PG_CONFIG = {
    :adapter  => 'postgresql',
    :host     => 'localhost',
    :database => 'blog_dev',
    :username => 'postgres',
  }

  task :migrate do
    run_migrations(:up, PG_CONFIG)
  end

  task :rollback do
    run_migrations(:down, PG_CONFIG)
  end
end

When we run rake db:migrate or rake db:rollback it will perform the proper actions defined within our migration methods. Now we can scope the migrations to the test database by simply merging the database name into the configuration hash in the test namespace.

namespace :db do

  # code removed

  namespace :test do
    def config
      PG_CONFIG.merge({:database => 'blog_test'})
    end

    task :prepare do
      run_migrations(:up, config)
    end

    task :rollback do
      run_migrations(:down, config)
    end
  end
end

Putting the Pieces Together

The Rakefile is great for keeping the databases current but testing presents a different set of hurdles. How can we generate data to test against? How do we clean up between tests? These are actually easily solved problems. Within our features/support directory we can add the active_record_connection.rb file with the following:

require 'active_record'

PG_CONFIG = {
  :adapter  => 'postgresql',
  :host     => 'localhost',
  :database => 'blog_test',
  :username => 'postgres',
}

ActiveRecord::Base.establish_connection(PG_CONFIG)
ActiveRecord::Migration.verbose = false

When cucumber runs it will load the file and open a connection to the test database. As we create entities to test we can also add those files to this directory. Here is an example of a migration to insert and delete posts:

class Posts < ActiveRecord::Migration

  def self.up
    execute  %q{
      INSERT INTO posts (headline, content)
      VALUES ('A Fishful of Dollars', 'Mo money, mo fish');
      }
  end

  def self.down
    execute %q{DELETE FROM posts;}
  end
end

In our step definitions we can call Post.up to insert a record and to clean up after our cucumber scenarios we can add the following to our cucumber support file:

After do
  Posts.down
end

This can be made more verbose but it's a start that can help you on your way to living the Polyglot Lifestyle.

Experimenting with new languages can present a unique set of challenges but you can use the knowledge you have to ease the transition. The important takeaway is to keep learning and testing your skills. That is the best way to stay sharp. Be sure to take a look at the full Elixir/Ruby mashup on github.

More posts about Ruby Development

  • Adobe logo
  • Barnes and noble logo
  • Aetna logo
  • Vanderbilt university logo
  • Ericsson logo

We're proud to have launched hundreds of products for clients such as LensRentals.com, Engine Yard, Verisign, ParkWhiz, and Regions Bank, to name a few.

Let's talk about your project