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, we can now retrieve the total number of likes for
any instance of the
Video) class with the following pattern:
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
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
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
Likeableis included within the class, the (Rails) class attribute
- Lines 18-22: Within the
Likeablemodule is defined the class method
notifiable_users. This method sets
users_to_be_notifiedto the parameters.
- Line 34:
notifiable_users :creator, :tagged_users: the
notifiable_usersclass method is called with two arguments (representing ActiveRecord attributes). This sets the class attribute
[:creator, :tagged_users](for the
- Line 14: we retrieve the values behind
:tagged_usersfor 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!