5

在 railscasts 项目中,您可以看到以下代码:

before(:each) do
  login_as Factory(:user, :admin => true)
end

该函数的对应定义为:

Factory.define :user do |f|
  f.sequence(:github_username) { |n| "foo#{n}" }
end

我不明白 admin 参数是如何传递给函数的,而在函数中没有关于 admin 参数的消息。谢谢

4

3 回答 3

9

Factory.define不是函数定义,它是一个接受符号或字符串(在本例中为用户)和定义您正在制作的工厂的块的方法。 Factory(:user, :admin => true)创建一个User具有管理属性的对象。它不是调用第二个片段中的代码,而是调用Factory()初始化工厂并选择一个(在本例中为第二个片段中定义的那个)。然后它也将哈希形式的选项传递给工厂。

Factory选择:user非常通用的工厂。该选项:admin=>true只是告诉Factory将 User 上的 admin 实例变量设置为 true。

This is actually what it is calling in factory.rb in factory girl

def initialize(name, options = {}) #:nodoc:
  assert_valid_options(options)
  @name = factory_name_for(name)
  @options = options
  @attributes = []
end

所以 Factory(name,options) 在这段代码中等价于 Factory.new(name,options) 。

http://www.ruby-doc.org/core/classes/Kernel.html 注意数组和字符串等有类似的结构。我正试图弄清楚他们现在是如何做到的。

即使对于体面的 Ruby 程序员来说,这一切都令人困惑。我强烈推荐《Metaprogramming Ruby》这本书,这可能是我读过的最好的 ruby​​ 书籍,它告诉你很多关于这个神奇的东西。

于 2011-02-17T06:51:16.307 回答
3

Michael Papile 的回答基本上是正确的。但是,我想详细说明一下,因为您可能希望了解一些技术上的细微差别。我查看了railscastsfactory_girl的代码,我相信还有一些额外的部分可以解释:admin => true arg 如何最终创建用户工厂的admin属性。属性添加实际上并没有通过Factory的 initialize() 方法发生,尽管正如 Michael 指出的那样,该方法确实被调用以服务于构建新的用户工厂对象。

我将在此解释中包含我采取的所有步骤,以防您想了解如何调查您可能遇到的类似问题。

由于您的原始帖子日期为 2 月 17 日,因此我查看了与该日期密切匹配的railscast版本。

我查看了它的 Gemfile:

https://github.com/ryanb/railscasts/blob/d124319f4ca2a2367c1fa705f5c8229cce70921d/Gemfile

第 18 行:

gem "factory_girl_rails"

然后我检查了与 2 月 17 日日期最接近的factory_girl_rails的提交。

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。因此,我们希望在用户工厂上调用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的定义。

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::Create,它在此处定义:

https://thoughtbot/factory_girl/blob/9fb8a3b40f24f0c8477776133a2f9cd654ca1c8c/lib/factory_girl/proxy/create.rb

基本上你需要知道的是代理正在构建一个新的用户工厂对象并在工厂对象创建之前和之后调用回调。

回到run方法,我们看到最初传递给Factory便利方法的所有额外参数(在本例中为:admin => true)现在都被标记为overrides。然后代理对象调用一个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 规范代码创建的用户工厂的属性。

如果您愿意,您可以搜索“编程设计模式”并阅读以下模式:工厂、代理、构建器、策略。

迈克尔·帕皮尔写道:

http://www.ruby-doc.org/core/classes/Kernel.html注意数组和字符串等有类似的结构。我正试图弄清楚他们现在是如何做到的。

如果您仍然好奇,您在内核文档中看到的 Array 和 String 实际上只是用于创建这些类型的新对象的工厂方法。这就是不需要方法调用的原因。它们本身实际上并不是构造函数调用,但它们确实分配和初始化 Array 和 String 对象,因此在幕后它们相当于在这些类型的对象上调用 initialize()。(当然,在 C 中,不是 Ruby)

于 2012-01-20T18:18:38.457 回答
-1

我不认为第二个片段函数的定义。函数定义有defend。我认为第二个片段看起来像一个函数或方法被调用的参数:user和一个带参数的块f

当然,对于元编程,您永远无法真正确定到底发生了什么。

于 2011-02-17T04:37:51.520 回答