My schema has Articles
and Journals
that can be tagged with Tags
. This requires a has_many through:
association with a polymorphic relationship to my Tagging
join table.
Okay, that's the easy and well documented part.
My problem is that Articles
can have both primary-tags and sub-tags. The primary tags are what I'm most interested in, but my model also needs to keep track of these sub tags. Sub tags are simply labels describing the Article
that are of lesser importance, but come from the same global pool of Tags
. (in fact, one Article
's primary-tag may be another's sub-tag).
Achieving this requires the Article
model to have two associations to the Tagging
model and two has_many through:
associations to Tags
(i.e. #tags & #sub-tags)
This is what I have so far, which while valid does not keep primary-tags and sub-tags separate.
class Article < ActiveRecord::Base
has_many :taggings, as: :taggable
has_many :tags, through: :taggings
has_many :sub_taggings, as: :taggable, class_name: 'Tagging',
source_type: 'article_sub'
has_many :sub_tags, through: :sub_taggings, class_name: 'Tag', source: :tag
end
class Tagging < ActiveRecord::Base
# id :integer
# taggable_id :integer
# taggable_type :string(255)
# tag_id :integer
belongs_to :tag
belongs_to :taggable, :polymorphic => true
end
class Tag < ActiveRecord::Base
has_many :taggings
end
I know that somewhere in there I need to find the right combination of source
and source_type
but I can't work it out.
For completeness here is my article_spec.rb
that I'm using to test this — currently failing on "the incorrect tags".
describe "referencing tags" do
before do
@article.tags << Tag.find_or_create_by_name("test")
@article.tags << Tag.find_or_create_by_name("abd")
@article.sub_tags << Tag.find_or_create_by_name("test2")
@article.sub_tags << Tag.find_or_create_by_name("abd")
end
describe "the correct tags" do
its(:tags) { should include Tag.find_by_name("test") }
its(:tags) { should include Tag.find_by_name("abd") }
its(:sub_tags) { should include Tag.find_by_name("abd") }
its(:sub_tags) { should include Tag.find_by_name("test2") }
end
describe "the incorrect tags" do
its(:tags) { should_not include Tag.find_by_name("test2") }
its(:sub_tags) { should_not include Tag.find_by_name("test") }
end
end
Thanks in advance for any help on achieving this. The main problem is I cannot work out how to tell Rails the source_type to use for the sub_tags association in Articles.
Hmmm... Silence again...? What gives SO? Hello...? Bueller?
Never fear, here's the answer:
After looking into Single Table Inheritance (not the answer, but an interesting technique for other slightly-related problems), I stumbled across a SO question regarding multiple-references to a polymorphic association on the same model. (Thank you hakunin for your detailed answer, +1.)
Basically we need to explicitly define the contents of the taggable_type
column in the Taggings
table for the sub_taggings
association, but not with source
or source_type
, instead with :conditions
.
The Article
model shown below now passes all the tests:
class Article < ActiveRecord::Base
has_many :taggings, as: :taggable
has_many :tags, through: :taggings, uniq: true, dependent: :destroy
has_many :sub_taggings, as: :taggable, class_name: 'Tagging',
conditions: {taggable_type: 'article_sub_tag'},
dependent: :destroy
has_many :sub_tags, through: :sub_taggings, class_name: 'Tag',
source: :tag, uniq: true
end
UPDATE:
This is the correct Tag
model that produces functional reverse polymorphic associations on Tags. The reverse association (ie. Tag.articles and Tag.sub_tagged_articles) passes tests.
class Tag < ActiveRecord::Base
has_many :articles, through: :taggings, source: :taggable,
source_type: "Article"
has_many :sub_tagged_articles, through: :taggings, source: :taggable,
source_type: "Article_sub_tag", class_name: "Article"
end
I have also extended and successfully tested the schema to allow tagging & sub_tagging of other models using the same Tag model and Tagging join table. Hope this helps someone.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With