1

I want to create an instance variable when a certain method is called and also set it to a default value. The instance should then only be able to get the variable but not set it. Here's a piece of code to showcase my problem:

class Foo    

  def self.bar(var)
    attr_reader name.to_sym
    instance_variable_set("@#{var.to_s}",[var.to_s])
  end

end

class Bar < Foo
  bar :foo
end

puts Bar.new.foo

When I run this code I expect to get ["foo"] but instead I get nil. It seems to be a problem of scope but after fiddling around with the code for some time I just don't seem to get it right.

Edit: I just found an exceptionally good article (read till the end) which addresses this problem very concisely. Click Me

3
  • I guess you can make use of the initialize method in ruby. Commented Sep 11, 2013 at 13:07
  • Well but I don't have the information what default value to set anymore during initialization. Or am I missing something? Could you give an example? Commented Sep 11, 2013 at 13:08
  • 1
    As written bar :foo is only called when the class is defined, and the instance variable is set on the class Bar. The attr_reader works, because that is supposed to be called agains the class. You cannot modify instances that don't exist. However, you could probably stash names and defaults somewhere (perhaps in the target class), and supply a method that you could call from initialize to read all the default values. Commented Sep 11, 2013 at 13:14

1 Answer 1

2

This is I think the sort of pattern you are trying to create:

class Foo

  def self.all_properties
    @all_properties ||= []
  end

  def self.property( pr_name )
    attr_reader pr_name.to_sym
    instance_variable_set( "@default_#{pr_name.to_s}", [pr_name.to_s] )
    all_properties << pr_name.to_sym
  end

  def set_default_properties
    self.class.all_properties.each do | pr_name |
      default = self.class.instance_variable_get( "@default_#{pr_name.to_s}" )
      instance_variable_set( "@#{pr_name}",  default.clone )
    end
  end

  def initialize
    set_default_properties
  end

end

class Bar < Foo
  property :foo
end

p Bar.new.foo

It is more complex than you might initially assume. You have to put the list of managed properties, and their default values somewhere. I have done this above with the class instance variables @all_properties and @default_foo. You can get them forwarded into each instance with a little more meta-programming than you have in the question - basically a copy from defaults stashed in the class when it was defined has to be made during instantiation. Why? Because the class definition is not re-run during instantiation, it happens just once beforehand (unless you start modifying the class on-the-fly in the constructor - but that would be unusual!)

Note that the clone in the code above is not quite enough to prevent instances interfering with each other. I haven't implemented a deep clone (left as exercise to anyone reading the code), but the following variation of that line will work for the code as it stands, because the structure of default data is always the same:

      instance_variable_set( "@#{pr_name}",  [ default[0].clone ] )
Sign up to request clarification or add additional context in comments.

3 Comments

The oversight that "you can't modify instances that don't exist" let to my belief to be able to solve this in a more concise way. Thanks a lot.
No problem. I've tidied the code, and added one important detail - using clone. You may or may not need this, but having instances share pointers to attribute values felt wrong to me as a starting point. Technically with an Array containing a String this should even be a deep clone, but that's more complex, so not done here.
I like the addition of clone alot. As soon as you get away from the nitty-gritties of pointer-heavy languages one is prone to overlook stuff like that.

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.