لا تعمل مهمة الإكسسور المضافة ديناميكيًا عند استدعاء Block عبر exita_eval في Ruby

StackOverflow https://stackoverflow.com/questions/4403973

سؤال

لدي فئة أضف إليها أصول السمة ديناميكيًا في وقت التشغيل. تشكل هذه الفئة الجزء A DSL ، حيث يتم تمرير الكتل إلى أساليب التكوين واستدعاء باستخدام evalue_eval. هذا يجعل من الممكن في DSL إزالة الإشارات إلى "الذات" عند الرجوع إلى طرق الفصل.

ومع ذلك ، فقد اكتشفت أنه يمكنني الإشارة إلى السمات لاسترداد قيمها ، لكنني غير قادر على تعيينها ، ما لم يوضح التعويض عن الذات ، كما توضح عينة الكود التالية.

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'"

هل يمكن لأي شخص أن يشرح لماذا لا يتصرف هذا كما هو متوقع؟

هل كانت مفيدة؟

المحلول

تصل المشكلة مع الطريقة التي يتعامل بها روبي مع مثيل والمتغيرات المحلية. ما يحدث هو أنك تقوم بتعيين متغير محلي في كتلة extal_eval ، بدلاً من استخدام ملحق 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"

استخدم وظائف Setter:

    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 إلى "مثيل".

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top