Michael Papile 的回答基本上是正确的。但是,我想详细说明一下,因为您可能希望了解一些技术上的细微差别。我查看了 railscasts 和 factory_girl 的代码,我相信还有一些额外的部分可以解释 :admin => true > arg 最终创建了用户工厂的 admin 属性。属性添加实际上并没有通过 Factory 的 initialize() 方法发生,尽管正如 Michael 指出的那样,该方法确实被调用以服务于构建新的用户工厂对象。
我将在此说明中包含我采取的所有步骤,以防您想了解如何着手调查您可能遇到的类似问题。
由于您的原始帖子日期为 2 月 17 日,因此我查看了与该日期非常匹配的 railscasts 版本。
我查看了它的 Gemfile:
https://github.com/ryanb/railscasts/blob/d124319f4ca2a2367c1fa705f5c8229cce70921d/Gemfile
第 18 行:
gem "factory_girl_rails"
然后我检查了 factory_girl_rails 的提交,它与 2 月 17 日的日期最接近。
https://github.com/thoughtbot/factory_girl_rails/blob/544868740c3e26d8a5e8337940f9de4990b1cd0b/factory_girl_rails.gemspec
第 16 行:
s.add_runtime_dependency('factory_girl', '~> 2.0.0.beta')
factory_girl 2.0.0.beta 版实际上并不容易找到。没有具有该名称的 github 标记,因此我只检查了提交日期方面最接近的标记。
https://thoughtbot/factory_girl/blob/9fb8a3b40f24f0c8477776133a2f9cd654ca1c8c/lib/factory_girl/syntax/vintage.rb
第 122-128 行:
# Shortcut for Factory.default_strategy.
#
# Example:
# Factory(:user, :name => 'Joe')
def Factory(name, attrs = {})
Factory.default_strategy(name, attrs)
end
因此,railscasts 中的 Factory 调用实际上是调用了一个便捷方法,该方法调用了位于同一文件中的“默认策略”:
第 39-52 行:
# Executes the default strategy for the given factory. This is usually create,
# but it can be overridden for each factory.
#
# Arguments:
# * name: +Symbol+ or +String+
# The name of the factory that should be used.
# * overrides: +Hash+
# Attributes to overwrite for this instance.
#
# Returns: +Object+
# The result of the default strategy.
def self.default_strategy(name, overrides = {})
self.send(FactoryGirl.find(name).default_strategy, name, overrides)
end
请注意,调用 FactoryGirl.find 是为了获取要调用 default_strategy 的对象。 find 方法解析到这里:
https://thoughtbot/factory_girl/blob/9fb8a3b40f24f0c8477776133a2f9cd654ca1c8c/lib/factory_girl/registry.rb
第 12-14 行:
def find(name)
@items[name.to_sym] or raise ArgumentError.new("Not registered: #{name.to_s}")
end
这里的名称是 :user。因此,我们希望在 user 工厂上调用 default_strategy。正如 Michael Papile 所指出的,这个用户工厂是由您最初认为是 Factory 的类定义的 railscasts 代码定义和注册的。
https://ryanb/railscasts/blob/d124319f4ca2a2367c1fa705f5c8229cce70921d/spec/factories.rb
第 23-25 行:
Factory.define :user do |f|
f.sequence(:github_username) { |n| "foo#{n}" }
end
所以在调查用户工厂的默认策略是什么时,我在 railscasts 项目中环顾四周,发现:
https://ryanb/railscasts/blob/d124319f4ca2a2367c1fa705f5c8229cce70921d/spec/factories.rb
第 43-45 行:
def default_strategy #:nodoc:
@options[:default_strategy] || :create
end
:create 是默认策略。我们回到 factory_girl 找到 create 的 def。
https://thoughtbot/factory_girl/blob/9fb8a3b40f24f0c8477776133a2f9cd654ca1c8c/lib/factory_girl/syntax/methods.rb
第 37-55 行:
# Generates, saves, and returns an instance from this factory. Attributes can
# be individually overridden by passing in a Hash of attribute => value
# pairs.
#
# Instances are saved using the +save!+ method, so ActiveRecord models will
# raise ActiveRecord::RecordInvalid exceptions for invalid attribute sets.
#
# Arguments:
# * name: +Symbol+ or +String+
# The name of the factory that should be used.
# * overrides: +Hash+
# Attributes to overwrite for this instance.
#
# Returns: +Object+
# A saved instance of the class this factory generates, with generated
# attributes assigned.
def create(name, overrides = {})
FactoryGirl.find(name).run(Proxy::Create, overrides)
end
create 策略调用此处定义的 run 方法:
https://thoughtbot/factory_girl/blob/9fb8a3b40f24f0c8477776133a2f9cd654ca1c8c/lib/factory_girl/factory.rb
第 86-97 行:
def run(proxy_class, overrides) #:nodoc:
proxy = proxy_class.new(build_class)
overrides = symbolize_keys(overrides)
overrides.each {|attr, val| proxy.set(attr, val) }
passed_keys = overrides.keys.collect {|k| FactoryGirl.aliases_for(k) }.flatten
@attributes.each do |attribute|
unless passed_keys.include?(attribute.name)
attribute.add_to(proxy)
end
end
proxy.result(@to_create_block)
end
这段代码在做什么的翻译/总结:
首先,通过在 proxy_class 上调用 new 来构建 proxy 对象,在本例中为 Proxy::Create ,这里定义:
https://thoughtbot/factory_girl/blob/9fb8a3b40f24f0c8477776133a2f9cd654ca1c8c/lib/factory_girl/proxy/create.rb
基本上,您只需要知道 proxy 正在构建一个新的用户工厂对象并在工厂对象创建之前和之后调用回调。
回到 run 方法,我们看到最初传递给 Factory 便捷方法(在本例中为 :admin => true) 现在被标记为覆盖。 proxy 对象然后调用 set 方法,将每个属性-名称/值对作为 args 传递。
set() 方法是 Build 类的一部分,Proxy 的父类。
https://thoughtbot/factory_girl/blob/9fb8a3b40f24f0c8477776133a2f9cd654ca1c8c/lib/factory_girl/proxy/build.rb
第 12-14 行:
def set(attribute, value)
@instance.send(:"#{attribute}=", value)
end
这里的@instance 指的是代理对象,用户工厂对象。
这就是 :admin => true 如何设置为 railscasts 规范代码创建的用户工厂的属性。
如果需要,您可以在 Google 上搜索“编程设计模式”并阅读以下模式:工厂、代理、构建器、策略。
迈克尔·帕皮尔写道:
http://www.ruby-doc.org/core/classes/Kernel.html 注意数组和
字符串等具有类似的结构。我想弄清楚他们是如何
现在就这样做了。
如果您仍然好奇,您在内核文档中看到的 Array 和 String 实际上只是用于创建这些类型的新对象的工厂方法。这就是不需要 new 方法调用的原因。它们本身实际上并不是构造函数调用,但它们确实分配和初始化 Array 和 String 对象,因此在幕后它们相当于在这些类型的对象上调用 initialize()。 (当然,在 C 中,不是在 Ruby 中)