Настройка attr_reader, чтобы сделать ленивое инстанцирование атрибутов

(Большое редактирование, я получил часть пути там …), я взламывал далеко, и я придумал это как способ указать вещи, которые должны быть сделаны, прежде чем атрибуты читаются:

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 разве существо не распознано как метод — кто-либо получил какие-либо идеи?

5
задан mikej 14 July 2010 в 16:22
поделиться

3 ответа

Вам нужно оценить блок в контексте экземпляра. 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
4
ответ дан 14 December 2019 в 13:25
поделиться

Вот еще один альтернативный подход, на который вы можете взглянуть. Это не так элегантно, как то, что вы пытаетесь сделать с помощью 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"
1
ответ дан 14 December 2019 в 13:25
поделиться

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
1
ответ дан 14 December 2019 в 13:25
поделиться
Другие вопросы по тегам:

Похожие вопросы: