ole
ole

Reputation: 5233

Create dynamic attribute in the Struct instance

Is it possible to create attribute dynamicaly in the Struct instance?

class Person < Struct.new(:name)
end

p = Person.new("Bilbo")
p[:surname] = "Jenkins" # does not work

Upvotes: 7

Views: 1788

Answers (3)

Cary Swoveland
Cary Swoveland

Reputation: 110755

You can subclass Struct, in which case you will be creating two classes, but it is more common to just create one:

Person = Struct.new(:name)

Person.instance_methods(false)
  #=> [:name, :name=]

p = Person.new("Bilbo")
  #=> #<struct Person name="Bilbo">

Does `p` have an instance variable `@name` whose value is `"Bilbo"`?

p.instance_variables
  #=> []

No, it does not. Rather, it has "members":

p.members
  #=> [:name]

Can name be treated as an instance variable with the accessors provided by Struct?

p.name
  #=> "Bilbo"
p.name = "cat"
p.name
  #=> "cat"

Yes! That's because the members of a Struct instance are stored in array that you are not intended to access directly, only through the accessors.

Can we add Struct members dynamically? I don't know the answer to that, but methods are not provide to do that easily. Instead, just add instance variables and, optionally, accessors.

We can add an instance variable and set its value with:

p.instance_variable_set('@surname', 'Jenkins')
  #=> "Jenkins"

p.instance_variables
  #=> [:@surname]

and retrieve its value with:

p.instance_variable_get('@surname')
  #=> "Jenkins"

If you wish to create accessors for that variable, this is one way:

p.class.instance_eval do
  attr_accessor :surname
end

p.surname
  #=> "Jenkins"
p.surname = 'cat'
  #=> "cat"
p.surname
  #=> "cat"

p.class.instance_methods(false)
  #=> [:name, :name=, :surname, :surname=]

Upvotes: 1

joelparkerhenderson
joelparkerhenderson

Reputation: 35493

You can define new methods on your Person class by doing this:

Person.send(:define_method, :surname){@surname}
Person.send(:define_method, :surname=){|x|@surname=x}

I prefer define_method instead of instance_eval because I try to omit eval when possible.

Upvotes: 1

August
August

Reputation: 12568

You could use an OpenStruct:

require 'ostruct'

p = OpenStruct.new(name: "Bilbo")
p[:surname] = "Jenkins"

p.surname # => "Jenkins"

Upvotes: 3

Related Questions