Ruby
Taking Advantage of the Polyglot Lifestyle
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.