Thursday, April 19, 2012

Ruby Metaprogramming with class_eval

One of the really cool and exciting aspects of using a dynamic programming language like Ruby is that you can generate code dynamically at runtime. Yup, you heard me, at runtime. Try doing something like this in Java. This is one of the features that allows all the magic that happens behind the scenes in Ruby on Rails possible. For example, this is how Rails' attr_accessor automatically creates getters and setters for you. If you don't remember, you can call attr_accessor in an ActiveRecord class to define a "virtual" attribute, or in other words an attribute that isn't persisted in the database table. I want to explain this idea in more detail by creating a new method called attr_accessor_with_history, which is similar to attr_accessor, but it will also store the attribute's history. Say we have the following class:
class Test
   attr_accessor_with_history :sample
end
Then this is how the attr_accessor_with_history method should work in the Ruby console:
t = Test.new
t.sample = "test"  # => "test"
t.sample = 1       # => 1
t.sample = :abc    # => :abc
t.sample_history   # => [nil, "test", 1, :abc]
So how can we do this? Let's first create a new file called class.rb and put the following inside:
class Class
   def attr_accessor_with_history(attr_name)

   end
end
By defining our method in the class Class, all Ruby classes will be able to use it. This is because in Ruby, all classes are an object of the class Class. You can find more information in the API here. The second thing we want to know is that Ruby provides a method called class_eval, which takes a string and evaluates it in the context of the current class. So in our example, the class Test is calling our method, so when class_eval is called, it will be evaluated as if it were called in the class Test.

Let's take care of the easy part first, which is creating the attribute's getter methods. This is pretty straight forward because we don't really need to do anything special here.
class Class
   def attr_accessor_with_history(attr_name)
      attr_name = attr_name.to_s         # make sure it's a string
      attr_reader attr_name              # create the attribute's getter
      attr_reader attr_name + "_history" # create bar_history getter

      # write our setter code below
      class_eval %Q(
                  def #{attr_name}=(attr_name)
                  end
                 )
   end
end
The %Q is Ruby syntax for an interpolated string, which allows us to create a string, and it will also interpret Ruby values such as anything in #{}. So in thinking about how to define our setter, we need to store the previous history of the variable. How can we do this? Well we can store stuff in an Array. If we push things in to the Array like a Stack, we can also figure out the order in which things were inserted. The one thing we need to be careful about is the initial case where we should initialize the array, and also insert the first value as nil. This is what we get:
class Class
   def attr_accessor_with_history(attr_name)
      attr_name = attr_name.to_s         # make sure it's a string
      attr_reader attr_name              # create the attribute's getter
      attr_reader attr_name + "_history" # create bar_history getter

      # write our setter code below
      class_eval %Q(
                  def #{attr_name}=(attr_name)
                     @#{attr_name} = attr_name

                     unless @#{attr_name + "_history"}
                        @#{attr_name + "_history"} = []
                        @#{attr_name + "_history"} << nil
                     end

                     @#{attr_name + "_history"} << attr_name

                   end
                 )
   end
end
First we set the value of the variable. Second, we check if the _history array has been initialized yet, and if it hasn't, then go ahead and initialize it. Lastly, we insert the new value into the _history array.

That was just a quick example of some of the cool things you can do with metaprogramming in Ruby. I want to end by showing a real world example of how I used metaprogramming on a project I'm currently working on. If you haven't read my previous blog posts, I'm working on a Rails site using MongoDB (with the Mongoid gem) that deals with computer games. I have a class called Event, and I need to define some scopes based on the game type so that I can do queries such as Event.all.starcraft2, which would return all Events that have a game attribute of 'starcraft2'. I could easily hard code these scopes, but it will be annoying to always have to add a new scope every time we add support for a new game. Instead I can dynamically create these scopes using Ruby metaprogramming.
  class_eval %Q(
              #{Constants::GAMES}.each_pair do |key, value|
                scope key, where(game: value)
              end
             )
First I need to explain the Constants::GAMES variable. I created a dynamic_fields.yml file that stores all the games that my application supports. Please refer to my previous post here for more detailed information. So when my application loads, it reads in that YML file into the Constants::GAMES variable, which is a hash. The key is the game name as a symbol, and the value is the game name as a string. For each game, it will create a scope with the same name (as the game), and it does all of this automatically. I could have wrote a line of code for each game, but this is much cleaner to write and easier to maintain in the future.

Friday, April 6, 2012

Installing and compiling Ruby from the source code

There are several different ways to install Ruby with the most popular being RVMand rbenv. Both have their advantages and disadvantages, but rbenv is a newer and more "lightweight" Ruby installer. I'll let you Google the differences between RVM and rbenv if you are more curious. I decided to install Ruby by downloading the source code and then compiling it myself. I'll be installing Ruby 1.9.3-p125 (the latest version as of writing this) on Debian Linux 6 (Squeeze).

1. First we need to install some development tools and libraries that Ruby needs to compile. Run the following commands from your terminal:
   apt-get install build-essential vim git-core curl

   apt-get install bison openssl libreadline6 libreadline6-dev zlib1g zlib1g-dev libssl-dev libyaml-dev libxml2-dev libxslt-dev autoconf libc6-dev ncurses-dev

   apt-get install libcurl4-openssl-dev libopenssl-ruby apache2-prefork-dev libapr1-dev libaprutil1-dev

   apt-get install libx11-dev libffi-dev tcl-dev tk-dev
Note you may need to run these commands as root or use sudo.

2. Download the latest version of Ruby here: http://www.ruby-lang.org/en/downloads/ under the Compiling Ruby — Source code section.

3. Extract the tar.gz file e.g. ruby-1.9.3-p125.tar.gz by typing:
tar -xvf ruby-1.9.3-p125.tar.gz
4. Open the README and follow the instructions on how to compile and install Ruby.
./configure
make
make test
make install
The install process will probably take a while, but once it's done, we can test to see if ruby installed successfully by typing "ruby -v" in the terminal. You should see something like, "ruby 1.9.3p125 (2012-02-16 revision 34643) [i686-linux]" if ruby installed correctly. Next we can install the Bundler and Rails gems to make sure Ruby is working correctly.
gem install bundler
gem install rails
Lastly, create a new Rails project to test to make sure everything works!