Refactoring is my favorite hobby. Nothing feels quite as satisfying than extracting some common methods to an elegantly named module.
Quickly though, I usually run into trouble - how exactly am I supposed to pass an argument to a module in Ruby? The answer is: through some metaprogramming! (such a scary word)
Let’s draft up a (somewhat contrived) example using Ruby on Rails.
Imagine a Facebook-like application with the following models and Likeable
module:
Through the Likeable
module, we can now retrieve the total number of likes for
any instance of the Image
(or Video
) class with the following pattern:
Image.find(params[:id]).total_likes
.
That’s pretty neat.
Now, let’s assume that the two classes handle differently which users get
notified when a new like is submitted. As it’s related to likes, we’d like to
include this behaviour within the Likeable
module.
However, Ruby does not provide an easy way to pass an argument to a method
defined within a module. We could of course add some conditional logic to the
module method, but that would become messy quickly as we create other
classes mixed in the Likeable
module.
Let’s not do that.
Instead, we’d like to be able to directly define who should be notified for each class within the model, like so:
Turns out, we can use some metaprogramming to mimic the passing of an argument:
Here’s what this code does:
- Lines 5-7: when the module
Likeable
is included within the class, the (Rails) class attributeusers_to_be_notified
is created. - Lines 18-22: Within the
Likeable
module is defined the class methodnotifiable_users
. This method setsusers_to_be_notified
to the parameters. - Line 34:
notifiable_users :creator, :tagged_users
: thenotifiable_users
class method is called with two arguments (representing ActiveRecord attributes). This sets the class attributeusers_to_be_notified
to[:creator, :tagged_users]
(for theVideo
class). - Line 14: we retrieve the values behind
:creator
and:tagged_users
for the specific instances of the class, and notify them individually.
You can now create a new class and easily define who gets notified for new likes:
There you go!