Как предлагается в комментариях, OpenStruct
может сделать большую часть тяжелой работы за вас. Стоит отметить, что если вы не freeze
это сделаете, то после его инициализации вы сможете добавить к нему больше атрибутов в течение всего времени его жизни, например:
struct = OpenStruct.new(name: "Joe", age: 20)
struct.email = "joe@example.com" # this works
p struct.email # => "joe@example.com"
(так что по сути это работает как Hash
с объектоподобным интерфейсом)
Такое поведение может быть нежелательным. И если вы сделаете freeze
структуру, она не позволит больше определения атрибутов, но тогда вы также потеряете возможность переопределять существующие значения (что, я думаю, вы хотите сделать в тех случаях, когда кто-то устанавливает immutable
в false
).
Чтобы флаг immutable
работал, как я понимаю, вы ожидаете этого, я бы создал класс, который использует OpenStruct
под капотом, например, так:
class BasePORO
def initialize(obj, immutable = true)
@immutable = immutable
@data = OpenStruct.new(obj.attributes)
obj.attributes.keys.each do |attr|
self.class.define_method(attr.to_sym) do
@data.send(attr.to_sym)
end
self.class.define_method("#{attr}=".to_sym) do |new_value|
if @immutable
raise StandardError.new("#{self} is immutable")
else
@data.send("#{attr}=".to_sym, new_value)
end
end
end
end
end
class UserPORO < BasePORO
end
Кстати, если вы настаивали на том, чтобы иметь решение, подобное тому, которое показано в вопросе, то вы могли бы достичь этого с помощью чего-то такого:
class BasePORO
def initialize(obj, immutable = true)
@immutable = immutable
attributes.each do |attr|
instance_variable_set("@#{attr}".to_sym, obj.attributes[attr.to_s])
self.class.define_method(attr.to_sym) do
instance_variable_get("@#{attr}".to_sym)
end
self.class.define_method("#{attr}=".to_sym) do |new_value|
if @immutable
raise StandardError.new("#{self} is immutable")
else
instance_variable_set("@#{attr}".to_sym, new_value)
end
end
end
end
private
# default attributes
def attributes
[:id]
end
end
class UserPORO < BasePORO
private
# overriding default attributes from BasePORO
def attributes
User.new.attributes.keys.map(&:to_sym).freeze
end
end