ActiveRecord: Least surprise? Really?

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.

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.

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:

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

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

It is making more sense now.

Cheers!

-Steve