Posts Tagged ‘methods’

Dynamic Methods in Ruby with method_missing

December 21, 2010

Make it up as you go

One way Ruby is dynamic is that you can choose how to handle methods that are called, but don’t actually exist. If you have a lot of very similar methods, you can even use this to define them all at once! Ruby does this using the method_missing method, which you override in the classes where you need more dynamic method calling.

ActiveRecord’s dynamic find_all_by methods

Ruby on Rails uses method_missing with ActiveRecord’s find_all_by methods. There is no find_all_by_name method, but if your Person model has a name attribute, you can call Person.find_all_by_name('Bob') and it will return all the records that match that name.

Here’s a very simplified version of how Rails handles find_all_by requests:

class Person < ActiveRecord::Base
  def self.method_missing method_name, *args
    if method_name =~ /^find_all_by_(\w+)$/
      self.all(:conditions => {$1 => args[0]})
    end
  end
end

Using regular expressions, method_missing sees if the method name matches something we expect. It parses out the interesting parts, and uses them to look up the objects we’re searching for. This is a good use case, because the attributes of an ActiveRecord model aren’t known until runtime.

Dynamic methods for dynamic objects outside Rails

We can apply this same technique outside of Rails. Let’s create the world’s most dynamic Ruby class:

# lib/widget.rb
class Widget
  def method_missing sym, *args
    if sym =~ /^(\w+)=$/
      instance_variable_set "@#{$1}", args[0]
    else
      instance_variable_get "@#{sym}"
    end
  end
end

We’ve just created a Widget object that can have any attributes you want to give it. method_missing checks if the called method ends with an equal sign – if so, it assigns the value you passed, to an instance variable with that name. If there’s no equal sign, it tries to get the value of an instance variable by that name:

ruby-1.9.2-p0 > widget = Widget.new
 => #<Widget:0x0000010383f618> 
ruby-1.9.2-p0 > widget.name = 'Bob'
 => "Bob" 
ruby-1.9.2-p0 > widget.age = 30
 => 30 
ruby-1.9.2-p0 > widget.name
 => "Bob" 
ruby-1.9.2-p0 > widget.age
 => 30 

Use method_missing with methods that use blocks

You can also pass blocks to method_missing. Say we have an ActiveRecord model called Person, with name and age attributes. Let’s create something similar to find_all_by that gets the list of matching people, and runs them through the map method. We’ll call it map_by:

# app/models/person.rb
class Person < ActiveRecord::Base
  def self.method_missing method_name, *args, &block
    if method_name =~ /^map_by_(\w+)$/
      list = self.all(:conditions => {$1 => args[0]})
      list.map(&block)
    end
  end
end

If a method is called that can’t be found, method_missing will check to see if it matches our map_by pattern, perform an ActiveRecord search, and push the results through map with the block we supplied.

Now let’s see if it works, by grabbing the names of all people in our database age 30:

ruby-1.9.2-p0 > Person.create :name => 'Bob', :age => 30
 => #<Person id: 2, name: "Bob", age: 30, created_at: "2010-12-21 02:23:57", updated_at: "2010-12-21 02:23:57"> 
ruby-1.9.2-p0 > Person.create :name => 'John', :age => 29
 => #<Person id: 3, name: "John", age: 29, created_at: "2010-12-21 02:24:11", updated_at: "2010-12-21 02:24:11"> 
ruby-1.9.2-p0 > Person.create :name => 'Marsha', :age => 30
 => #<Person id: 4, name: "Marsha", age: 30, created_at: "2010-12-21 02:24:22", updated_at: "2010-12-21 02:24:22"> 
ruby-1.9.2-p0 > Person.map_by_age(30){|person| person.name}
 => ["Bob", "Marsha"] 

It works! Now I’m going to refactor the Person class to make it easier to add more dynamic methods in the future. I’ll even add an each_by handler so we can see multiple dynamic methods in action:

# app/models/person.rb
class Person < ActiveRecord::Base
  class << self
    def method_missing method_name, *args, &block
      case method_name
      when /^map_by_(\w+)$/ then map_by $1, args[0], &block
      when /^each_by(\w+)$/ then each_by $1, args[0], &block
      else super method_name, *args, &block
      end
    end
    
    def map_by attribute, value, &block
      list = self.all(:conditions => {attribute => value})
      list.map(&block)
    end

    def each_by attribute, value, &block
      list = self.all(:conditions => {attribute => value})
      list.each(&block)
    end
  end
end

I’ve done a few things. First, I changed our “if” conditional to a case statement, so that we can add to it in the future, and it will be clean and readable. I also moved the actual map_by code into its own method, for the same reason. And now, method_missing calls its parent method if it doesn’t find a match, to preserve inheritance.

You might also notice that instead of defining self.method_missing and self.map_by, I’ve wrapped these method definitions in a class << self block that essentially does the same thing. I think this is cleaner when you have several class methods.

method_missing can be used in any Ruby class, so long as you can anticipate dynamic methods that the users of your class might need, and preserve the chain of inheritance. This should be used sparingly, when you can cut down on method definitions by defining them dynamically. It’s easy to abuse this, and there is extra overhead involved. But for the right situations, method_missing can create shorter, more readable code.

Ruby Method Permissions: The Differences Between Public, Protected, and Private

December 15, 2010

It’s easy to code in Ruby for years, and never pay attention to method permissions. I know, because I did it. I came from a C/C++ background, so I understood the concepts of public vs private. And I vaguely understood that Ruby’s protected fell somewhere in between. I just never really cared enough to look it up. Eventually I got curious. I’ll run down the different types of method visibility here.

Examples

It’s hard to think of a good, simple example to use for this. I’ll go with hobbits. They’re friendly to other species, but they have private lives, as well. Much of their privacy seems to be open to other hobbits, since they appear to be able to wander into each others’ houses in the shire at will. Of course, they also have a few choice secrets kept just to themselves. They have all the qualities of a well-rounded Ruby method. Let’s define their class here:

class Hobbit
  def initialize(name, rooms, has_ring)
    @name, @rooms, @has_ring = name, rooms, has_ring
  end

  def name
    @name
  end

  def name_of(hobbit)
    hobbit.name
  end

  def rooms_of(hobbit)
    hobbit.rooms
  end

  def hobbit_has_ring?(hobbit)
    hobbit.has_ring?
  end

  protected

  def rooms
    @rooms
  end

  private

  def has_ring?
    @has_ring
  end
end

Each hobbit has three attributes. Their name is public, the number of rooms in their house is protected, and whether or not they have The Ring is private. There are methods to read these attributes, and to try to read them from other hobbits. Now let’s create a couple hobbits to work with:

irb(main):001:0> require 'hobbit.rb'
=> true
irb(main):002:0> frodo = Hobbit.new('Frodo', 3, true)
=> #<Hobbit:0x10038b5c0 @rooms=3, @name="Frodo", @has_ring=true>
irb(main):003:0> samwise = Hobbit.new('Samwise', 2, false)
=> #<Hobbit:0x1003769e0 @rooms=2, @name="samwise", @has_ring=false>

Public Methods

This is the default method type in Ruby (and most languages), and it means that it can be called from anywhere, by any hunk of code that knows what the object is. We can easily find out the names of our hobbits:

irb(main):004:0> frodo.name
=> "Frodo"
irb(main):005:0> samwise.name
=> "Samwise"

Protected Methods

Now, what about the rooms in their houses? This info is accessible only through protected methods:

irb(main):006:0> frodo.rooms
NoMethodError: protected method `rooms' called for #<Hobbit:0x10034a4a8 @rooms=3, @name="Frodo", @has_ring=true>
	from (irb):6
	from :0
irb(main):007:0> frodo.rooms_of(frodo)
=> 3
irb(main):008:0> frodo.rooms_of(samwise)
=> 2

As you can see, we can’t call the hobbits’ room methods directly, but one hobbit can call the protected rooms method for itself, or any other hobbit. In purely programming terms, public methods are globally accessible, where protected methods can only be called by other methods in the class.

Private Methods

What about ring status? This info is locked in private methods. How can we access this?

irb(main):013:0> frodo.has_ring?
NoMethodError: private method `has_ring?' called for #<Hobbit:0x10034a4a8 @rooms=3, @name="Frodo", @has_ring=true>
	from (irb):13
	from :0
irb(main):014:0> frodo.hobbit_has_ring?(frodo)
NoMethodError: private method `has_ring?' called for #<Hobbit:0x10034a4a8 @rooms=3, @name="Frodo", @has_ring=true>
	from ./hobbit.rb:19:in `hobbit_has_ring?'
	from (irb):14
	from :0
irb(main):015:0> frodo.hobbit_has_ring?(samwise)
NoMethodError: private method `has_ring?' called for #<Hobbit:0x100374c80 @rooms=2, @name="Samwise", @has_ring=false>
	from ./hobbit.rb:19:in `hobbit_has_ring?'
	from (irb):15
	from :0

We can’t access the has_ring? method directly. We can’t access it indirectly from another method, specifying the hobbit we’re interested in. Not even if that hobbit is ourselves! Let’s reopen our class and add the i_have_the_ring? method:

class Hobbit
  def i_have_the_ring?
     has_ring?
  end
end

Now frodo has a way to check if he has the ring:

irb(main):021:0> frodo.i_have_the_ring?
=> true

In Ruby, protected methods are still open to other objects of the same type. So as long as the “receiver” (hobbit in hobbit.rooms) is the same class as the calling object, method access is granted.

But private methods are not allowed to specify a receiver at all. I can’t say frodo.has_ring? or even self.has_ring? inside the frodo object at all. I’m only allowed to say has_ring? and the object will assume it’s calling its own method.

Summary

While public methods are fairly straightforward, the difference between protected and private can cause problems if you’re not aware of it. Ruby’s private implementation is even stricter than C’s, not allowing receivers. But protected balances that out with the ability to “walk into sibling objects’ houses”.

I tend to use protected methods when I don’t intend for them to be called directly, and I don’t write tests for them. I write tests for the public methods that call them. And I generally avoid private methods, because I can’t envision needing to block sibling-to-sibling access (although I probably should if I’m not testing them, right?) but I don’t want to spend hours debugging a method call that breaks the no-receiver rule.

Lazy Loading Ruby Methods the Smart Way

September 3, 2010

I just spent an hour trying to solve a seemingly simple problem. Luckily, test-driven development is the reason I spotted this problem at all! Here’s where I started:

  class Survey < ActiveRecord::Base
    SUBMISSION_STATII = ['submitting', 'submitted']

    def in_submission?
      SUBMISSION_STATII.include? self.status
    end
  end

I want the in_submission? method to cache its response in an instance variable, so it doesn’t have to run the check every time. I plan on this method getting called a lot. I wrote my code to test, using shoulda and mocha:

  should "cache the result" do
    Survey.SUMISSION_STATII.expects(:include?).with('new').returns(false).once
    survey = Survey.new :status => 'new'

    survey.in_submission?
    survey.in_submission?
  end

This is simply verifying that the status is checked for inclusion only once, even if we call the method multiple times. And here’s the code that I thought should satisfy the test:

  def in_submission?
    @in_submission ||= SUBMISSION_STATII.include?(self.status)
  end

I only added the @in_submission ||= part. This is my standard trick, but in this case it failed. Why? because whenever @in_submission is set to false, it will trigger the full code to run again next time. No “caching” happens. We should check whether the instance variable is defined, not whether it equates to true:

  def in_submission?
    return @in_submission if defined?(@in_submission)
    @in_submission = SUBMISSION_STATII.include?(self.status)
  end

Sure, we lose our sweet one-liner, but we gain an hour of productivity when our tests pass the first try. Or even worse, you didn’t bother to test caching at all, so it’s just slowing down your app and possibly even mucking things up. But you are using TDD, aren’t you?


Follow

Get every new post delivered to your Inbox.