(Большое редактирование, я получил часть пути там …), я взламывал далеко, и я придумал это как способ указать вещи, которые должны быть сделаны, прежде чем атрибуты читаются:
class Class
def attr_reader(*params)
if block_given?
params.each do |sym|
define_method(sym) do
yield
self.instance_variable_get("@#{sym}")
end
end
else
params.each do |sym|
attr sym
end
end
end
end
class Test
attr_reader :normal
attr_reader(:jp,:nope) { changethings if @nope.nil? }
def initialize
@normal = "Normal"
@jp = "JP"
@done = false
end
def changethings
p "doing"
@jp = "Haha!"
@nope = "poop"
end
end
j = Test.new
p j.normal
p j.jp
Но changethings
разве существо не распознано как метод — кто-либо получил какие-либо идеи?
Вам нужно оценить блок в контексте экземпляра. yield
по умолчанию оценит его в родном контексте.
class Class
def attr_reader(*params, &blk)
if block_given?
params.each do |sym|
define_method(sym) do
self.instance_eval(&blk)
self.instance_variable_get("@#{sym}")
end
end
else
params.each do |sym|
attr sym
end
end
end
end
Вот еще один альтернативный подход, на который вы можете взглянуть. Это не так элегантно, как то, что вы пытаетесь сделать с помощью define_method
, но, возможно, на него стоит взглянуть.
Добавьте новый метод lazy_attr_reader
в Class
class Class
def lazy_attr_reader(*vars)
options = vars.last.is_a?(::Hash) ? vars.pop : {}
# get the name of the method that will populate the attribute from options
# default to 'get_things'
init_method = options[:via] || 'get_things'
vars.each do |var|
class_eval("def #{var}; #{init_method} if !defined? @#{var}; @#{var}; end")
end
end
end
Затем используйте его следующим образом:
class Test
lazy_attr_reader :name, :via => "name_loader"
def name_loader
@name = "Bob"
end
end
В действии:
irb(main):145:0> t = Test.new
=> #<Test:0x2d6291c>
irb(main):146:0> t.name
=> "Bob"
IMHO изменение контекста блока довольно нелогично с точки зрения того, кто использовал бы такой attr_reader
на стероидах .
Возможно, вам стоит подумать о простом подходе «указать имя метода с использованием дополнительных аргументов»:
def lazy_attr_reader(*args, params)
args.each do |e|
define_method(e) do
send(params[:init]) if params[:init] && !instance_variable_get("@#{e}")
instance_variable_get("@#{e}")
end
end
end
class Foo
lazy_attr_reader :foo, :bar, :init => :load
def load
@foo = 'foo'
@bar = 'bar'
end
end
f = Foo.new
puts f.bar
#=> bar