2010-12-09 2 views
2

У меня есть класс, к которому я добавляю динамические атрибуты атрибутов во время выполнения. Этот класс является частью DSL, посредством чего блоки передаются в методы настройки и вызывается с помощью instance_eval. Это позволяет в DSL удалять ссылки на «я» при ссылках на методы класса.Динамически добавленное назначение доступа не работает при вызове блока через экземпляр_eval в Ruby

Однако я обнаружил, что могу ссылаться на атрибуты для извлечения их значений, но не могу их назначить, если только не объяснение self, как показано в следующем примере кода.

class Bar 

    def add_dynamic_attribute_to_class(name) 
    Bar.add_dynamic_attribute(name) 
    end 

    def invoke_block(&block) 
    instance_eval &block 
    end 

    def self.add_dynamic_attribute(name) 
    attr_accessor name 
    end 

end 

b = Bar.new 

b.add_dynamic_attribute_to_class 'dyn_attr' 

b.dyn_attr = 'Hello World!' 

# dyn_attr behaves like a local variable in this case 
b.invoke_block do 
    dyn_attr = 'Goodbye!' 
end 

# unchanged! 
puts "#{b.dyn_attr} but should be 'Goodbye!'" 

# works if explicitly reference self 
b.invoke_block do 
    self.dyn_attr = 'Goodbye!' 
end 

# changed... 
puts "#{b.dyn_attr} = 'Goodbye!" 

# using send works 
b.invoke_block do 
    send 'dyn_attr=', 'Hello Again' 
end 

# changed... 
puts "#{b.dyn_attr} = 'Hello Again!" 

# explain this... local variable or instance method? 
b.invoke_block do 

    puts "Retrieving... '#{dyn_attr}'" 

    # doesn't fail... but no effect 
    dyn_attr = 'Cheers' 

end 

# unchanged 
puts "#{b.dyn_attr} should be 'Cheers'" 

Может кто-нибудь объяснить, почему это не ведет себя так, как ожидалось?

ответ

4

Проблема связана с тем, как Ruby имеет дело с экземплярами и локальными переменными. Происходит то, что вы устанавливаете локальную переменную в вашем экземпляре, а не используете рубиновый аксессуар.

Это может помочь объяснить:

class Foo 
    attr_accessor :bar 

    def input_local 
    bar = "local" 
    [bar, self.bar, @bar, bar()] 
    end 

    def input_instance 
    self.bar = "instance" 
    [bar, self.bar, @bar, bar()] 
    end 

    def input_both 
    bar = "local" 
    self.bar = "instance" 
    [bar, self.bar, @bar, bar()] 
    end 
end 

foo = Foo.new 
foo.input_local #["local", nil, nil, nil] 
foo.input_instance #["instance", "instance", "instance", "instance"] 
foo.input_both #["local", "instance", "instance", "instance"] 

Путь bocks работы является то, что они различают местные и переменные экземпляра, но если локальная переменная не определена, когда это читатель называется, класс по умолчанию к (как в случае с вызовом input_instance в моем примере).

Существует три способа получить нужное поведение.

Использование переменных экземпляра:

 
    class Foo 
     attr_accessor :bar 

     def evaluate(&block) 
     instance_eval &block 
     end 
    end 

    foo = Foo.new 
    foo.evaluate do 
     @bar = "instance" 
    end 
    foo.bar #"instance" 

Используйте переменную сам:

 
    class Foo 
     attr_accessor :bar 

     def evaluate(&block) 
     block.call(self) 
     end 
    end 

    foo = Foo.new 
    foo.evaluate do |c| 
     c.bar = "instance" 
    end 
    foo.bar #"instance" 

Использование функции сеттер:

 
    class Foo 
     attr_reader :bar 
     def set_bar value 
     @bar = value 
     end 

     def evaluate(&block) 
     instance_eval &block 
     end 
    end 

    foo = Foo.new 
    foo.evaluate do 
     set_bar "instance" 
    end 
    foo.bar #"instance" 

Все эти примеры установить foo.bar на "экземпляр" ,

+0

Я не совсем понимаю; вы говорите, что классы ruby ​​различают локальные и переменные экземпляра, но проблема связана с методами доступа. т. е. bar! = @bar! = bar()! = bar =(). – VirtualStaticVoid

+0

BTW: Мне удалось обойти эту проблему, реализовав метод «config», который возвращает self, в классе, чтобы мой DSL читал лучше (т.е. вместо использования self. * Теперь он имеет конфигурацию. *) – VirtualStaticVoid

+3

Я обновил сообщение с более подробной информацией. Проблема в том, что вы неосознанно устанавливаете локальную переменную, а не экземпляр varaible. Чтобы быть более точным, bar == @bar == bar() == self.bar, если локальная переменная с именем bar не определена. В противном случае bar! = @bar == bar() == self.bar, если определена локальная переменная с именем bar. Чтобы быть в безопасности, не определяйте локальные переменные с именем «bar» или используйте только другие методы. –