316 Advanced Ruby: More metaprogramming with Modularity and ActiveSupport::Concern [2d]

Updated . Posted . Visible to the public.

This lesson continues with metaprogramming: how Ruby looks up methods, what include, extend and prepend do, and how ActiveSupport::Concern and our Modularity gem build on that.

Important

Work on this lesson in builder mode.

Learning goals

  • You can explain how Ruby looks up a method: the ancestor chain of a class, including singleton classes, and where include, extend and prepend insert a module into it.
  • You can explain why extending an object with a module is the same as including the module into the object's singleton class.
  • You can find where a method is defined even when metaprogramming defined it, e.g. with Method#source_location, the Rails API docs or your IDE.
  • You can write a monkey patch that wraps an existing method and calls the original, and you can explain why Module#prepend is cleaner than aliasing and overriding.
  • You can forward arbitrary positional and keyword arguments to another method (e.g. with (...)) and explain what changed about arguments in Ruby 3.
  • You can explain what ActiveSupport::Concern adds to a plain module and when it is worth using.
  • You can build a parameterized module with our Modularity gem and explain what its bracket syntax does.
  • You can compare a plain module, a Concern and a Modularity trait for the same task and argue which one fits.

Resources

Read what's new to you, skim what's familiar, skip what you already master. Stop when you can meet the learning goals.

Your agent can also generate an overview, a tutorial or an explanation for anything here, tailored to what you already know. Just ask.

Exercises

Finding method definitions

Because we have metaprogramming, it might not be apparent where a method is defined. There are several ways to find a source:

Monkey patch

Write a monkey patch that patches the #attributes= methods of all your models.

The monkey patch should print the number of attributes that will be set, then call the original method:

movie = Movie.find(1)

movie.attributes = { title: 'Sunshine', year: '2007' }
# Console prints "Setting 2 attributes"

# Check that the original #attributes= method is still called:
movie.title # => 'Sunshine'
movie.year # => 2007

Write two versions of that patch:

  1. Using Module#prepend.
  2. By aliasing the existing method and then overriding it.

Hint

  • The base class for all your models is ApplicationRecord. Prepend your patch with ApplicationRecord#prepend(YourPatch).
  • A monkey patch is usually an initializer Show archive.org snapshot .
  • We have some tips for organizing monkey patches.
  • When you add or change an initializer you need to restart your Rails server or console for the changes to be picked up. Only changes in app are picked up automatically.

Virtual attributes

With plain Ruby

Look at the following "mini Active Record" interface:

class Login
  include Attributes

  attribute :email
  attribute :password
  attribute :remember_me, default: true
end

login = Login.new
login.email = "me@example.org"
login.password = "mypassword"

login.email         # => "me@example.org"
login.password      # => "mypassword"
login.remember_me   # => true

login.attributes    # => { email: "me@example.org", password: "mypassword", remember_me: true }

login.remember_me = false
login.remember_me   # => false

Implement the Attributes module in plain ruby, so it supports the API above.

With ActiveSupport::Concern

Add a second implementation AttributesConcern that uses ActiveSupport::Concern.

It should work identical to the first implementation.

With Modularity

Add a third implementation DoesAttributes that uses Modularity.

For modularity we often use an alternative interface:

class Login
  include DoesAttribute[:email]
  include DoesAttribute[:password]
  include DoesAttribute[:remember_me, default: true]
end

Implement this new interface. For ruby 3 make sure that you use at least modularity 3.1.0 Show archive.org snapshot .

Compare implementations

Now compare your implementations. Which one do you like best?

Does modularity offer features the other approaches don't?

Profile picture of Henning Koch
Henning Koch
Last edit
Fabian Schwarz
License
Source code in this card is licensed under the MIT License.
Posted by Henning Koch to makandra Curriculum (2015-09-22 11:23)