Reputation: 4306
Rails 5.1.2 Ruby 2.5.3
I understand there are multiple ways to impliment this relationship, however, this question is more about why the following doesn't work rather than solving a real world problem.
has_many
setup
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
In the above Subscriber
model which is setup to use has_many
relationships following would work:
s = Subscriber.new
s.subscriptions.build
# OR
s.promotions.build
Following that, I would expect Subscriber
to behave the same way with has_one
relationships
has_one
setup
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
However, attempting to build the nested promotion
association with the equivalent has_one
build methods results in a NoMethodError (undefined method 'build_promotion' for #<Subscriber:0x00007f9042cbd7c8>)
error
s = Subscriber.new
s.build_promotion
However, this does work:
s = Subscriber.new
s.build_subscription
I feel it's logical that one should expect to build nested has_one
relationships in the same way one builds has_many
.
Is this a bug or by design?
Upvotes: 1
Views: 347
Reputation: 15838
Checking the code, when you call has_one, it creates the build_
, create_
and create_..!
methods ONLY if the reflection is "constructable"
define_constructors(mixin, name) if reflection.constructable?
Now, checking the constructable?
method, it returns the result of calculate_constructable
https://github.com/rails/rails/blob/ed1eda271c7ac82ecb7bd94b6fa1b0093e648a3e/activerecord/lib/active_record/reflection.rb#L452
And for the HasOne class, it returns false if you use the :through
option https://github.com/rails/rails/blob/ed1eda271c7ac82ecb7bd94b6fa1b0093e648a3e/activerecord/lib/active_record/reflection.rb#L723
def calculate_constructable(macro, options)
!options[:through]
end
So, I'd say it's not a bug, it's made like that by design. I don't know the reason though, maybe it feels logical but I guess there's some things to consider that are not that simple.
Upvotes: 3