Become a Rails Association Pro: Replicating has_many with Pure Ruby

Gokul
4 min readMay 6, 2023

Understanding the has_many Association

In Ruby on Rails, the has_many association is used to define a one-to-many relationship between two models. It allows us to easily access a collection of associated records for a particular record. This association is one of the most commonly used in Rails applications.

To understand how the has_many association works, let's consider an example of a blog application with two models: Post and Comment. Each post can have many comments, so we'll define a has_many association between them.

In the Post model, we'll add the following line of code:

class Post < ApplicationRecord
has_many :comments
end

This line of code tells Rails that a post has many comments associated with it. It also generates several methods that we can use to access and manipulate the associated comments:

  • comments: This method returns a collection of comments associated with the post.
  • comments.create: This method creates a new comment associated with the post.
  • comments.build: This method returns a new comment associated with the post (but does not save it to the database).
  • comments.destroy_all: This method destroys all comments associated with the post.

We can also define options for the has_many association. For example, we might want to order the comments by creation date:

class Post < ApplicationRecord
has_many :comments, -> { order(created_at: :desc) }
end

This will order the comments associated with a post in descending order of creation date.

In the Comment model, we'll add the following line of code:

class Comment < ApplicationRecord
belongs_to :post
end

This line of code tells Rails that a comment belongs to a post. It also generates several methods that we can use to access and manipulate the associated post:

  • post: This method returns the post associated with the comment.

We can also define options for the belongs_to association. For example, we might want to validate that a comment always has a post associated with it:

class Comment < ApplicationRecord
belongs_to :post, required: true
end

This will raise an error if we try to save a comment without associating it with a post.

In addition to the has_many association, Rails also provides several other types of associations, including belongs_to, has_one, has_many_through, and has_and_belongs_to_many. These associations allow us to define more complex relationships between models in our application.

How?

To replicate the has_many association with pure Ruby code, we can create a Post class and a Comment class, and define a one-to-many relationship between them.

Here’s an example implementation:

class Post
attr_reader :id, :title, :comments

def initialize(id, title)
@id = id
@title = title
@comments = {}
end

def add_comment(comment)
@comments[comment.id] = comment
end

def remove_comment(comment)
@comments.delete(comment.id)
end

def find_comment(comment_id)
@comments[comment_id]
end
end
class Comment
attr_reader :id, :body, :post_id

def initialize(id, body, post_id)
@id = id
@body = body
@post_id = post_id
end
end

In this code, the Post class has a comments attribute that stores a hash of comments. The add_comment method is used to add a comment to the post's comments hash. The remove_comment method is used to remove a comment from the post's comments hash. The find_comment method is used to find a comment by its ID.

The Comment class has an id attribute and a body attribute. The id attribute is used to uniquely identify each comment.

To use this implementation, we can create a new Post object and add comments to it like this:

post = Post.new(1, "My First Post")
comment1 = Comment.new(1, "This is a great post!", post.id)
comment2 = Comment.new(2, "I disagree with your points.", post.id)
post.add_comment(comment1)
post.add_comment(comment2)

We can then retrieve comments from the post like this:

post.comments 
# => {
1=>#<Comment:0x00007fbd128b6c28 @id=1, @body="This is a great post!", @post_id=1>,
2=>#<Comment:0x00007fbd128b6c00 @id=2, @body="I disagree with your points.", @post_id=1>
}


post.find_comment(1)
# => #<Comment:0x00007fbd128b6c28 @id=1, @body="This is a great post!", @post_id=1>

We can then retrieve the post from the comment like this:

Comment.last.post

#<Post:0x00007fbd428d6c32 @id=1, @title="My First Post", @comments={
1=>#<Comment:0x00007fbd128b6c28 @id=1, @body="This is a great post!", @post_id=1>,
2=>#<Comment:0x00007fbd128b6c00 @id=2, @body="I disagree with your points.", @post_id=1>
}>

We’ve created a new post and a new comment, along with the retrieve options. We’ve then associated the two objects by setting the post’s comments to the post object and the inverse.

I appreciate you taking the time to read this. Please follow me on Medium and subscribe to receive access to exclusive content in order to keep in touch and continue the discussion. Happy Reading!!!

--

--

Gokul

Consultant | Freelancer | Ruby on Rails | ReactJS