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
- Find out how
ActiveSupport::Concern
works. - Understand the documentation for our Modularity gem Show archive.org snapshot
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:
- Learn about
Method#source_location
Show archive.org snapshot . - Look up a method like
save!
in the Rails API docs Show archive.org snapshot . The result should have a link to the source location on GitHub. - CTRL+click on a method call in RubyMine. This will open a list of possible source locations.
Note
Given that Ruby is a dynamic language, RubyMine can only guess. RubyMine rarely knows the type of an object.
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:
- Using
Module#prepend
. - By aliasing the existing method and then overriding it.
Hint
- The base class for all your models is
ApplicationRecord
. Prepend your patch withApplicationRecord#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?