Aliasing the Ruby & Rails way
A method alias can make sense or even can be necessary for various reasons.
- Satisfy TemplateMethod requirements using duck typing
- Increasing a methods meaning/ readability
- Extend a methods behavior (in the sense of aspect oriented programming)
Since there are 2 ways of aliasing in Rails I’m going to disclose both and when to use which one.
Let’s start with the alias_method
.
Pure Ruby aliasing
Consider a PORO like Article.
class Article
attr_reader :name
alias_method :topic, :name
def initialize(name)
@name = name
end
end
and use it accordingly:
article = Article.new('Ruby')
article.name
# => "Ruby"
article.topic
# => "Ruby"
Simple as that.
However with Rails models we have to respect how Rails model work.
Rails model aliasing
First we try to apply the Ruby aliasing to Rails models as well.
rails g model Article name:string && rake db:migrate
and the corresponding model with an alias topic
on the attribute method name
:
class Article < ApplicationRecord
alias_method :topic, :name
end
Unfortunately that does not work.
Article.new
# => NameError: undefined method `name' for class 'Article'
The reason is how Rails model work internally. Ruby on Rails creates attribute accessors dynamically and therefore helps itself with meta programming and the method_missing
approach. But at the time of class instantiation the accessors are not created yet.
However when alias_method
tries to make a copy of the original method, it consequentially fails withNameError
.
The solution is Module#alias_attribute
. Ruby on Rails extended Module
and enables aliasing in ActiveRecord. The principle is simple. A new method is created by meta programming, which internally calls the original method.
Thus the model has to look like:
class Article < ApplicationRecord
alias_attribute :topic, :name
end
with:
article = Article.new(name: 'Ruby')
article.name
# => "Ruby"
article.label
# => "Ruby"
Furthermore alias_attribute
generates the appropriate setter methods.
article = Article.new(name: 'Ruby')
article.topic = 'RubyOnRails'
article.name
# => "RubyOnRails"
article.topic
# => "RubyOnRails"
Since the alias_attribute
is glued toModule
, it works for each class even POROs:
class Book
attr_accessor :name
alias_attribute :title, :name
def initialize(name:)
@name = name
end
end
With that Book
provides two more methods label
and label=
book = Book.new name: 'Ruby'
book.title
# => "Ruby"
book.title = 'RubyOnRails'
book.title
# => "RubyOnRails"
book.name
# => "RubyOnRails"