Rahul
Rahul

Reputation: 452

No method error for class variable defined with attr_accessor

I want to define methods dynamically using an array of strings.

Here is a simple piece of code that should achieve that.


class SomeClass
  attr_accessor :my_array

  def initialize(user, record)
    @my_array=[]
  end

  my_array.each do |element|
    alias_method "#{element}?".to_sym, :awesome_method
  end

  def awesome_method
    puts 'awesome'
  end

end

When I instantiate this class in the console, I get the following error

NoMethodError (undefined method `each' for nil:NilClass)

What is wrong with this code and how to make it work. any help highly appreciated :)

Edit 1: What I ultimately want to achieve is to inherit from SomeClass and override my_array in the child class to dynamically define methods with its attributes like so

class OtherClass < SomeClass

  my_array = %w[method1 method2 method3] 
  # Some mechanism to over write my_array. 

end

And then use self.inherited to dynamically define methods in child class.

Is there a good way to achieve this?

Upvotes: 0

Views: 1311

Answers (2)

user6851498
user6851498

Reputation:

In your code, you use an instance variable (@my_array) and an attr_accessor over it, and then try to access my_array from class level (that is, from the body of the class definition, outside of any methods). But instance variables only exist at instance level, so it is not available in the class scope.

One solution (the natural one, and the one which you would probably use in other languages) is to use a class variable: @@my_array. But class variables in ruby are a little problematic, so the best solution would be to make use of class instance variables, like that:

class SomeClass
  class << self
    attr_accessor :my_array
  end

  @my_array=[]

  def initialize(user, record)
  end

  @my_array.each do |element|
    alias_method "#{element}?".to_sym, :awesome_method
  end

  def awesome_method
    puts 'awesome'
  end

end

The syntax is a little tricky, so, if you look that up and it still doesn't makes sense, try just reading about scopes and using a regular class variable with @@.

Edit:

Ok, so, after your edit, it became more clear what you are trying to accomplish. A full working example is like follows:

class SomeClass
  class << self
    attr_accessor :my_array
  end

  @my_array=[]

  def awesome_method
    puts 'awesome'
  end

  def self.build!
    @my_array.each do |element|
      self.define_method("#{element}?".to_sym){ awesome_method }
    end
  end

end

class ChildClass < SomeClass
  @my_array = %w[test little_test]

  self.build!
end

child_instance = ChildClass.new

child_instance.test?
>> awesome

child_instance.little_test?
>> awesome

So, I've made some tweaks on SomeClass:

  1. It does not need an initialize method
  2. I tried to use the inherited hook for this problem. It won't ever work, because this hook is called as soon as "ChildClass < SomeClass" is written, and this must be before you can define something like @my_array = %w[test little_test]. So, I have added a self.build! method that must be called in the child instances so that they build their methods from my_array. This is inevitable, but I think it is also good, because it makes more explicit in the subclasses that you are doing something interesting there.
  3. I think you want "define_method", not "alias_method".
  4. awesome_method in passed in a block, which is ruby's way of doing functional programming.

With that done, ChildClass inherits from SomeClass, and it's instances have the dynamically created methods 'test?' and 'little_test?'.

Upvotes: 3

Vibol
Vibol

Reputation: 1168

You need to change my_array to class level accessible, in my case class constant.

class SomeClass
  DYNAMIC_METHOD_NAMES = %w(method_a method_b method_C).freeze

  def initialize(user, record)
  end

  DYNAMIC_METHOD_NAMES.each do |element|
    alias_method "#{element}?".to_sym, :awesome_method
  end

  def awesome_method
    puts 'awesome'
  end
end

Upvotes: 2

Related Questions