Association properties


#1

One of the great advantages of relational modelling (over typical object
modelling) is the ability to easily extend an association (i.e. an
association table) with properties of the association itself. In
general O/R mappings make this very difficult to access or take
advantage of. RoR certainly makes the association properties available
by grabbing the association properties due to their participation in the
join, but I haven’t yet figured out how to manage update of these
properties.

An example of this is below. The question is, how can I update
‘indirect_count’, ‘created_at’ and created_by properties of the
association table when I add a member to a group? Do I need to add the
proper instance variables to the Actor object that I am adding to the
members set? Or do I need to go to the SQL itself? Maybe this should
be a FAQ?

— MySQL —
CREATE TABLE users (
id int(11) NOT NULL auto_increment,
type varchar(16) default ‘null’,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE group_members (
group_id int(11) NOT NULL default ‘0’,
member_id int(11) NOT NULL default ‘0’,
indirect_count int(11) NOT NULL default ‘0’,
created_at datetime NOT NULL default ‘0000-00-00 00:00:00’,
created_by int(11) NOT NULL default ‘0’,
KEY group_id (group_id),
KEY member_id (member_id),
KEY created_by (created_by),
CONSTRAINT group_members_ibfk_3 FOREIGN KEY (member_id) REFERENCES
users (id),
CONSTRAINT group_members_ibfk_4 FOREIGN KEY (group_id) REFERENCES
users (id)
CONSTRAINT group_members_ibfk_5 FOREIGN KEY (created_by)
REFERENCES users (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

— actor.rb —
class Actor < ActiveRecord::Base
set_table_name ‘users’
has_and_belongs_to_many :groups, :join_table => ‘group_members’,
:foreign_key => ‘member_id’
end

class Person < Actor
end

class Group < Actor
has_and_belongs_to_many :members, :class_name => ‘Actor’, :join_table
=> ‘group_members’
end



#2

I think maybe you are overcomplicating it…here’s a simple example of
(i
think) what you are trying to accomplish. It’s possible I’m not
understanding as well, so correct me if I am wrong.

users

id
name
created_at

groups

id
name
creator_id
created_at

groups_users (join table)

group_id
user_id
date_joined

class User < ActiveRecord::Base
has_and_belongs_to_many :groups

def join_group(group, creator)
groups.push_with_attributes(group, :date_joined => Time.now, :creator_id
=>
creator.id http://creator.id)
end
end

class Group < ActiveRecord::Base
has_and_belongs_to_many :users

def add_user(user, creator)
users.push_with_attributes(user, :date_joined => Time.now, :creator_id
=>
creator.id http://creator.id)
end
end

this way you can do:

user = User.create(:name => “Bob”)
group = Group.create(:name => “Geeks-R-Us”)

add a user/group association

user.join_group(group, user)

or group.add_user(user, user)

how many users in each group

groups = Group.find(:all)
groups.each do { |g| puts “group: #{g.name http://g.name}, members: #{
g.users.size}” }

how many groups does user belong to

user.groups.size

remove a user/group association

user.groups.delete(group)

or group.users.delete(user)

hope this helps


#3

Chris H. wrote:

I think maybe you are overcomplicating it…here’s a simple example of
(i think) what you are trying to accomplish. It’s possible I’m not
understanding as well, so correct me if I am wrong.

Actually, I’m not overcomplicating it, but ‘push_with_attributes’ is the
right answer it seems. Since a Group can contain other groups, I need
the inheritance I showed. Thanks for the pointer. It’s still a bit
annoying that I have to make explicit reference to ‘creator_id’, but
that’s the price of putting this information into the association table.


#4

Chris H. wrote:

if a group can belong to group, take a look at acts_as_tree:

Yes, but a group (or user) can itself potentially belong to many other
groups… Trust me, I do know what I’m modelling.


#5

if a group can belong to group, take a look at acts_as_tree:

groups

id
parent_id (can be null)
name

class Group < ActiveRecord::Base
has_and_belongs_to_many :users
acts_as_tree


end

root_group = group.create(:name => “Root Group”)
child_group = group_a.children.create(:name => “Child Group”)

acts_as_tree ==>
http://api.rubyonrails.com/classes/ActiveRecord/Acts/Tree/ClassMethods.html#M000478


#6

Maybe this link could be helpful:

http://wiki.rubyonrails.com/rails/pages/HowToCreateASelfReferentialManyToManyRelationship


#7

Can you specify the operations you want to do with some fixtures and
test cases?

fixtures:
G 1,2 (group 1 belongs to group 2)
G 1,3
G 4,3
G 4,5
U 6,4 (user 6 belongs to group 4)

tests:
UserBelongsToGroup(6,1)=false
UserBelongsToGroup(6,2)=false
UserBelongsToGroup(6,3)=true
UserBelongsToGroup(6,4)=true
UserBelongsToGroup(6,5)=true
UsersGroups(6)=[3,4,5] (All groups a user belongs to)
GroupsUsers(3)=[6] (All users belonging to a group)

The tables are ok, presuming Type is used to separate between user and
group.
After your confirmation, it’s possible to dig into implementation.