It’s common in Rails to use a has_many :through relationship to model User/Group Memberships. Sometimes we have extra data in the join that we would like to make use of, but getting that data in there can be combersome depending on our approach. For example, given the following diagram and schema:

ActiveRecord::Schema.define(:version => 20120324170519) do create_table "groups", :force => true do |t| t.string "name" t.datetime "created_at", :null => false t.datetime "updated_at", :null => false end create_table "memberships", :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 create_table "users", :force => true do |t| t.string "name" t.datetime "created_at", :null => false t.datetime "updated_at", :null => false end end

We might deal directly with the join table to assign our additonal data.

@user = User.create(name: 'User 1') @user = Group.create(name: 'Group 1') @membership = Membership.create do |m| m.user = @user m.group = @group m.role = 'admin' end @user.admin? # => true @user.editor? # => false

There’s a better way to pull this off …

@group.admins << @user @user.admin? # => true @user.editor? # => false

And this is how it’s done …

class User < ActiveRecord::Base has_many :memberships has_many :groups, :through => :memberships def admin? memberships.where(:role => 'admin').first end def editor? memberships.where(:role => 'editor').first end end

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

class Group < ActiveRecord::Base has_many :memberships has_many :users, :through => :memberships has_many :admins, :through => :memberships, :source => :user, :conditions => "memberships.role = 'admin'" do def <<(admin) proxy_association.owner.memberships.create(:role => 'admin', :user => admin) end end has_many :editors, :through => :memberships, :source => :user, :conditions => "memberships.role = 'editor'" do def <<(editor) proxy_association.owner.memberships.create(:role => 'editor', :user => editor) end end end

We’re defining an extension on our group’s has_many association which overrides the << method on that collection. We then tell the proxy association’s owner (which is our group object) to create the user/group join record, but with an additional role assignment of ‘admin’.

@group.admins << @user @user.admin? # => true @user.editor? # => false

Pretty expressive, thanks to ActiveRecord!