Krishnaswamy Subramanian

Kirukkalgal - gist on my random thoughts

Rails: Restricting Many to Many Association in Rails

| Comments

Rails offers two different way to implement the many to many association, this post will guide you to choose the right association for your project need

has_and_belongs_to_many.rb
1
2
3
4
5
6
7
class Post
  has_and_many_belongs_to_many :comments
end

class Comments
  has_and_many_belongs_to_many :posts
end
has_many_through.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Post
  has_many :commentables
  has_many :comments, :through => :commentables
end

class Comment
  has_many :commentables
  has_many :posts, :through => :commentables
end

class Commentable
  belongs_to :post
  belongs_to :comment
end

In both approach, the intermediate table is going to hold the relation and also will yield the same result but when to choose what, still puzzled?

Pointers to choose has_and_belongs_to_many. If you choose not to:

  • Access the model which holds the relationship
  • Have any validations for the association
  • Read the associaton through the relationship

To make it more clear, lets try to restrict the number of comments for a post. This as well can be implemented in many ways but we (atleast I initally went with this approach) naturally tend to add logic in Post class

post.rb
1
2
3
4
5
6
7
8
9
10
class Post
  has_many :commentables,
  has_many :comments, :through => :commentables, :before_add => :check_comment_limit

  private
  def check_comment_limit(comment)
      #If you want to stop comment getting added to post, you have to raise an error.
      raise 'Comment limit reached' if self.comments.count = 5
  end
end

Another approach to tackle this is to add validation logic to the Comment class

comment.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Comment
  has_many :commentables,
  has_many :posts, through => :commentables

  validate :check_comment_limit

  private
  def check_comment_limit
      comment_count = self.posts.comment.count
      errors.add(:posts, 'Comment limit reached') if comment_count = 5
      #If you want to stop comment getting added to post, you have to raise an error.
      raise 'Comment limit reached' if comment_count = 5
  end
end

In both the approaches we have to raise an exception to prevent the comment from getting added to the post. I felt these approaches are not the conventional way of doing things, I went back and read the rails guide on association and rewrote the validation logic in the relation model which holds the association.

commentable.rb
1
2
3
4
5
6
7
8
9
10
11
12
class Commentable
  belongs_to :post
  belongs_to :comment

  validate :check_comment_limit

  private
  def check_comment_limit
      comment_count = self.posts.comment.count
      errors.add(:post, 'Comment limit reached') if comment_count = 5
  end
end

We don’t have to raise the exception by ourself rails will handle it gracefully.

Comments