0

I am attempting to add a method to all objects which allows adding variables to instances on the fly. However, I have run into the issue that when I begin the class block, the method arguments are out of scope and thus no longer available.

How might I access the method arguments from within the class block? Or is there a better way to do this?

class Object
    def addvar(name, value = nil)
        puts name # => "test"
        class << self
            puts name # => returns nil!
            attr_accessor name # => nil is not a symbol nor a string (TypeError)
        end
        self.instance_variable_set("@" + name.to_s, value)
    end
end

x = Object.new
x.addvar("test", 3)

It's also possible to use eval with string substitution, but that's something I would rather avoid for security reasons:

class Object
    def addvar(name, value = nil)
        eval("class << self
                  attr_accessor :#{name}
             end
             self.#{name} = #{value}")
    end
end

x = Object.new
x.addvar(:test, 3)
puts x.test
2
  • Why do you want to add variables on the fly? What do you try to achieve? Commented Oct 9, 2017 at 6:18
  • The use I thought of would be for mods in games; rather than having to mess with the underlying classes (possibly breaking inter-mod compatibility), modders can instead directly add properties to instances. Commented Oct 9, 2017 at 6:20

1 Answer 1

1

You might need the instance_variable_set, instance_variable_get, and other methods for metaprogramming in Ruby. Honestly, this is what attr_accessor uses under the hood.

The following code adds a singleton method to the object you call the add_var method on by using define_singleton_method:

class Object
  def addvar(name, value = nil)
    instance_name = "@#{name}"

    instance_variable_set(instance_name, value)

    define_singleton_method(name) do
      instance_variable_get(instance_name)
    end

    define_singleton_method("#{name}=") do |new_value|
      instance_variable_set(instance_name, new_value)
    end
  end
end

x = Object.new
x.addvar("test1", 3)
p x.test1

p Object.new.test1

=> 3
=> 1.rb:20:in `<main>': undefined method `test1' for #<Object:0x007fc98b161e10> (NoMethodError)

If you want to declare the method for any object of the class, you can use define_method on the class:

class Object
  def addvar(name, value = nil)
    instance_name = "@#{name}"

    instance_variable_set(instance_name, value)

    self.class.send(:define_method, name) do
      instance_variable_get(instance_name)
    end

    self.class.send(:define_method, "#{name}=") do |new_value|
      instance_variable_set(instance_name, new_value)
    end
  end
end

x = Object.new
x.addvar("test1", 3)
p x.test1

p Object.new.test1

=> 3
=> nil

By the way, you still can use attr_accessor, you can just call it on the class object:

class Object
  def addvar(name, value = nil)
    instance_variable_set("@#{name}", value)

    self.class.send(:attr_accessor, name)
  end
end

x = Object.new
x.addvar("test1", 3)
p x.test1

x.test1 = 5
p x.test1

p Object.new.test1
Sign up to request clarification or add additional context in comments.

3 Comments

This adds a get method, but what about a set method? Ideally, one would only need to use addvar once, and then can use the syntax x.test1 = 5.
Great! I also noticed that you can also use self.singleton_class instead of self.class in your last example, and it will not add the property to all objects.
In general, class << self is not often needed. Always better to use the singleton methods if possible

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.