Association Accessors Helpers
Aldo Portillo
Posted on October 3, 2023
Introduction
In my previous article, I wrote about association accessor methods that can be defined in order to get information from our model. There are shortcuts to define these methods for you. They are simple to the point that they are complicated. I've spent all week rewriting this article to make these helper methods comprehensible.
has_many vs. belongs_to
The belongs_to helper method returns a single instance.
The has_many helper method returns multiple instances.
I really wish, I can say something like, "the belongs_to method is typically used in a join table"; however, there are different cases for when to use which. If you're still with me. Good luck.
Direct Associations vs. Indirect Associations
Direct Associations are when two tables are linked because one table has the key of the other table.
Indirect Associations happen when two tables are linked through a join table.
Example (I'm doing the best I can)
We will be using a social media user table and follow_request table as an example since I believe it handles a lot of edge cases. In the code blocks, I will also provide the code we are replacing with the shortcut.
In our User table we have: (I have removed a lot of things for simplicity)
id | username |
---|---|
1 | Alice |
2 | Bob |
3 | Carol |
4 | Eve |
5 | Mallory |
In our FollowRequest table we have:
id | recipient_id | sender_id | status |
---|---|---|---|
6 | 1 | 2 | pending |
7 | 1 | 3 | accepted |
8 | 3 | 5 | pending |
Lets define our direct associations first:
#app/models/follow_request.rb
##Code
def sender
my_sender_id = self.sender_id
matching_users = User.where({ :id => my_sender_id })
the_user = matching_users.at(0)
return the_user
end
## Shortcut
belongs_to(:sender, :class_name => "User", :foreign_key => "sender_id")
### Lets also define the recipient:
belongs_to(:recipient, :class_name => "User", :foreign_key => "recipient_id")
As you can see, our direct association follows a certain syntax:
#app/models/follow_request.rb
belongs_to(:method_name, :class_name => "Name of the table you are pointing at", :foreign_key => "The Attribute in the current table that corresponds to the other table's ID")
Are you lost yet? Neither am I 🤥
We will now create an indirect association. Before we get into that, lets create some more direct associations and introduce two more tables,
Photo
id | image | likes_count | owner_id |
---|---|---|---|
1 | url | ------------- | ---------- |
2 | url | ------------- | ---------- |
3 | url | ------------- | ---------- |
4 | url | ------------- | ---------- |
5 | url | ------------- | ---------- |
Like
id | fan_id | photo_id |
---|---|---|
1 | url | ---------- |
2 | url | ---------- |
3 | url | ---------- |
4 | url | ---------- |
5 | url | ---------- |
## Direct Associations
#app/models/user.rb
has_many(:likes, :class_name => "Like", :foreign_key => "fan_id")
#Returns <Likex000000>
Now that we have declared those. Lets get into writing our first Indirect Association.
Like is the join table between the Photo table and User table.
#app/models/user.rb
##Code
def liked_photos
my_likes = self.likes
array_of_photo_ids = Array.new
my_likes.each do |a_like|
array_of_photo_ids.push(a_like.photo_id)
end
## Shortcut
has_many(:liked_photos, :through => "likes", :source => "photo", :foreign_key => "photo_id")
#Returns <Photox000000>
As you can see, our indirect association follows a certain syntax:
has_many(:method_name, :through => "name of the method that gets the instances from the join table", :source => "The Attribute in the current table that corresponds to the other table's ID", :foreign_key => "name of the attribute id in the join table")
Scoped Associations
Well what if we want to get the followers of a user? We will first need to get the accepted follow_requests associated with a user and then get the users from those follow_requests.
This means we start with a scoped direct association.
Refer to our FollowRequest table. There we can see we have two attributes that refer to user_id: sender_id and receiver_id.
For the sake of understanding this, we can think of User as parent and FollowRequest as child.
In child, we write a new method named scope. Scope accepts the name to call the scope as the first parameter and a lambda with a query as the second.
In parent, we can write a new has_many to allow us to use the scope within user instead of follow_request.
#syntax
scope(:name, -> query_method({params}))
# app/models/follow_request.rb
scope(:status, -> where(status: "accepted"))
#app/models/user.rb
#This is optional but helps us if we want to make an indirect association later which we do want.
has_many(:accepted_received_follow_requests, -> {status}, :class_name => "FollowRequest", :foreign_key => "recipient_id")
That's really all we need. Since we have already defined a method in User to return all follow_requests, we can just chain those two. If we added the has_many(:accepted_received_follow_requests,...) in our user model, we can also just call that.
user_instance.follow_requests.status
#or
user_instance.accepted_received_follow_requests
#returns all accepted follow requests of User <FollowRequestx000000>
Finally, we can use this direct scoped association to get an indirect scoped association.
has_many(:followers, :through => "accepted_received_follow_requests", :source => "sender")
#returns all users whose follow requests were accepted by User <Userx000000>
Conclusion
I tried my best to write this article since I noticed that most of the documentation didn't explain the parameters in detail or they used shortcuts. Most of the information here, I got from trial and error with the rails console. If there is something completely wrong reach out and I will make adjustments.
Posted on October 3, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.