
I’m not sure why, but I hate having to leave code at a point where I know it’s inefficient. There is some code in msgpad right now that is working fine, but at the expense of scalability. I’ve taken the approach of developing the code base so that it is simple. I guess I’ll add complexity later when I need to improve performance.
I’ve performed a major restructure in the code base because things just weren’t playing nicely together. The “perps” were the User, Pad, and the relationship between the two.
A User is obviously someone who uses msgpad.
A Pad is a place where User’s can converse.
The relationship was previously defined by a join table with decorations such as the state of the relationship between the pad and the user. In ruby on rails you define a join table using a has_and_belongs_to_many defintion.
So the User model had
has_and_belongs_to_many :pads
and the Pad model had
has_and_belongs_to_many :users
so if you had a pad object you could do things like
the_pad = Pad.find(1)
for user in the_pad.users
print user.login
end
Cool yeh?
The naming convention for a join table in ruby on rails is to concatenate the two joining table names by a ‘_’ with the table names in alphabetical order. So in this case the join table was ‘pads_users’.
There’s a bit of an issue with this naming convention. pads_users is kind of robotic. Normal people would call the relationship a membership… I think. Fortunately in ruby on rails you can call your model something, and the underlaying table something else via the set_table_name definition in the model. So I renamed my model from PadsUsers to Memberships. As a result I found testing stopped working properly and there were a couple of headaches I don’t recall.
Since then I have redefined the join table as an entity in its own right and called it Memberships. “David” actually says that decorated join tables as models are generally an overlooked solution in his book ‘Agile Web Development With Rails’ on page 241. By making the relationship in this way, we are able to perform operations on the relationship itself.
Each row in Memberships has an id, as well as the foreign keys of the pad and user that it links. So now
Pad model:
has_many :memberships
#This definition is not strictly necessary.
has_many :members, :through=>:memberships, :source=>:user
User model:
has_many :memberships
#This definition is not strictly necessary.
has_many :pads, :through=>:memberships
The equivilent of pad.users before is now pad.members. Alternatively I could solely go through memberships so you would have pad.memberships.each {|user| p user.login}. I also now have the ability to reference the membership explicitly and perform operations on the membership such as changing a membership status. Previously you would have had to do something like the following…
pad.users.find(@alex.id).state = ‘hungry’
or this to add a user to a pad…
pad.push_with_attributes(:user, {:state=>’invited’} )
and here’s an example of an oddity with the habtm relationship (from what I recall from experience):
membership = pad.users.find(@alex.id)
#This won’t work because membership is a User object.
membership.state = ‘hungry’
#This will work… but it’s ugly!
membership = pad.users.find(@alex.id).state = ‘hungry’
anyway, now I simply go…
m = Membership.find_membership(@alex, @the_pad)
m.hungry!
print m.user.name+” is now “+m.state
This arrangement is working well so far and in the future I’ll likely stay away from has_and_belongs_to_many. There are some technical peculiarities that come with the has_and_belongs_to_many relationship that at least for me, make it something to avoid if possible. I’m not entirely sure if having the two has_many’s with one on the join and one on the joined table is going to stuff up query caching. I guess I’ll find out later :)
Let me just disclaim this article by claiming that I am not a guru and am not immune to ignorance.