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

Updated . Posted . Visible to the public.

Learn

Method lookup

Understand all the terms in How Ruby method lookup works, in particular:

  • include
  • extend
  • singleton class
  • prepend

Do you understand why object.extend(SomeModule) is the same as object.singleton_class.include(SomeModule)?

How does include and extend work together with inheritance?

You may also read more about the Ruby Object Model, if all of this is quite confusing to you.

Metaprogramming libraries

Forwarding arguments

  • Read and understand the card "Changes to positional and keyword args in Ruby 3.0"
  • Monkey patching existing methods often requires you to handle a dynamic set of (keyword) arguments. The "forward everything" syntax (...) can be quite handy in some cases.

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.

Virtual attributes 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?

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)