ActiveRecord: Least surprise? Really?


#1

OK I AM new to RoR, and I have more experience with Java and straight
SQL. So maybe I’m just so used to thinking in a way that is
anticipating more than “least” surprise. Anyway, here’s what
surprised me:

If I have an object that belongs to another object, I would expect the
parent to have the FK to the child, not the other way around.

Why? Because if I were to do it in memory (forget about persistence
for a minute), it would like kinda like this:

p = Parent.new
p.children << Child.new

Now in this example, p has an array of children (as in, it HAS MANY).
If I wanted to do a two-way relationship, I would do:

p = Parent.new
c = Child.new
p.children << c
c.parent = p

In this case, the parent has an array of children (literally a list of
many references to its children), and the child has a single reference
to a parent (assuming an xml-like hierarchy here, not a family-tree
type hierarchy).

Anyway, when I create a “has_one” association, I would expect this
object’s table to literally have a FK column for the “child” object.
When I create a “belongs_to” association, I would expect the same
thing.

When I create a “has_many” association, I would expect a fact table
“parent_child” to be created (with id, parent_id, and child_id
columns), and if I wanted a two-way relationship, I would add
“belongs_to” to the Child.


What’s my point?
Glad you asked.

If I wanted to create a class that maintains various user lists:
class UserLists < ActiveRecord::Base
has_many :naughty, :class => “User”
has_many :nice, :class => “User”
has_many :absent, :class => “User”
end

I would expect some fact tables to be created: naughty_users (with
columns id, userlist_id, user_id), nice_users (with id, userlist_id,
user_id), and absent_users (again with id, userlist, and id).

Could someone set my thinking straight here? Maybe I just need a
little explanation around best practices or something…


#2

If I wanted to create a class that maintains various user lists:
class UserLists < ActiveRecord::Base
has_many :naughty, :class => “User”
has_many :nice, :class => “User”
has_many :absent, :class => “User”
end

I would expect some fact tables to be created: naughty_users (with
columns id, userlist_id, user_id), nice_users (with id, userlist_id,
user_id), and absent_users (again with id, userlist, and id).

has_many :foo, :class => ‘User’ only says “find a model called User, and
expect
it to have a user_list_id pointing to us”.

To distinguish moral relevance, use:

has_many :naughty, :class => ‘User’, :conditions => { :morality =>
'naughty }

Less mind-reading on AR’s part - it was not going to guess that
User.naughty had
a relationship with UserList#naughty, unless you specify that.


Phlip


#3

Steve H wrote:

Could someone set my thinking straight here? Maybe I just need a
little explanation around best practices or something…

I’m having trouble following your logic.

Parent < ActiveRecord::Base
has_many :children
end

Child < ActiveRecord::Base
belongs_to :parent
end

A parent has many children and each child belongs to one a parent.

parent = Parent.find_by_name(“Bill”)
parent.children.count
=> 0
parent.children.build(:name => “Steve”);
parent.children.count
=> 1
steve = parent.children.find_by_name(“Steve”)
steve.name
=> Steve

Database:

parents
|id|name|
|1|Bill|

children
|id|parent_id|name|
|id|1|Steve|

It all make perfect sense to me.


#4

When I create a “has_many” association, I would expect a fact table
“parent_child” to be created (with id, parent_id, and child_id
columns), and if I wanted a two-way relationship, I would add
“belongs_to” to the Child.

I think you are getting your associations mixed up. In addition to the
has_many and belongs_to associations, you can also have a
has_and_belongs_to_many.

to use that one you would do this:

class User < ActiveRecord::Base
has_and_belongs_to_many :lists
end

class List < ActiveRecord::Base
has_and_belongs_to_many :users
end

and then you have a joining table, that if you stick to conventions,
you call it:
lists_users

  • user_id
  • list_id

That is the simplest way to handle this, assuming you do not need to
store any details about the membership.

Now if you need to store more details about the membership, then you
would have three classes: User, List and Membership

class User < ActiveRecord::Base
has_many :memberships
end

class List < ActiveRecord::Base
has_many :memberships
end

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

Hope that helps. For the full API, go to,
http://rails.rubyonrails.com/classes/ActiveRecord/Associations/ClassMethods.html


#5

Philip, Robert, alberto, thanks for your replies!! They all helped
and I do appreciate it.

It is making more sense now.

Cheers!

-Steve