3
votes

I'm having trouble dynamically defining class methods within a module. See code below. I get a NameError: undefined local variable or method when trying to reference another class method in the module. Seems like this might be a scope or context issue, but I've not been able to figure it out so far.

module Bar

  def self.included(base)
    base.extend ClassMethods
  end

  module ClassMethods

    def fruits
      ["apple", "orange", "banana"]
    end

    def example_function(string)
      string.upcase
    end

    fruits.each do |fruit|
      method_name = fruit.to_sym
      define_method(method_name) { example_function(fruit) }
    end

  end

end

class Foo
  include Bar
end

puts Foo.apple
puts Foo.orange
puts Foo.banana

I want to be able to call:

puts Foo.apple => "APPLE"
puts Foo.orange => "ORANGE"
puts Foo.banana => "BANANA"

Currently when I try any of these I get the following error: NameError: undefined local variable or method 'fruits' for Bar::ClassMethods:Module

Additionally, class methods in Bar::ClassMethods should be available to Foo, so I should be able to call:

puts Foo.fruits => ["apple", "orange", "banana"]

Requirements:

  1. All code is within a single module.
  2. The module allows mixing both instance and class methods (article below).
  3. The target methods are dynamically defined.

Read "Include vs Extend in Ruby" (esp. section titled "A Common Idiom") http://www.railstips.org/blog/archives/2009/05/15/include-vs-extend-in-ruby/

Help with this is greatly appreciated!

1

1 Answers

2
votes

The problem is, the fruits method is defined as an instance method, but you are calling it without an instance on ClassMethods. Just define it like this:

def self.fruits
  ["apple", "orange", "banana"]
end

And your code works.

EDIT: To make the fruits method accessible as a class method on Foo too, you have to declare the fruits array in a way that is accessible while inside the ClassMethods module. One way would be to declare the array as a class variable and use it in the fruits method and the each block. Look at the following code:

module Bar

  def self.included(base)
    base.extend ClassMethods
  end

  module ClassMethods
    @@fruits = ["apple", "orange", "banana"]

    def fruits
      @@fruits
    end

    def example_function(string)
      string.upcase
    end

    @@fruits.each do |fruit|
      method_name = fruit.to_sym
      define_method(method_name) { example_function(fruit) }
    end
  end
end

class Foo
  include Bar
end

puts Foo.apple
puts Foo.orange
puts Foo.banana
puts Foo.fruits