尽管 has_one 为 has_one 构建方法

build methods for has_one though has_one

Rails 5.1.2 Ruby2.5.3

我知道有多种方法可以实现这种关系,但是,这个问题更多的是关于为什么以下内容不起作用而不是解决现实世界的问题。

has_many设置

class Subscriber < ApplicationRecord
  has_many :subscriptions, inverse_of: :subscriber
  has_many :promotions, through: :subscriptions, inverse_of: :subscriptions

  accepts_nested_attributes_for :subscriptions
  accepts_nested_attributes_for :promotions
end

class Subscription < ApplicationRecord
  belongs_to :subscriber, inverse_of: :subscriptions
  belongs_to :promotion, inverse_of: :subscriptions
end

class Promotion < ApplicationRecord
  has_many :subscriptions, inverse_of: :promotion
  has_many :subscribers, through: :subscriptions, inverse_of: :subscriptions

  accepts_nested_attributes_for :subscriptions
  accepts_nested_attributes_for :subscribers
end

在上面的 Subscriber 模型中,该模型设置为使用 has_many 以下关系:

s = Subscriber.new
s.subscriptions.build
# OR
s.promotions.build

之后,我希望 Subscriberhas_one 关系

的行为方式相同

has_one设置

class Subscriber < ApplicationRecord
  has_one :subscription, inverse_of: :subscriber
  has_one :promotion, through: :subscription, inverse_of: :subscriptions

  accepts_nested_attributes_for :subscription
  accepts_nested_attributes_for :promotion
end

class Subscription < ApplicationRecord
  belongs_to :subscriber, inverse_of: :subscription
  belongs_to :promotion, inverse_of: :subscriptions
end

class Promotion < ApplicationRecord
  has_many :subscriptions, inverse_of: :promotion
  has_many :subscribers, through: :subscriptions, inverse_of: :subscription

  accepts_nested_attributes_for :subscriptions
  accepts_nested_attributes_for :subscribers
end

但是,尝试使用等效的 has_one 构建方法构建嵌套的 promotion 关联会导致 NoMethodError (undefined method 'build_promotion' for #<Subscriber:0x00007f9042cbd7c8>) 错误

s = Subscriber.new
s.build_promotion

但是,这确实有效:

s = Subscriber.new
s.build_subscription

我觉得人们应该期望以与构建 has_many 相同的方式构建嵌套 has_one 关系是合乎逻辑的。

这是错误还是设计使然?

检查代码,当您调用 has_one 时,它仅在反射为 "constructable"[ 时创建 build_create_create_..! 方法=21=]

https://github.com/rails/rails/blob/b2eb1d1c55a59fee1e6c4cba7030d8ceb524267c/activerecord/lib/active_record/associations/builder/singular_association.rb#L16

define_constructors(mixin, name) if reflection.constructable?

现在,检查 constructable? 方法,它 returns calculate_constructable https://github.com/rails/rails/blob/ed1eda271c7ac82ecb7bd94b6fa1b0093e648a3e/activerecord/lib/active_record/reflection.rb#L452

的结果

对于 HasOne class,如果您使用 :through 选项 https://github.com/rails/rails/blob/ed1eda271c7ac82ecb7bd94b6fa1b0093e648a3e/activerecord/lib/active_record/reflection.rb#L723

,它 returns 为假
def calculate_constructable(macro, options)
  !options[:through]
end

所以,我想说这不是错误,它是设计使然的。虽然我不知道原因,也许感觉合乎逻辑,但我想有些事情要考虑并不是那么简单。