31

I dynamically created an instance variable within my class:

class Mine
  attr_accessor :some_var

  def intialize
    @some_var = true
  end

  def my_number num
    self.instance_variable_set "@my_#{num}", num
  end
end

How do I make @my_#{num} now as an attr value?

e.g. I want to be able to do this:

dude = Mine.new
dude.my_number 1
dude.my_1
=> 1
eywu
  • 2,454
  • 1
  • 20
  • 24

7 Answers7

31

this answer doesn't pollutes the class space, example.. if i do mine.my_number 4 then the other instances of Mine will not get the my_4 method.. this happens because we use the singleton class of the object instead of the class.

class Mine
  def my_number num
    singleton_class.class_eval { attr_accessor "my_#{num}" }
    send("my_#{num}=", num)
  end
end

a = Mine.new
b = Mine.new
a.my_number 10 #=> 10
a.my_10 #=> 10
b.my_10 #=> NoMethodError
Orlando
  • 8,452
  • 2
  • 53
  • 48
  • Is there a reason using `singleton_class`? Also being a one-liner, `class_eval { attr_accessor :"my_#{num}" }` syntax would be cleaner :) – Halil Özgür Nov 12 '15 at 14:49
  • 2
    @HalilÖzgür you want to define that method only for that instance, so that's why you use `singleton_class`, if you use `self.class`, then all instances get the method and you don't want that. you are right with the syntax, I'll make the change – Orlando Nov 12 '15 at 15:45
  • btw singleton_class is the same as doing `class << self` – Orlando Nov 12 '15 at 15:47
  • I mean the above code produces the same output without `singleton_class`. – Halil Özgür Nov 12 '15 at 19:24
  • 1
    @HalilÖzgür `class_eval` is a class method, not an instance method – Orlando Nov 12 '15 at 20:54
  • oh, sorry! I was confusing something else with this, interestingly it seemed to display the same result for once. – Halil Özgür Nov 12 '15 at 22:29
24

This can be accomplished using __send__. Here:

class Mine
  attr_accessor :some_var

  def intialize
    @some_var = true
  end

  def my_number num
    self.class.__send__(:attr_accessor, "my_#{num}")
    self.__send__("my_#{num}=", num)
  end
end

dude = Mine.new
dude.my_number 1
puts dude.my_1

=> 1
Dorkus Prime
  • 976
  • 7
  • 11
  • 2
    This will define accessors on all instances, not just the one you called `my_number` on. I added an additional answer that only adds methods for the instances you added an instance variable for. – carpeliam Jun 06 '11 at 21:40
10

Easy. You can dynamically define the attribute reader inside the my_number method:

  def my_number num
     self.instance_variable_set "@my_#{num}", num
     self.class.class_eval do
        define_method("my_#{num}") { num }
     end
  end

see if that works for you

Gerry
  • 5,146
  • 1
  • 21
  • 33
6

You may want to use OpenStruct:

require "ostruct"

class Mine < OpenStruct
end

dude = Mine.new
dude.my_number = 1
dude.my_number # => 1

I don't know why you'd want dude.my_1 to return 1 - isn't that giving you back what you already have?

Andrew Grimm
  • 70,470
  • 47
  • 186
  • 310
4

There's one problem with the two methods here... if an instance variable is set in one instance, its accessor will be available to all instances, because you're defining methods on self.class instead of on self.

dude = Mine.new
dude.my_number 1
puts dude.my_1
dudette = Mine.new
dudette.my_1 = 2    # works, but probably shouldn't
dudette.my_number 2
dude.my_2 = 3       # works, but probably shouldn't

What you probably want to do is modify only the instance that has the instance variable:

class Mine
  # ...
  def my_number num
    class << self
      attr_accessor "my_#{num}"
    end
    self.send("my_#{num}=", num)
  end
end

This way, instance variables only get accessors on the objects they were created for. I also didn't bother with instance_variable_set, because if you're setting an accessor, then I think it reads better to just reuse that. But that's a style call. The big deal here is calling class << self instead of self.class.

carpeliam
  • 6,306
  • 2
  • 35
  • 41
0

older thread, but I found it useful thank you. Here is the code Dorkus Prime's answer, but also taking instance vars from name\values in a hash

@cookies = browser.cookies.to_a

@cookies.each do |cookie|
self.class.__send__(:attr_accessor, "#{cookie[:name]}")
self.__send__("#{cookie[:name]}=",cookie[:value])
end
0

Yet another solution to add to the pile, define_singleton_method:

class Mine
  def my_number num
    define_singleton_method("num_#{num}") { num }
  end
end

One side effect of all of these solutions is that if you call it multiple times with different numbers, you end up with a bunch of methods on your object:

dude = Mine.new
dude.my_number 1
dude.my_number 5
dude.my_1
=> 1
dude.my_5
=> 5

We can fix this by removing the old method:

class Mine
  def my_number num
    old_num = @num
    if @num
      # need to use `old_num` local variable
      # instance var scope is different inside `class_eval`
      singleton_class.class_eval { remove_method("num_#{old_num}") }
    end

    @num = num

    define_singleton_method("num_#{num}") { @num }
  end
end
lobati
  • 5,978
  • 4
  • 33
  • 53