How to make one of "has_many :through" associations primary
So you have an ActivreRecord model with many to many type of association with another model:
class Place < ActiveRecord::Base
has_many :place_category_relations
has_many :categories, through: :place_category_relations
end
But now that you have many categories, you probably want to make one primary for your object.
You basically want a
belongs_to :primary_category, class_name: 'Category'
but for that you have to add another key to the Place model. A migration, reinforcing synchronizations between the PlaceCategoryRelation
table and Place > primary_category_id
column... what a headache!
What about a
has_one :primary_category
but... there's something missing... a belongs_to
somewhere in the associated model.
So you need an intermediary association that acts as a :through =>
for the has_one
:
has_one :primary_category_relation, -> { where(is_primary: true) }, class_name: 'PlaceCategoryRelation'
There you go, the easiest way in Rails to do that is to add a boolean flag on the PlaceCategoryRelation
model that only the primary category for a place gets set to true.
You defined 2 associations in your model, specifying the class_name
for one and the source
for the other, but this is pretty powerful stuff, without creating redundancy:
class Place < ActiveRecord::Base
has_many :place_category_relations
has_many :categories, through: :place_category_relations
has_one :primary_category_relation, -> { where(is_primary: true) }, class_name: 'PlaceCategoryRelation'
has_one :primary_category, through: :primary_category_relation, source: :category
end
class PlaceCategoryRelation < ActiveRecord::Base
belongs_to :place
belongs_to :category
# Make sure only one category is primary per place
validates :is_primary, uniqueness: true, scope: :place_id
end
This solution I found after being close to write a StackOverflow question about which is the best of 2 proposed solutions, which neither were elegant as the one I wrote here. Before I pressed the "Ask Question" button I noticed a similar question, but the model was with has_many
instead of has_many :through =>
, and then I searched on Google for "rails has_one conditions has many through" which yielded the closest idea to what I described here.
Feel free to make suggestions to improve this, I am eager to learn more!