DEV Community

Aldo Portillo
Aldo Portillo

Posted on • Edited on

Association Accessors Helpers

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")

Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>

Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>

Enter fullscreen mode Exit fullscreen mode

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.

Top comments (1)

Collapse
 
melo616 profile image
Kat

This was instrumental in my understanding of this subject. Great blog post