1

In Rails - what is the effect of using has_many :through with has_and_belongs_to_many? Consider having two models - Posts and Tags which have a many-to-many relationship as indicated below:

class Tag < ActiveRecord::Base
  has_many :posts_tag
  has_and_belongs_to_many :posts
end

class Post < ActiveRecord::Base
  has_many :posts_tag
  has_many :tags,  :through => posts_tag
end

class PostsTag < ActiveRecord::Base
  belongs_to :tag
  belongs_to :post
end

The reason I use has_and_belongs_to_many is because a tag belongs to many posts.

I did look into the Rails Association guide and see that they don't mention this case for a many-to-many relationship. I, however, did try this and running it in Rails didn't yield any behavior and from the small test database that I built, also seemed to return the correct results for post.tags and tag.posts - where post and tag refer to an instance of the Post and Tag models respectively.

Is this correct usage or does it have any side affects that I am not aware of? Also, if it is correct, is this the Rails way of achieving this?

Thanks!

Aayush Kumar
  • 1,618
  • 1
  • 11
  • 31

2 Answers2

2

You use has_and_belongs_to_many only when you're setting a many-to-many association (in other words, when the other side also has has_and_belongs_to_many). That is the meaning of this association.

You should have

class Tag < ActiveRecord::Base
  has_many :posts_tags
  has_many :posts, :through => :post_tags
end

class PostsTag < ActiveRecord::Base
  belongs_to :tag
  belongs_to :post
end

class Post < ActiveRecord::Base
  has_many :posts_tags
  has_many :tags, :through => :posts_tags
end

Notice that I used the plural, post_tags (because this is the correct way).

If you have the situation like in your comment, you should have a

belongs_to :post_tag

in your Post model, and

has_many :posts

in your PostTag model.

You may ask now: "Why should I use belongs_to :post_tag? It doesn't belong to a tag, it has a tag. So, shouldn't I use has_one :post_tag?". This was also my question at first, but then I figured that it Rails cannot always perfectly suit the english language. You need the post_tag_id column on your post, and belongs_to expects exactly that. On the other hand, has_one would expect that a column named post_id is present on the other side, that is in your post_tag. But this would be impossible, because post_tag has many posts (not only one), so the post IDs cannot be held in post_tags.

Update:
The difference between associations are only in the methods you are provided and options you can pass in (the one explained in the Rails guide on associations). For example, has_one and belongs_to have the same methods:

association(force_reload = false)
association=(associate)
build_association(attributes = {})
create_association(attributes = {})

But, for example, methods association= and create_association imply different things concerning where the foreign key should be (like I explained above).

has_and_belongs_to_many and has_many probably don't have anything different in their methods, but they differ in the options you can pass. For example, you can pass in

:dependent => :destroy

on the has_many association, but you can't pass it to a has_and_belongs_to_many, because that wouldn't make sense, since it implies a many-to-many association; if a parent record is destroyed, child records can still be connected with other records, so they shouldn't also be destroyed.

Janko
  • 8,985
  • 7
  • 34
  • 51
  • Yes, I guess the part where you said "Rails cannot always perfectly suit the English language" is what I was really looking for :P Given that some common operations were working with a `has_many :through` and `has_and_belongs_to_many`, from a purely pedagogical perspective it would be interesting to know if that has any side-affects. I understand that's not the correct way of using it though. – Aayush Kumar Feb 25 '12 at 18:43
1

While I'm not sure of the exact effects of having a has_many :through on one side of a relationship and a has_and_belongs_to_many on the other side, I do know that the more correct way, would be to use a reversed has_many :through like so:

class Tag < ActiveRecord::Base
  has_many :posts_tag
  has_many :posts,  :through => posts_tag
end

Keeping the other relationships the same.

Chris Cherry
  • 28,118
  • 6
  • 68
  • 71
  • How about the side-affects of using `has_one` one side of a relationship and `has_and_belongs_to_many` on the other side? For instance in the examples above, if we were to say that each `post` can have at most one `tag`. However, a `tag` would belong to many `posts`. – Aayush Kumar Feb 23 '12 at 08:05
  • In that case you have the relation a bit backwards. Tag `has_many` Posts, and Posts `belongs_to` a Tag. Post would have `tag_id` on it, allowing for only a single tag. `has_and_belongs_to_many` is going to use a hidden join table that `has_many` won't be using. – Chris Cherry Feb 23 '12 at 20:43