Reputation: 1394
Is there a way to include a module to a class such that the module's methods override the methods of the class? For example:
module UpcasedName
def name
@name.upcase
end
end
class User
attr_accessor :name
include UpcasedName
end
u = User.new
u.name = 'john'
puts u.name # outputs 'john', not 'JOHN'
In the example above, u.name is 'john', not 'JOHN'. I know that if I extend the user object instead of including the module to the class, this will work
module UpcasedName
def name
@name.upcase
end
end
class User
attr_accessor :name
end
u = User.new
u.name = 'john'
u.extend UpcasedName
puts u.name # outputs 'JOHN'
However, I want to include the module at the class level, not object level.
Upvotes: 4
Views: 742
Reputation: 6790
It may not always be an option, but I think it's better if you simply move the class's methods into its own module and mix that module back into the class. This feels cleaner to me.
Upvotes: 0
Reputation:
The problem here is that attr_accessor
creates a User.name method that override the UpcasedName.name method, so one solution would be using attr_writer
:
module UpcasedName
def name
@name.upcase
end
end
class User
attr_writer :name
include UpcasedName
end
u = User.new
u.name = 'john'
puts u.name # outputs 'JOHN'
Upvotes: 0
Reputation: 1394
Based on ucron's answer, it's possible to do this without activesupport as follows:
module UpcasedName
def self.included(base)
base.send :alias_method, :name_without_feature, :name
base.send :alias_method, :name, :name_with_upcase
end
def name_with_upcase
@name.upcase
end
end
class User
attr_accessor :name
include UpcasedName
end
u = User.new
u.name = 'john'
puts u.name
Upvotes: 0
Reputation: 25750
Include is similar to inheriting from a another class, in the sense that the methods of the class you include a module into have precedence over the included methods. You can even call super in your class to access the method from the module:
class User
attr_accessor :name
def name
super
end
include UpcasedName
end
u = User.new
u.name = 'john'
puts u.name # outputs 'JOHN'
Here's an article about it: include vs. extend in Ruby
Upvotes: 2
Reputation: 2852
Right now there have been several approaches to doing this. Well the first and most basic would be to use alias_method_chain from ActiveSupport
require 'activesupport'
module UpcasedName
def self.included( base )
base.alias_method_chain :name, :upcase
end
def name_with_upcase
@name.upcase
end
end
class User
attr_accessor :name
include UpcasedName
end
u = User.new
u.name = 'john'
puts u.name
The approach you posted is actually similar to the approach posted by Bruce Williams' method here : http://www.codefluency.com/articles/2009/01/03/wrapping-a-method-in-ruby
If you're really hardcore about this you can follow the approaches posted by Yehuda Katz here: http://yehudakatz.com/2009/01/18/other-ways-to-wrap-a-method/
Upvotes: 3