Ruby: Mixin, which adds dynamic instance methods whose names are created using the class method

I have the following:

module Thing def self.included(base) base.send :extend, ClassMethods end module ClassMethods attr_reader :things def has_things(*args) options = args.extract_options! # Ruby on Rails: pops the last arg if it a Hash # Get a list of the things (Symbols only) @things = args.select { |p| p.is_a?(Symbol) } include InstanceMethods end end module InstanceMethods self.class.things.each do |thing_name| # !!! Problem is here, explaination below define_method "foo_for_thing_#{thing_name}" do "bar for thing #{thing_name}" end end end end 

In another class that mixes in the Thing module:

 class Group has_things :one, :two, :option => "something" end 

When calling has_things inside the class, I would like to have the dynamic methods "foo_for_thing_one" and "foo_for_thing_two". For instance:

 @group = Group.new @group.foo_for_thing_one # => "bar for thing one" @group.foo_for_thing_two # => "bar for thing two" 

However, I get the following error:

 `<module:InstanceMethods>': undefined method `things' for Module:Class (NoMethodError) 

I understand that β€œself” in the problem line above (the first line of the InstanceMethods module) refers to the InstanceMethods module.

How can I refer to the method of the β€œthings” class (which returns [: one ,: two] in this example), so I can scroll through and create dynamic instance methods for each? Thank you If you have other suggestions for this, let me know.

+6
ruby module ruby-on-rails mixins
source share
1 answer

Quick response:

Place the contents of InstanceMethods inside the has_things method has_things and remove the InstanceMethods module.

The best answer:

Your use of the InstanceMethods-ClassMethods anti-template is especially unreasonable here as well when handling goods, which it added to your confusion regarding the scope and context. Do the simplest thing that could work. Do not copy another user's code without critical thinking.

The only module you need is ClassMethods, which should be given a usable name and should not be included, but rather use the extension of the class that you want has_things to provide. Here is the simplest thing that could work:

 module HasThings def has_things(*args) args.each do |thing| define_method "thing_#{thing}" do "this is thing #{thing}" end end end end class ThingWithThings extend HasThings has_things :foo end ThingWithThings.new.thing_foo # => "this is thing foo" 

Only add complexity (extract options, normalize input, etc.) when you need it. The code is just in time, not just in case.

+18
source share

All Articles