views:

138

answers:

5

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.

+3  A: 

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/

ucron
Thanks for the reference. However, the base.alias_method_chain :name, :upcase needs to be changed to base.alias_method_chain :name, :name_with_upcase
gsmendoza
+2  A: 

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

Firas Assaad
A: 

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
gsmendoza
A: 

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'
A: 

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.

http://gist.github.com/515856

Mario