Как использовать хеш в выражении class_eval в Ruby
Я работал над домашним заданием, когда столкнулся с неприятной проблемой. Назначение - это упражнение в метапрограммировании Ruby, и цель состоит в том, чтобы определить attr_accessor_with_history, который выполняет все те же действия, что и attr_accessor, но также предоставляет историю всех значений, которыми когда-либо был атрибут. Вот предоставленный код из назначения вместе с некоторым кодом, который я добавил в попытке выполнить назначение:
class Class
def attr_accessor_with_history(attr_name)
attr_name = attr_name.to_s
attr_hist_name = attr_name+'_history'
history_hash = {attr_name => []}
#getter
self.class_eval("def #{attr_name} ; @#{attr_name} ; end")
#setter
self.class_eval %Q{
def #{attr_name}=(val)
# add to history
@#{attr_hist_name} = [nil] if @#{attr_hist_name}.nil?
@#{attr_hist_name} << val
history_hash[@#{attr_name}] = @#{attr_hist_name}
# set the value itself
@#{attr_name} = val
end
def history(attr) ; @history_hash[attr.to_s] ; end
}
end
end
class Foo
attr_accessor_with_history :bar
attr_accessor_with_history :crud
end
f = Foo.new # => #<Foo:0x127e678>
f.bar = 3 # => 3
f.bar = :wowzo # => :wowzo
f.bar = 'boo!' # => 'boo!'
puts f.history(:bar) # => [3, :wowzo, 'boo!']
f.crud = 42
f.crud = "Hello World!"
puts f.history(:crud)
Я хотел использовать хеш для хранения разных историй для разных атрибутов, но я не могу получить доступ к этому хешу в выражении class_eval для установщика. Независимо от того, как я пытаюсь его настроить, я всегда либо получаю NoMethodError для метода []=, потому что history_hash каким-то образом становится типом NilClass, либо происходит NameError, потому что он видит history_hash как неопределенную локальную переменную или метод. Как мне использовать хеш в выражениях class_eval?
1 ответ
или NameError происходит, потому что он видит "history_hash" как неопределенную локальную переменную или метод
Я бы сказал, что вы не можете, потому что это локальная переменная, которая недоступна в контексте, который вы хотите. Однако зачем вам это вообще нужно? Я вполне уверен, что это "некоторый код, который я добавил в попытке выполнить назначение", а не исходный код назначения (который, я полагаю, ожидает, что вы сохраните историю @bar
в @bar_history
- или что еще attr_hist_name
все о?)
Я также чувствую себя неловко из-за обрывов строк; как правило, в этом нет необходимости, и Ruby может добиться большего успеха благодаря мощным средствам метапрограммирования. Вот как я это сделаю:
class Class
def attr_accessor_with_history(attr_name)
attr_setter_name = :"#{attr_name}="
attr_getter_name = :"#{attr_name}"
attr_hist_name = :"@#{attr_name}_history"
attr_name = :"@#{attr_name}"
self.class_eval do
define_method(attr_getter_name) do
instance_variable_get(attr_name)
end
define_method(attr_setter_name) do |val|
instance_variable_set(attr_name, val)
history = instance_variable_get(attr_hist_name)
instance_variable_set(attr_hist_name, history = []) unless history
history << val
end
end
end
end
class Object
def history(attr_name)
attr_hist_name = :"@#{attr_name}_history"
instance_variable_get(attr_hist_name)
end
end
Наконец, так как это базовые классы, исправляющие обезьяны, я бы предпочел использовать уточнения, чтобы добавлять их там, где это необходимо, но это, вероятно, излишнее назначение.