Ian Durkan
Ian Durkan

Reputation: 1232

How to add an instance variable to a class to back an added method?

I'm new to Ruby and confused about how I can make a method in Class similar to :attr_accessor, in that it adds methods to a user class, but so these added methods have access to a pre-initialized instance variable. It's difficult for me to explain so here is a greatly simplified sample of my efforts:

class Class
    def super_accessor_wow(attr_name)
        attr_name = attr_name.to_s 
        new_var_name = "@crazy_var_name"

        instance_variable_set(new_var_name, ["hi", "everyone"])

        module_eval(%Q/
            def super_#{attr_name}()
                return @#{attr_name}
            end

            def super_#{attr_name}=(value)
                @#{attr_name} = value
            end

            def greetings
                return #{new_var_name}
            end
        /)
    end
end

This is how I'm trying to use the new method on Class to modify my own class:

class Foo
    super_accessor_wow(:bar)
end

foo1 = Foo.new()
foo1.super_bar = 1000

puts foo1.super_bar
puts foo1.greetings.inspect

The first puts prints '1000'

The second puts prints 'nil', so my instance_variable_set call in super_accessor_wow seemingly has no effect.

I expected that the second puts would print '['hi', 'everyone']' by the way. All of this code is contained in a single Ruby file.

Upvotes: 0

Views: 135

Answers (2)

user1549550
user1549550

Reputation:

Your instance_variable_set is called when you call super_accessor_wow during the class definition. No instance of the class exists yet. You create an instance of the class when you call new. You could add your @crazy_var_name initialization to the constructor, or you could define it in the greetings method:

Put the default in a class variable, and initialize the instance variable in the constructor (be aware that this creates a constructor for your class, and if you then create your own constructor, it will override this one):

class Class
    def super_accessor_wow(attr_name)
        attr_name = attr_name.to_s
        new_var_name = "@crazy_var_name"
        new_var_name_default = "@#{new_var_name}"

        module_eval(%Q/
            #{new_var_name_default} = ["hi", "everyone"]

            def initialize()
              #{new_var_name} = #{new_var_name_default}
            end

            def super_#{attr_name}()
                return @#{attr_name}
            end

            def super_#{attr_name}=(value)
                @#{attr_name} = value
            end

            def greetings
                return #{new_var_name}
            end
        /)
    end
end

class Foo
    super_accessor_wow(:bar)
end

foo1 = Foo.new()
foo1.super_bar = 1000

puts foo1.super_bar
puts foo1.greetings.inspect
puts Foo.class_variable_get('@@crazy_var_name').inspect
puts foo1.instance_variable_get('@crazy_var_name').inspect

Outputs:

1000
["hi", "everyone"]
["hi", "everyone"]
["hi", "everyone"]

Define it in the greetings method:

class Class
    def super_accessor_wow(attr_name)
        attr_name = attr_name.to_s
        new_var_name = "@crazy_var_name"

        module_eval(%Q/
            def super_#{attr_name}()
                return @#{attr_name}
            end

            def super_#{attr_name}=(value)
                @#{attr_name} = value
            end

            def greetings
                #{new_var_name} = ["hi", "everyone"] unless #{new_var_name}
                return #{new_var_name}
            end
        /)
    end
end

class Foo
    super_accessor_wow(:bar)
end

foo1 = Foo.new()
foo1.super_bar = 1000

puts foo1.super_bar
puts foo1.greetings.inspect

Outputs

1000
["hi", "everyone"]

Upvotes: 1

Pete Schlette
Pete Schlette

Reputation: 2038

As noted in the comment, instance_variable_set takes a symbol, not a string, so we'll fix that up first.

instance_variable_set(new_var_name.to_sym, ["hi", "everyone"])

But the big issue is that instance_variable_set isn't being called by an instance of Foo, it's being called by the Foo class itself. So, an instance variable is being set, but not on what you expected.

Foo.instance_variable_get(:@crazy_var_name).inspect
# ["hi", "everyone"]

Upvotes: 0

Related Questions