简体   繁体   中英

How do I include a module to a class such that the module overrides the class?

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.

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/

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

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

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

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'

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM