Ruby Dynamic Methods

We define methods using def keywords which is fine for most of the cases.

But consider a situation where you have to create a series of methods all of which have the same basic structure and logic then it seems repititive and not dry.

Ruby, being a dynamic language, you can create methods on the fly.

So, what does that mean?

lets see this simplest example:


  class A
    define_method :a do
      puts "hello"
    end

    define_method :greeting do |message|
      puts message
    end
  end

  A.new.a #=> hello
  A.new.greeting 'Ram ram' #=> Ram ram

The define_method defines an instance method in the receiver. The syntax and usage are self-explained in the example above.

lets see following example that may be useful in practical scenarios.


  class User

    ACTIVE = 0
    INACTIVE = 1
    PENDING = 2

    attr_accessor :status

    def active?
      status == ACTIVE
    end

    def inactive?
      status == User::INACTIVE
    end

    def pending?
      status == User::PENDING
    end

  end

  user = User.new
  user.status = 1

  user.inactive?
  #=> true
  user.active?
  #=> false

Refactored code using dynamic method definition:


  class User

    ACTIVE = 0
    INACTIVE = 1
    PENDING = 2

    attr_accessor :status

    [:active, :inactive, :pending].each do |method|
      define_method "#{method}?" do
        status == User.const_get(method.upcase)
      end
    end

  end

  user = User.new
  user.status = 1

  user.inactive?
  #=> true
  user.active?
  #=> false

We use define_method to define method dynamically.

We can also define instance methods with a class method, using this technique we can expose a class method that will generate the instance methods. COOL !

Example:

  class User

    ACTIVE = 0
    INACTIVE = 1
    PENDING = 2

    attr_accessor :status

    def self.states(*args)
      args.each do |arg|
        define_method "#{arg}?" do
          self.status == User.const_get(arg.upcase)
        end
      end
    end

    states :active, :inactive, :pending
  end


Now, what about class methods. The simplest way is

    class A
      class << self
        define_method method_name do
          #...
        end
      end
    end

There are instance_eval and class_eval also, which are used for dynamic method definition. These methods allow you to evaluate arbitrary code in the context of a particular class or object. These methods can be very confusing sometimes. You can read this discussion and this blog post and understand how they can be used.

From that discussion, we summerize

  Foo = Class.new
  Foo.class_eval do
    def class_bar
      "class_bar"
    end
  end
  Foo.instance_eval do
    def instance_bar
      "instance_bar"
    end
  end
  Foo.class_bar       #=> undefined method ‘class_bar’ for Foo:Class
  Foo.new.class_bar   #=> "class_bar"
  Foo.instance_bar       #=> "instance_bar"
  Foo.new.instance_bar   #=> undefined method ‘instance_bar’ for #<Foo:0x7dce8>

Note that, we don’t use define_method inside *_eval, becasue it does not matter if you use define_method inside class_eval or instance_eval it would always create an instance method.

And, we get this:

  Foo = Class.new

  Foo.class_eval do
    define_method "class_bar" do
      "class_bar"
    end
  end

  Foo.instance_eval do
    define_method "instance_bar" do
      "instance_bar"
    end
  end

  Foo.class_bar #=> undefined
  Foo.new.class_bar #=> "class_bar"
  Foo.instance_bar #=> undefined
  Foo.new.instance_bar #=> "instance_bar"

Next, we can invoke methods dynamically. One way to invoke a method dynamically in ruby is to send a message to the object. We can send a message to a class either within the class definition itself, or by simply sending it to the class object like you’d send any other message. This can be accomplished by usin send.

The simplest example could be:


  s= "hi man"

  p s.length #=> 6
  p s.include? "hi" #=> true

  p s.send(:length) #=> 6
  p s.send(:include?,"hi") #=> true

How can this be ever useful?

Lets see the following code( example taken from here)


  class ApplicationController < ActionController::Base
    protect_from_forgery
    helper_method :current_staff, :current_employee, current_admin

    def authenticate_staff!(opts={})
      current_staff || not_authorized
    end

    def current_staff
      current_user if current_user.is_a? Staff
    end

    def authenticate_employee!(opts={})
      current_employee || not_authorized
    end

    def current_employee
      current_user if current_user.is_a? Employee
    end

    def authenticate_admin!(opts={})
      current_admin || not_authorized
    end

    def current_admin
      current_user if current_user.is_a? Admin
    end
  end

And refactored one:

  %w(Staff Employee Admin).each do |k|
    define_method "current_#{k.underscore}" do
      current_user if current_user.is_a?(k.constantize)
    end

    define_method "authenticate_#{k.underscore}!" do |opts={}|
      send("current_#{k.underscore}") || not_authorized
    end
  end

Dynamically defined methods can help guard against method definition mistakes, avoid repetitive codes and be concise and smart.

Happy Metaprogramming.

Comments