19

in Ruby can I automatically populate instance variables somehow in the initialize method?

For example if I had:

class Weekend
  attr_accessor :start_date, :end_date, :title, :description, :location

  def initialize(params)
    # SOMETHING HERE TO AUTO POPULATE INSTANCE VARIABLES WITH APPROPRIATE PARAMS
  end

end
Greg
  • 34,042
  • 79
  • 253
  • 454

5 Answers5

17

You can use instance_variable_set like this:

params.each do |key, value|
  self.instance_variable_set("@#{key}".to_sym, value)
end
Mischa
  • 42,876
  • 8
  • 99
  • 111
7

To keep things simple:

class Weekend
  attr_accessor :start_date, :end_date, :title, :description, :location

  def initialize(params)
    @start_date = params[:start_date] # I don't really know the structure of params but you have the idea
    @end_date   = params[:end_date]
  end
end

You could do something smarter with a twist of metaprogramming but is this really necessary?

Mischa
  • 42,876
  • 8
  • 99
  • 111
apneadiving
  • 114,565
  • 26
  • 219
  • 213
  • 3
    I understand the desire to avoid unnecessary metaprog, but this isn't really the succinct code the OP was asking for. You have to manually specify each field name. – Kelvin Jun 01 '12 at 18:49
5

Ruby can be scary simple sometimes. No looping in sight!

class Weekend < Struct.new(:start_date, :end_date, :title, :description, :location)
  # params: Hash with symbols as keys
  def initialize(params)
    # arg splatting to the rescue
    super( * params.values_at( * self.class.members ) )
  end
end

Note that you don't even need to use inheritance - a new Struct can be customized during creation:

Weekend = Struct.new(:start_date, :end_date, :title, :description, :location) do
  def initialize(params)
    # same as above
  end
end

Test:

weekend = Weekend.new(
  :start_date => 'start_date value',
  :end_date => 'end_date value',
  :title => 'title value',
  :description => 'description value',
  :location => 'location value'
)

p [:start_date , weekend.start_date  ]
p [:end_date   , weekend.end_date    ]
p [:title      , weekend.title       ]
p [:description, weekend.description ]
p [:location   , weekend.location    ]

Note that this doesn't actually set instance variables. You class will have opaque getters and setters. If you'd rather not expose them, you can wrap another class around it. Here's an example:

# this gives you more control over readers/writers
require 'forwardable'
class Weekend
  MyStruct = ::Struct.new(:start_date, :end_date, :title, :description, :location)
  extend Forwardable
  # only set up readers
  def_delegators :@struct, *MyStruct.members

  # params: Hash with symbols as keys
  def initialize(params)
    # arg splatting to the rescue
    @struct = MyStruct.new( * params.values_at( * MyStruct.members ) )
  end
end
Kelvin
  • 20,119
  • 3
  • 60
  • 68
  • @BKSpurgeon I don't know how common it is. If you intend to use it on a team project, I suggest asking the team whether they're comfortable with it. – Kelvin Feb 08 '18 at 17:04
2

I think you could simply put:

Weekend < Struct.new(:start_date, :end_date, :title, :description, :location)

And then add anything else to the weekend class with:

class Weekend
#whatever you need to add here
end
mjd2
  • 321
  • 1
  • 2
  • 13
  • 1
    I came back to answer this question I saw two years ago just to find someone who gave the answer I had in mind one year ago. (but I prefer `class Weekend< Struct.new...` – nurettin Nov 13 '14 at 14:47
2

I suggest

class Weekend
  @@available_attributes = [:start_date, :end_date, :title, :description, :location]
  attr_accessor *@@available_attributes

  def initialize(params)
    params.each do |key,value|
      self.send(:"#{key}=",value) if @@available_attributes.include?(key.to_sym)
    end
  end
end
kalifs
  • 177
  • 3