我正在阅读一些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,如果没有传入第二个参数,但实际上传入了第二个参数。
答案 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是作弊。我将猜测与旧版本的Ruby的向后兼容性,这些版本没有现在那样丰富的参数语法。
name
,scope
和options
都是位置参数。我们可以定义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改变了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这样就可以了。
name
是必需的**
converts keyword arguments to a hash。scope = nil
是positional argument,所以它是可选的。**options
是positional argument that has a default keyword argument。&extension
是convert the keywords into a hash。当你致电has_many :subscribers, through: :subscriptions, source: :user
时,会发生这种情况。
name
,因此需要:subscribers
。scope
,但它不在位置,其余参数是关键字,所以它使用默认的nil
。options
哈希。extension
是一个块参数,如果不使用它们就没问题。您可以在block argument中了解有关参数处理如何在Ruby中发生的更多信息。