Last Updated: February 25, 2016
·
7.191K
· zinkkrysty

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!