簡體   English   中英

Ruby:使用 class_eval 定義常量只能通過 const_get 而不是直接通過::lookup 找到

[英]Ruby: defining constants using class_eval can be found only via const_get but not via directly :: lookup

給定用戶 class:

class User
end

我想使用.class_eval定義一個新常量。 所以:

User.class_eval { AVOCADO = 'fruit' }
  1. 如果我嘗試通過User::AVOCADO訪問它,我會得到uninitialized constant User::AVOCADO ,但User.const_get(:AVOCADO)有效。 為什么?

  2. 如果我在included的方法中的 Rails Concern 中定義了一個常量,並將關注點包含在User class 中,我可以通過常規::查找來訪問它。 例如:

module FruitConcern
  extend ActiveSupport::Concern

  included do
     AVOCADO = 'fruit'
  end

end

class User
  include FruitConcern
end

User::AVOCADO
=> 'fruit'

但是,查找 ActiveSupport::Concern 的源代碼included的只是將該塊存儲在實例變量 ( @_included_block ) 中,然后它對append_features的覆蓋將調用base.class_eval(&@_included_block)

所以,如果它只是用同一個塊調用User.class_eval ,為什么User::AVOCADO在該included塊內定義常量時起作用,而不是當我直接調用User.class_eval { AVOCADO = 'fruit' }時?

  1. 奇怪的是(請參閱此博客文章),在執行User.class_eval { AVOCADO = 'teste' }時,似乎 Ruby 也將常量泄漏到頂層。 所以:
User.const_get(:AVOCADO)
=> "fruit"
AVOCADO
=> 'fruit'

我知道塊具有平面范圍,但沒有事先定義常量,我希望class_evalselfdefault definee者都更改為接收者。 這里發生了什么? 這個常量是如何在頂層和用戶 scope 中定義的?

Ruby 中有三個隱式上下文

  • self ,“當前對象”:隱式接收器和 scope 實例變量。
  • 默認的definee :最終使用def bar定義的方法沒有明確的目標(即不是def foo.bar )。
  • 常數 scope。

前兩個在鏈接的文章中得到了很好的解釋。 鏈接的文章中承諾了第三篇的一篇文章,但它從未出現過。 很多人寫過很多關於常量的文字,但不幸的是,沒有人寫過權威的規范,類似於 yugui 為默認的 definee 所做的。

所以,不幸的是,我也只能推測,因此無益地增加了關於這個話題的大量非權威性文字。

塊在 Ruby 中的詞法范圍,它們捕獲它們的詞法環境。 一般來說,這意味着塊內部的引用與塊外部的含義完全相同,就好像塊不存在一樣。 (當然,塊局部變量除外。)

所以,

foo { AVOCADO = 'fruit' }

意思是一樣的

AVOCADO = 'fruit'

當然,除非foo以某種方式改變了評估塊的上下文。 我們知道instance_eval改變了self 我們知道class_eval改變了self默認的 definee 然而,重要的是: class_eval不會改變隱式常量 scope。

因此,分配AVOCADO = 'fruit' inside

User.class_eval { AVOCADO = 'fruit' }

如果它在塊之外,則具有完全相同的含義

AVOCADO = 'fruit'

換句話說,它與頂層的常量賦值具有相同的含義,正如我們所知,在頂層:

  • self是未命名的 singleton object,俗稱main
  • 默認定義Object ,增加了方法默認為private的扭曲,並且
  • 隱式常量 scope 也是Object

因此, AVOCADOObject中定義。

這意味着以下工作:

class User
  AVOCADO
end
#=> 'fruit'

因為常量查找首先在詞匯上“向外”(在這種情況下失敗),然后在 inheritance 層次結構中“向上”,它成功,因為User隱式是Object的子類。

User.const_get(:AVOCADO)
#=> 'fruit'

也有效,因為這實際上與之前的相同,只是通過反射動態完成:它在 class User中啟動常量查找算法,就像你寫的一樣

class User
  AVOCADO
end

但是,這不起作用:

User::AVOCADO

老實說,這令人困惑,因為AVOCADO應該繼承自Object

當您定義常量時,您不會將常量分配給self 您正在當前模塊嵌套中定義一個常量。

當您顯式打開 class 或模塊時,您還設置了模塊嵌套:

module Foo
  BAR = 1
  puts Module.nesting.inspect # [Foo]
end

當您執行User.class_eval { AVOCADO = 'fruit' }時,模塊嵌套是“Main”,也就是全局 object:

User.class_eval do
  ADVOCADO = 'Fruit'
  puts Module.nesting.inspect # []
end

塊實際上並不改變模塊嵌套。 另一方面, const_set在另一個模塊嵌套中定義了一個常量。

Ruby 實際上也沒有兩次定義常量。 相反,當您使用 const_get 或引用常量而不明確使用 scope 分辨率運算符 Ruby 將查看模塊嵌套並將樹向上遍歷到全局 scope:

class A
end
B = 'eureka'
A.const_get(:B) # 'eureka'

這就是您可以引用頂級常量而不用::開頭的方式。 但是,當您使用User::ADVOCADO時,您明確地引用了User內部的常量。

當談到這個例子時,你誤解了正在發生的事情。 根本不是關於class_eval 您正在定義常量FruitConcern::AVOCADO

然后,當您將FruitConcern包含到User中時,您將FruitConcern添加到包含在常量查找中的祖先鏈中:

module FruitConcern
  ADVOCADO = 'fruit'
end

class User
  include FruitConcern
end

User::ADVOCADO  # fruit

看:

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM