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
Likeableis included within the class, the (Rails) class attributeusers_to_be_notifiedis created. - Lines 18-22: Within the
Likeablemodule is defined the class methodnotifiable_users. This method setsusers_to_be_notifiedto the parameters. - Line 34:
notifiable_users :creator, :tagged_users: thenotifiable_usersclass method is called with two arguments (representing ActiveRecord attributes). This sets the class attributeusers_to_be_notifiedto[:creator, :tagged_users](for theVideoclass). - Line 14: we retrieve the values behind
:creatorand: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!