How can I access the has_many attachment model: through an associative object, without additional requests?

This is what I met several times now, and I would like to either figure out how to do what I want, or create and send a Rails patch that does this. Many times in my applications I will have some models that look something like this:

class User < ActiveRecord::Base has_many :memberships has_many :groups, through: :memberships end class Membership belongs_to :user belongs_to :group def foo # something that I want to know end end class Group has_many :memberships has_many :users, through: :memberships end 

What I want to do is access the appropriate membership from the call to the association without additional requests. For example, I want to do something like this:

 @group = Group.first @group.users.each do |user| membership = user.membership # this would be the membership for user in @group end 

Is there anything in Rails that allows this? Since the only methods that I know to achieve the result I'm talking about are terribly ugly and ineffective, something like this:

 @group.users.each do |user| membership = Membership.where(group_id: @group.id, user_id:user.id).first end 

Does ActiveRecord have several built-in tools to create this goal? It seems that this would not be too complicated, he already had to take the connection model in order to get the association in any case, so if this function does not exist, it seems to me that it should. I have come across this several times and am ready to roll up my sleeves and solve it forever. What can I do?

Update: Another template for this, which I could use, basically gets what I want, looks something like this:

 @group.memberships.includes(:user).each do |membership| user = membership.user end 

But aesthetically, I don’t like this solution, because I’m actually not interested in membership, since I am a user, and it seems to me wrong to sort through the connection model, and not the purpose of the association. But this is better than I indicated above (thanks to Jan Jan from Intridea to remind me of this).

+4
source share
4 answers

If you just want to access some membership attributes, there is an ugly trick

 group.users.select('*, memberships.some_attr as membership_some_attr') 

This works because membership is included in the JOIN implicitly.

Update

What else if you add another ugly trick to User

 class User < ActiveRecord::Base has_many :memberships has_many :groups, through: :memberships belongs_to :membership #trick, cheat that we have membership_id end 

Now:

 group.users.select('*, memeberships.id as membership_id').includes(:membership).each do |user| membership = user.membership end 
+2
source

If the data you want to receive is an attribute in the connection table, then it includes a pretty clean way to do this.

However, from your post, it seems that you have a membership method that wants to do some intelligent work with the underlying data. Also, it seems that you want to do two things with one request:

  • Display information about the user (which is why you iterate over the user object in the first place, if you just wanted membership, you just go to them).
  • Print some intellectual and processed information for your membership object.

As you noticed, everything you do here seems strange, because no matter where you put this code, it doesn't look like the right model.

I usually identify this feeling as the need for another layer of abstraction. Consider creating a new model called MemberhipUsers (this is a scary name, but you can think of something else).

Below is my attempt at ad-hoc coding, which has not been tested, but should give you an idea of ​​the solution:

 class MembershipUser < User def self.for_group(group) select('memberships.*, users.*'). joins('join memberships on memberships.user_id = users.id'). where('memberships.group_id = ?', group.id) end def foo # now you have access to the user attributes and membership attributes # and you are free to use both sets of data for your processing end end 

By creating a class representing the user and his membership in the specified group, you have created a context in which the foo method considers necessary. I assume that foo meant little without being in the context of a specific user, and that you are referring to the associated user in the foo method.

-Nick (@ngauthier)

EDIT: forgot to bring this complete circle:

 class Group def membership_users MembershipUser.for_group(self) end end # then iterate group.membership_users.each do |membership_user| membership_user.user_name # a pretend method on user model membership_user.foo # the foo method that only on MembershipUser end 
0
source

You can do it as follows:

Get all users within specific membership groups:

Where group_array is an array of identifiers for groups in which you want @user to be a member.

@user = User.all (: include =>: groups ,: conditions => ["memberships.group_id in (?)", group_array]). first

Flip it with:

@group = Group.all (: include =>: users ,: conditions => ["memberships.user_id in (?)", user_array]). first

0
source
  users = Group.first.users.select("*, memberships.data as memberships_data, users.*") 

It will give you access to all

0
source

All Articles