I have the following classes:
class Post < ApplicationRecord
has_many :post_tags
has_many :tags, through: :post_tags
end
class PostTag < ApplicationRecord
belongs_to :post
belongs_to :tag
end
class Tag < ApplicationRecord
has_many :post_tags
has_many :posts, through: :post_tags
end
and the following test (rspec)
RSpec.describe Post, :model do
let(:post) { Post.new }
let(:post_tag) { PostTag.new tag: tag }
let(:tag) { Tag.new }
before do
PostTag.destroy_all
end
# passes
it 'sets tags when setting tags association' do
post.tags << tag
expect(post.tags).not_to be_empty
end
# fails
it 'sets tags when setting post_tags association' do
post.post_tags << post_tag
expect(post.tags).not_to be_empty
end
end
Is there a way to set the PostTag
association (has_many
) and be able to get a collection proxy of Post
instances when calling posts
(has_many :thorugh
) association without saving the record?
more context:
I want to implement a duplicate method and I need to duplicate the associations too, the join model has extra information like post_tag.name
that I don't want to lose on duplication
class Post < ApplicationRecord
has_many :post_tags
has_many :tags, through: :post_tags
def duplicate
new_instance = dup
new_instance.post_tags = post_tags.map(&:dup)
new_instance
end
end
I tried to do it in the way of the test that passes (setting the tags
via the has_many :through
) association, but that means I will lose data that is stored in the PostTags
instances. Is there a way to set the post_tags
and get the tags
without persisting?
I think I can confidently say the answer is 'no'.
it 'sets tags when setting post_tags association' do
post.post_tags << post_tag # post_tag has no ID yet, it's not saved.
expect(post.tags).not_to be_empty
end
post.tags
relies on has_many :tags, through: :post_tags
So the SQL of post.tags
will be something like:
SELECT "tags".* FROM "tags" INNER JOIN "post_tags" ON "tags"."id" = "post_tags"."post_id" WHERE "post_tags"."post_id" = 22
This requires post_tags
and tags
to be records in the database that can be retrieved.
I bet this test would pass if you saved the records first:
RSpec.describe Post, :model do
let(:post) { Post.new }
let(:post_tag) { PostTag.new tag: tag }
let(:tag) { Tag.new }
it 'sets tags when setting post_tags association' do
post_tag.save
tag.save
post.post_tags << post_tag.reload
expect(post.tags).not_to be_empty
end
end
But this isn't the behavior you want, I'm guessing.
Is there an issue with multiple post
s having the same post_tag
s?
def duplicate
new_instance = dup
new_instance.post_tags = post_tags # don't make dupes .map(&:dup)
new_instance
end