Reputation: 43153
I'm working on a three model relationship with one aspect that I'm not sure how to approach. Here's the basic relationship:
class Taxonomy
has_many :terms
# attribute: `inclusive`, default => false
end
class Term
belongs_to :taxonomy
has_and_belongs_to_many :photos
end
class Photo
has_and_belongs_to_many :terms
end
This is pretty straightforward stuff except for one thing:
A Taxonomy can be either 'Inclusive' or 'Exclusive'. Exclusive means the terms are mutually exclusive, Inclusive means they're not.
Now, I can handle this on the client-side without a problem, but I am not quite sure how to set up a validation on Photos (or somewhere else) that says basically: "validate that no more than one term from an exclusive taxonomy is associated with this record."
Bonus Question
Also, while it's not essential, I've been thinking it would be nice to setup a join of some kind between Taxonomies and Photos, ie., I'd like an easy way to query for all the photos that have been classified with terms from a given taxonomy.
I think I can do this with something like Photo.where('term_id in ?', Taxonomy.find(1).terms.map(&:id))
but, obviously that's not very elegant. I'm pretty sure has_many :through can't work since terms are habtm to photos, so if anyone knows a more elegant way to setup that relationship I'd love to hear it.
Thanks!
Update, to further explain the taxonomy idea:
If a Taxonomy is exclusive
ie. taxonomy.inclusive = false
, then there can only be one term from that taxonomy attached to a given photo. Now, a Photo may have more than one taxonomy applied to it. For instance:
Taxonomy: Colors, Inclusive Terms: Red (id 1), Blue (id 2), Green (id 3)
Taxonomy: Region, Exclusive Terms: North America (id 4), South America (id 5)
So let's say we have a photo with a lot of red and blue in it, so it gets those two terms, and it might be in South America, so it gets that term. But, a Photo can't be of both North America and South America.
So in this case if we called: photo.terms.map &:id
we would get [1,2,4]
On the client side I can basically do (pseudo code)
form_for photo
- Taxonomy.each do |tax|
- if tax.inclusive?
- tax.terms.each do |term|
- check_box term
- else
- tax.terms.each do |term|
- radio_button term
But on the model side, I would like to say, that a photo can have any of term_id 1,2,3, but only either/or 4 and 5.
Does that make sense?
Upvotes: 1
Views: 173
Reputation: 4930
For your validation problem, you can add a uniqueness validation on Term
model for taxonomy_id
field which is only executed if the taxonomy is not inclusive, ie :
class Term
validates :taxonomy_id, uniqueness: true, unless: Proc.new { |term| term.taxonomy.inclusive }
end
You can also do this with a custom validation method :
class Term
validate :uniqueness_of_taxonomy_if_exclusive
def uniqueness_of_taxonomy_if_exclusive
if !taxonomy.inclusive && Term.find_by_taxonomy_id(taxonomy_id)
errors.add(:taxonomy_id, "already exists for this exclusive taxonomy")
end
end
end
Note that this is useless in this case as the first method give the same result but may be useful if you have more complex validation to do.
For the second part of your question, here is the way to make your query in ActiveRecord, let's assume taxonomy
is the taxonomy for which you want the photos :
Photo.includes(:terms => :taxonomy).where('taxonomies.id' => taxonomy.id)
UPDATE after your comment
I believe your validation should take place in the table which make the link between photos and terms. If you have following Rails convention, it's actually the table photos_terms
.
To add a validation on it, you will have to make it a first class citizen, ie: add an active record model for this table and an id field, let's say you name it PhotoTermLink
.
Here is the code for the validation you want :
class PhotoTermLink
belongs_to :photo
belongs_to :term
validates :term_id, uniqueness: { scope: :photo_id }, unless: Proc.new { |link| link.term.taxonomy.inclusive }
end
Upvotes: 2