0

Look at this piece of code:

class Person
  attr_accessor :name, :age

  def initialize(data)
    data.each { |k, v| self.send("#{k}=", v) }
  end
end

data = {
  name: 'Luke Skywalker',
  age: 19
}

person = Person.new(data)

person.inspect # => #<Person @name="Luke Skywalker", @age=19>

This works fine. It does what it's supposed to do. The thing is, I don't want the attributes to be mutable, in other words, I want to use attr_reader. I know that if I use attr_reader Ruby won't create the writer methods for me (def attr=(val) ...) and therefore I won't be able to use "#{k}=" in send. I really DON'T want to do this:

class Person
  attr_reader :name, :age

  def initialize(name:, age:)
    @name = name
    @age = age
  end
end

Do you guys know any workaround to achieve what I want?

Thanks for all the help!

Noctilucente
  • 326
  • 1
  • 10
  • Not that it's necessarily better, but I want to point out there is a different way to do it, other than with instance_variable_set .... you can make an attr_reader which is private – max pleaner Jan 17 '19 at 06:52
  • @maxpleaner Could you elaborate? – Noctilucente Jan 17 '19 at 07:00
  • `attr_reader :name` + `private :name`. But that doesn't make it immutable. If you want them to be immutable, you can do `@name = name.freeze` and/or add a `freeze` as the last line in the initialize method, then it's not possible to change any instance variables. – Kimmo Lehto Jan 17 '19 at 07:12
  • sorry, i meant a private attr_writer ... but @KimmoLehto is right, i forgot about the `private` method ... you can do `attr_accessor :name; private :name=` – max pleaner Jan 17 '19 at 08:34

1 Answers1

1

You can use instance_variable_set, you don't have to rely on attr writer:

def initialize(data)
  data.each { |key, value| instance_variable_set("@#{key}", value) }
end
Marek Lipka
  • 50,622
  • 7
  • 87
  • 91