1

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!

Karan
  • 14,824
  • 24
  • 91
  • 157

4 Answers4

3

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.

Community
  • 1
  • 1
Buck Doyle
  • 6,333
  • 1
  • 22
  • 35
  • I think this could be slightly simplified to `user.groupizations.find_by_group_id(user.groups.first).role`. In Rails 3.1 or newer, using `#first` should also be faster since it will retrieve a single row, whereas `#[]` will force all records to be retrieved. – Steve Jorgensen May 13 '12 at 01:06
  • Yes, I originally had `user.groups.first` but I changed it to keep in line with how @Newton was accessing the group in the question. But like I said, can’t imagine why the `role` for only the first group would be accessed arbitrarily like this. – Buck Doyle May 13 '12 at 01:11
  • I guess I was unclear though. The simplification was just the omission of `.id`. You can usually pass a model instance as a finder argument, and ActiveRecord understands that it should use the id value from that. – Steve Jorgensen May 13 '12 at 01:15
2

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.

Kevin Bedell
  • 13,254
  • 10
  • 78
  • 114
2

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.

pixeltrix
  • 991
  • 7
  • 7
1

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.

Steve Rowley
  • 1,548
  • 1
  • 11
  • 18