define_method does what the name entails. Allows you to create a method (instance method) on the fly. Let's take a step back.
If you were a five year old I would probably use a videogame analogy: the powerups. You're Mario or Zelda or whatever and you are running through the game. You end up picking up a mushroom or a tool and you're (let's ignore the fact that these powerups are usually temporary) able to do something new that you weren't five seconds ago. Basically the videogame added, while you were already playing, a new skill to your player (and only yours if you were playing a multi player game). You can use this skill like if it were there from the beginning, even if you know it was not.
define_method is like one of those super powers in videogames: allows you to add functions (skills) to your object (a singular player).
A quick side note: most examples on the internet use define_method for a tangential (but still powerful) usage: shortening the amount of code you have to type. For example: let's say you have a Job object that can be in a few different states: created, queued, running, completed, failed and you want to ask the job if it's running. You might decide to use define_method to iterate over all the possible states (likely defined in constants or a hash) and create methods so you can do job.running?.
I think the true power of define_method is in the analogy with the videogame though, not just to let the developer write less code during the definition of the class.
Let's see some code, shall we?
Let's start from the side note, adding methods based on a series of states:
Following the game analogy here we're still a little off, because if you look closely we defined the method inside the class Job which means that ANY job will have those methods.
classPlayerattr_reader:namedefinitialize(name)@name=nameenddefwalkp"#{name}: walking"enddefrunp"#{name}: running"end# this allows you to add any method to a single instance at runtimedefcreate_method(name,&block)self.class.send(:define_method,name,&block)endendmario=Player.new("mario")luigi=Player.new("luigi")mario.walkluigi.runmario.create_method(:fly)dop"#{name}: flying like an eagle!"endmario.fly
This will print:
"mario: walking""luigi: running""mario: flying like an eagle!"
There's still an issue, if we were to inadvertantly ask Luigi to fly this would happen:
"luigi: fly like an eagle!"
Wait, what? The thing is that define_method operates on the class by default. What if we really want this to happen just for mario?
This is where I get lost in Ruby metaprogramming (I admit I never fully understood this syntax):
classPlayerattr_reader:namedefinitialize(name)@name=nameenddefwalkp"#{name}: walking"enddefrunp"#{name}: running"enddefcreate_method(name,&block)(class<<self;self;end).instance_evaldodefine_method(name,&block)endendendmario=Player.new("mario")luigi=Player.new("luigi")mario.walkluigi.runmario.create_method(:fly)dop"#{name}: fly like an eagle!"endmario.flyluigi.fly
this will print:
"mario: walking""luigi: running""mario: fly like an eagle!"
Traceback (most recent call last):
t.rb:34:in `<main>': undefined method `fly'for#<Player:0x00007fa7c0950cf8 @name="luigi"> (NoMethodError)
As you can see only mario now has that method. Fortunately there's a clearer way to create this "method generator", define_singleton_method:
classPlayerattr_reader:namedefinitialize(name)@name=nameenddefwalkp"#{name}: walking"enddefrunp"#{name}: running"enddefcreate_method(name,&block)self.define_singleton_method(name,block)endendmario=Player.new("mario")luigi=Player.new("luigi")mario.walkluigi.runmario.create_method(:fly)dop"#{name}: fly like an eagle!"endmario.flyluigi.fly
Ruby metaprogramming is definitely a complicated part of the language. :-)
Hi Tiffany!
define_method
does what the name entails. Allows you to create a method (instance method) on the fly. Let's take a step back.If you were a five year old I would probably use a videogame analogy: the powerups. You're Mario or Zelda or whatever and you are running through the game. You end up picking up a mushroom or a tool and you're (let's ignore the fact that these powerups are usually temporary) able to do something new that you weren't five seconds ago. Basically the videogame added, while you were already playing, a new skill to your player (and only yours if you were playing a multi player game). You can use this skill like if it were there from the beginning, even if you know it was not.
define_method
is like one of those super powers in videogames: allows you to add functions (skills) to your object (a singular player).A quick side note: most examples on the internet use
define_method
for a tangential (but still powerful) usage: shortening the amount of code you have to type. For example: let's say you have aJob
object that can be in a few different states:created
,queued
,running
,completed
,failed
and you want to ask the job if it's running. You might decide to usedefine_method
to iterate over all the possible states (likely defined in constants or a hash) and create methods so you can dojob.running?
.I think the true power of
define_method
is in the analogy with the videogame though, not just to let the developer write less code during the definition of the class.Let's see some code, shall we?
Let's start from the side note, adding methods based on a series of states:
This prints:
Following the game analogy here we're still a little off, because if you look closely we defined the method inside the class
Job
which means that ANY job will have those methods.Let's take it a step further:
This will print:
So, how we give a powerup to Mario but not to luigi? The standard library comes to our rescue:
This will print:
There's still an issue, if we were to inadvertantly ask Luigi to fly this would happen:
Wait, what? The thing is that
define_method
operates on the class by default. What if we really want this to happen just for mario?This is where I get lost in Ruby metaprogramming (I admit I never fully understood this syntax):
this will print:
As you can see only mario now has that method. Fortunately there's a clearer way to create this "method generator", define_singleton_method:
Ruby metaprogramming is definitely a complicated part of the language. :-)
Thank you for this amazing, detailed response! It is complicated but you definitely made it much clearer. :)
You're welcome :)