Dynamic method definition with ruby’s .define_method

Camille Feghali
2 min readFeb 10, 2019

--

It’s been two weeks since the start of class here at the Flatiron school’s software engineering immersive, and we’ve already started using Ruby’s very powerful ActiveRecord module that allows developers to map object relations to databases using very little code.

While working on a mini-project to get accustomed to the gem, I set up my tables, migrations, and relationships like so:

class Customer < ActiveRecord::Base

has_many :reviews
has_many :restaurants, through: :reviews

end

class Restaurant < ActiveRecord::Base
​ has_many :reviews
has_many :customers, through: :reviews

end

class Review < ActiveRecord::Base

belongs_to :restaurant
belongs_to :customer

end

I ran a .methods on my Customer model to know what new methods it inherited from ActiveRecord. I realized something very peculiar.

Notice the following methods.

 :after_remove_for_reviews?,
:after_remove_for_reviews=,
:before_add_for_restaurants,
:before_add_for_restaurants?,
:before_add_for_restaurants=,

ActiveRecord defined custom methods according to the relationships established between my models. That means that if my models were named Squirrel andAcorn, these methods would be named after_remove_for_acorns? and before_add_for_squirrels etc - We will not talk about what these methods do in this blogpost - but that means that in ruby, there is a way to define methods dynamically! and that is something that I find very exciting.

In comes the .define_method method.

The define_method method allows us to do just that, define a method — but how is it different from the traditional way of placing your logic between a def and end block?

Let’s see how it works with a simple example. Say we have the following Mouse class:

class Mouse

define_method("eat_cheese") do |argument|
"The mouse is eating #{argument} cheese"
end

define_method("eat_wires") do |argument|
"The mouse is eating #{argument} wires"
end

define_method("eat_nuts") do |argument|
"The mouse is eating #{argument} nuts"
end
end

jerry = Mouse.new
puts jerry.eat_cheese("cheddar")
puts jerry.eat_wires("electrical")
puts jerry.eat_nuts("hazel")

#==> The mouse is eating cheddar cheese
#==> The mouse is eating electrical wires
#==> The mouse is eating hazel nuts

Now this is all good and dandy, but, really we could’ve just defined the methods the traditional way with def and end statements right?

The true power of define_method is unlocked when used to handle the creation of methods based on a particular naming. Take the following example:

class Mouse
["cheese", "wires", "nuts"].each do |food|
define_method("eat_#{food}") do |argument|
"The mouse is eating #{argument} #{food}"
end
end
end

jerry = Mouse.new
puts jerry.eat_cheese("cheddar")
puts jerry.eat_wires("electrical")
puts jerry.eat_nuts("hazel")

#==> The mouse is eating cheddar cheese
#==> The mouse is eating electrical wires
#==> The mouse is eating hazel nuts

Not only did we do the same job with much fewer lines of code, we could change the array elements, and our methods would change names — And that’s awesome because it allows developers to write dynamic code that can adapt to different situations.

Reference:

http://rubymonk.com/learning/books/2-metaprogramming-ruby/chapters/25-dynamic-methods/lessons/72-define-method

--

--