Become a Rails Association Pro: Replicating has_many with Pure Ruby
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!!!