لا تعمل مهمة الإكسسور المضافة ديناميكيًا عند استدعاء Block عبر exita_eval في Ruby
-
25-09-2019 - |
سؤال
لدي فئة أضف إليها أصول السمة ديناميكيًا في وقت التشغيل. تشكل هذه الفئة الجزء 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 إلى "مثيل".