哈希作为参数和默认参数值

时间:2018-05-29 00:34:10

标签: ruby-on-rails ruby

我正在阅读一些Rails内部,特别是has_many方法。它显示了以下示例:

  # Option examples:
  #   has_many :comments, -> { order "posted_on" }
  #   has_many :comments, -> { includes :author }
  #   has_many :people, -> { where(deleted: false).order("name") }, class_name: "Person"
  #   has_many :tracks, -> { order "position" }, dependent: :destroy
  #   has_many :comments, dependent: :nullify
  #   has_many :tags, as: :taggable
  #   has_many :reports, -> { readonly }
  #   has_many :subscribers, through: :subscriptions, source: :user
  def has_many(name, scope = nil, options = {}, &extension)
    reflection = Builder::HasMany.build(self, name, scope, options, &extension)
    Reflection.add_reflection self, name, reflection
  end

特别注意这一点:

has_many :subscribers, through: :subscriptions, source: :user

第二个参数是哈希。我注意到它被分配给选项局部变量。但是为什么不将它分配给范围变量,因为范围变量是参数列表中的第二个参数?为什么在赋值期间跳过范围变量?我没有设置默认值nil,如果没有传入第二个参数,但实际上传入了第二个参数。

2 个答案:

答案 0 :(得分:1)

哈希已分配给scope参数,因为它已作为第二个参数传入,因此options将获得其默认值{}。但是,如果您将散列作为第二个值传递,ActiveRecord会交换参数。这是在Builder::Association中处理的(因为Builder::HasMany继承自Builder:: CollectionAssociation,而Builder::Association继承自if scope.is_a?(Hash) options = scope scope = nil end )。 build类方法调用create_builder,然后将参数传递到initialize检查

ActiveRecord

所以没有内置的红宝石幕后魔术,Hash只是选择检查你是否通过nil作为第二个参数并为你“修复”它,而不是强迫你通过initialize

CollectionAssociation会覆盖Association#initialize方法,但您可以看到它们在方法开始时调用super,确保首先调用ThreadLocalMap并发生此param交换。

答案 1 :(得分:1)

首先,has_many的定义在Rails 4和Rails 5之间发生了变化。让我们从Rails 4开始。

Rails 4

你说这是多么可疑。 Rails是作弊。我将猜测与旧版本的Ruby的向后兼容性,这些版本没有现在那样丰富的参数语法。

namescopeoptions都是位置参数。我们可以定义has_many来查看正在发生的事情。

def has_many(name, scope = nil, options = {}, &extension)
  puts "name: #{name}"
  puts "scope: #{scope}"
  puts "options: #{options}"
  puts "extension: #{extension}"
end

如果我们运行has_many :subscribers, through: :subscriptions, source: :user ...

name: subscribers
scope: {:through=>:subscriptions, :source=>:user}
options: {}
extension: 

那不对。 Let's look at its source ...

def has_many(name, scope = nil, options = {}, &extension)
  reflection = Builder::HasMany.build(self, name, scope, options, &extension)
  Reflection.add_reflection self, name, reflection
end

参数传递给调用create_builder model, name, scope, options, &block的{​​{3}}。这会调用new(model, name, scope, options, &block)来创建一个新实例。 Builder::HasMany.build我们发现了这个......

def initialize(model, name, scope, options)
  # TODO: Move this to create_builder as soon we drop support to activerecord-deprecated_finders.
  if scope.is_a?(Hash)
    options = scope
    scope   = nil
  end
  ...

所以这只是作弊。如果scope是哈希,则会将其切换为options。这是一个非常讨厌的黑客,看看错误的参数在修复之前经过多层方法调用。

Rails 5

Rails 5改变了has_many的签名。

def has_many(name, scope = nil, **options, &extension)
  puts "name: #{name}"
  puts "scope: #{scope}"
  puts "options: #{options}"
  puts "extension: #{extension}"
end

has_many :subscribers, through: :subscriptions, source: :user

现在它按预期工作。

name: subscribers
scope: 
options: {:through=>:subscriptions, :source=>:user}
extension: 

In its initializer这样就可以了。

当你致电has_many :subscribers, through: :subscriptions, source: :user时,会发生这种情况。

  1. Ruby需要第一个位置name,因此需要:subscribers
  2. Ruby需要第二个位置scope,但它不在位置,其余参数是关键字,所以它使用默认的nil
  3. Ruby填充关键字并将它们放入options哈希。
  4. extension是一个块参数,如果不使用它们就没问题。
  5. 您可以在block argument中了解有关参数处理如何在Ruby中发生的更多信息。