Access extra attributes in join table when using t

2020-07-30 03:06发布

问题:

I was considering adding some extra properties between a has_many relationship.

For example, I have a Users table, and a Group table. Users can join groups via a :through has_many relationship. I want to add the property of the 'role' of the user in that group.

create_table "groupization", :force => true do |t|
    t.integer  "user_id"
    t.integer  "group_id"
    t.string  "role"
    t.datetime "created_at",                     :null => false
    t.datetime "updated_at",                     :null => false
  end

I was wondering, how can I access the role attribute. I was thinking something like:

user.groups[0].role 

Is that a correct approach? I know the syntax is wrong (I tried it); what would the correct syntax be like? Thanks!

回答1:

In your User model, you'll have something like:

class User < ActiveRecord::Base
  has_many :groupizations
  has_many :groups, :through => :groupizations
end

You'd access the role information just through the has_many relationship to groupizations like this in the console.

foo = User.first
bar = foo.groupizations.first
bar.role

Assuming you had groupizations on the first user.

Getting a specific user/group relationship could be done like this:

Groupizations.where("group_id = ? and user_id = ?", group.id, user.id)

Then from there you could get the role you were looking for.



回答2:

I don’t think there’s an elegant way to do this, just something like this:

user.groupizations.find_by_group_id(user.groups[0].id).role

The example code you gave with the [0] doesn’t seem like a realistic use, so if you showed us how you’d actually be wanting to access role, there might be a cleaner way.

This was discussed here.



回答3:

The best approach to this is to work with the join model and then delegate/proxy methods to the group, e.g:

class User < ActiveRecord::Base
  has_many :memberships, :include => :group, :dependent => :delete_all
  has_many :groups, :through => :memberships
end

class Group < ActiveRecord::Base
   has_many :memberships, :include => user, :dependent => :delete_all
   has_many :users, :through => :memberships
end

class Membership < ActiveRecord::Base
  belongs_to :user
  belongs_to :group

  def group_name
    group && group.name
  end

  def user_name
    user && user.name
  end
end

Then if you were to display a user's groups in a view:

<ul>
<% @user.memberships.each do |membership| %>
  <li><%= membership.group_name %> (<%= membership.role %>)</li>
<% end %>
</ul>

similarly for a group's users:

<ul>
<% @group.memberships.each do |membership| %>
  <li><%= membership.user_name %> (<%= membership.role %>)</li>
<% end %>
</ul>

This way only takes three queries - one each for the user, groups and memberships.



回答4:

It's not clear what you are looking for exactly, so I'll throw out another example with the console output to make it more obvious what it happening. Point is, however, that to get just the role(s) for a user, you'd need to specify which of the user's groups you mean (as shown by @BuckDoyle, although he just picks the first one to mirror your example). To get a list of all a user's roles, you need to iterate over the user's groupizations.

1.9.3-p194 :050 > User.find_by_name("Homer").groupizations.each {|groupization| 
                 puts groupization.group.org + ': ' + groupization.role}

User Load (2.8ms)  SELECT "users".* FROM "users" WHERE "users"."name" = 'Homer' LIMIT 1
Groupization Load (1.5ms)  SELECT "groupizations".* FROM "groupizations" WHERE 
"groupizations"."user_id" = 2

Group Load (1.9ms)  SELECT "groups".* FROM "groups" WHERE "groups"."id" = 1 
LIMIT 1

The Movementarians: cult follower

Group Load (1.4ms)  SELECT "groups".* FROM "groups" WHERE "groups"."id" = 2 
LIMIT 1

Nuclear Plant: employee

One last thought (kind of a trivial one) "groupizations" certainly works, but if "role" is the only attribute you are going to add, naming the relationship model more concretely (say "Memberships") has a practical benefit in that it makes the relationships a little clearer, at least in my experience.