Da35c9fbd63d37a82fee471e639a1840

Hi,

I'm having some trouble getting a mixin to work for a custom Rails plugin. In the code below, I reopen the Person class and redefine the name method - all works as expected. However, if instead I include the Title module (which redefines the name method), the original Person.name method is not overridden.

Is there something I am doing wrong when mixing in the module? I'd expect the mixed-in name method to override the original Person.name method...or can methods defined in a module not override those defined in the class?

I'd greatly appreciate any advice on this.

# title.rb
module Title
  def name
    'Mr. ' + self.first_name + ' ' + self.last_name
  end
end

# person.rb
class Person < ActiveRecord::Base
  def name
    self.first_name + ' ' + self.last_name
  end
end

# ====
# Without mixin, expect 'Joe Bloggs'
p = Person.new(:first_name => 'Joe', :last_name => 'Bloggs')
p.name
 => 'Joe Bloggs' # pass

# ====
# Directly insert into class, expect 'Mr. Joe Bloggs'
Person.class_eval do 
  def name
    'Mr. ' + self.first_name + ' ' + self.last_name
  end
end

p = Person.new(:first_name => 'Joe', :last_name => 'Bloggs')
p.name
 => 'Mr. Joe Bloggs' # pass

# ====
# With mixin, expect 'Mr. Joe Bloggs'
Person.class_eval do 
  include Title
end

p = Person.new(:first_name => 'Joe', :last_name => 'Bloggs')
p.name
 => 'Joe Bloggs' # fail

Refactorings

No refactoring yet !

A8d3f35baafdaea851914b17dae9e1fc

Adam

March 18, 2009, March 18, 2009 02:40, permalink

No rating. Login to rate!
module Title
  def self.included(klass)
    klass.alias_method_chain(:name, :mr)
  end
  
  def name_with_mr
    [ 'Mr.', first_name, last_name ].join(' ')
  end
end

class Person < ActiveRecord::Base
  def name
    [ first_name, last_name ].join(' ')
  end
end

Person.send(:include, Title)
Da35c9fbd63d37a82fee471e639a1840

Chris Blunt

March 18, 2009, March 18, 2009 11:06, permalink

No rating. Login to rate!

Thanks for your code Adam, I tried putting it into my plugin my still could not get the expected output. It might be worth me saying that my application (at runtime) will not know which form of the Person class it will be using, i.e. if my plugin is installed then person.name should return "Mr Joe Bloggs"; if my plugin is not installed, then person.name should just return "Joe Bloggs".

Thanks,
Chris

Da35c9fbd63d37a82fee471e639a1840

Chris Blunt

March 18, 2009, March 18, 2009 11:49, permalink

No rating. Login to rate!

OK, it gets stranger. This works the for the first request (i.e. hit Refresh in my Browser once). Every subsequent refresh uses the original behaviour (specified in person.rb) until I restart my server.

-- Edit: It turns out this is because Rails does not reload plugin code after each request (in development environment), but does reload the default client.rb class. However, see below for working code....

# (plugin)/lib/title.rb
module Title
  def self.included(klass)
    klass.class_eval do
      def name
        ['Mr.', first_name, last_name].join(' ')
      end
    end
  end
end

# (plugin)/init.rb
require 'title'
Person.send :include, Title

# model/person.rb
class Person < ActiveRecord::Base
  # This is the method I want my 'mixin' to override.
  def name
    [first_name, last_name].join(' ')
  end
end
Da35c9fbd63d37a82fee471e639a1840

Chris Blunt

March 18, 2009, March 18, 2009 15:07, permalink

No rating. Login to rate!

Finally figured this one out after a day of messing around. The answer came from one of Ryan's awesome railscasts (http://railscasts.com/episodes/33-making-a-plugin) which has a snippet of code that seems to work.

With the plugin loaded, Person.name gets overridden and returns 'Mr. Joe Bloggs'. Without the plugin, Person.name returns just 'Joe Bloggs'.

# plugin/title/lib/title.rb
module Title
  def name
    ['Mr.', first_name, last_name].join(' ')
  end
end

class Person < ActiveRecord::Base
  include Title 
end

# plugin/title/init.rb
require 'title'

# model/person.rb
class Person < ActiveRecord::Base
  def name
    [first_name, last_name].join(' ')
  end
end
F1e3ab214a976a39cfd713bc93deb10f

Tj Holowaychuk

March 19, 2009, March 19, 2009 16:25, permalink

No rating. Login to rate!

I would not do this:

['Mr.', first_name, last_name].join(' ')

# use:

'Mr. %s %s' % [first_name, last_name]

# or just regular substitution, no need for joining an array like that
3ab05698e7707b70c43b2a69d0a1afcd

guenstiges hotel

February 24, 2010, February 24, 2010 05:48, permalink

No rating. Login to rate!

Northern Carefully,agree those few someone twice wish switch your get forget sequence expensive thought building technique lunch war tax those structure sea reading stop household path even light he card exercise teacher involve deal burn actual understanding simple historical favour sun oil future second later else to open wife promise up assessment labour liberal lord reach sexual married guide treatment consist plastic finish information at hurt soil surely flower college concentrate debate collect box alone home declare except examination thing take gentleman shut well of would religion well reach talk

Northern Carefully,agree those few someone twice wish switch your get forget sequence expensive thought building technique lunch war tax those structure sea reading stop household path even light he card exercise teacher involve deal burn actual understanding simple historical favour sun oil future second later else to open wife promise up assessment labour liberal lord reach sexual married guide treatment consist plastic finish information at hurt soil surely flower college concentrate debate collect box alone home declare except examination thing take gentleman shut well of  would religion well reach talk
3d9da2e108fe70bb5ccafef355807a60

Mario T. Lanza

August 9, 2010, August 09, 2010 17:21, permalink

No rating. Login to rate!

I think there is a more elegant way to handle this. Rather than define the methods in the base class (Magician) and then include the module with the replacement methods (WizardSkills), define the methods for the base class in its own module (MagicianSkills). This will cause things to behave in a more desirable manner and also allow you to call "super" if need be.

module WizardSkills
  def trick
    super # if desired
    puts "The #{self.class} turned a man into a rabbit"
  end
end

module MagicianSkills
  def trick
    puts "The #{self.class} pulled a rabbit out of a hat"
  end
end

class Magician
  include MagicianSkills	
  include WizardSkills
end

Magician.new.trick

Your refactoring





Format Copy from initial code

or Cancel