Tricky active record relationships - polymorphic bi-directional self-referential - sql

How would you model the references and citations to publications (articles, books, chapters, etc...)?
A publication can be an article, book or a chapter and it has many references to other publications and other publications refer to it (call these citations)
I need to be able to list the relationships among the publications: The references in a publication and the citations from other publications to this publication
My initial understanding is that this would be a polymorphic relationship to handle the different types of publications and that it would require a bidirectionalself join.
My stab at it
Publication
belongs_to :writing, :polymorphic =>true
has_and_belongs_to_many :references
:class_name => "Publication"
:join_table => 'reference_citation'
:foreign_key => 'reference_id'
:foreign_key => 'citation_id'
Book, Chapter, Article all have:
has_many :publications :as =>writing
I find this a bit confusing so any suggestions that would help clarify it would be great. Even object and field naming suggestions.
[I asked a less clear version of this question here.]
I also probably need to use has many through because I will need the ability to destroy the relationship

Here's a solution using a self-referential relationship using single table inheritance. Use these commands to create the app:
$ rails myproject
$ cd myproject
$ script/generate model publication type:string name:string
$ script/generate model citation publication_id:integer reference_id:integer
The setup the relationships this way:
class Publication < ActiveRecord::Base
has_many :citations
has_many :cited_publications, :through => :citations, :source => :reference
has_many :references, :foreign_key => "reference_id", :class_name => "Citation"
has_many :refered_publications, :through => :references, :source => :publication
end
class Citation < ActiveRecord::Base
belongs_to :publication
belongs_to :reference, :class_name => "Publication"
end
class Article < Publication
end
class Book < Publication
end
class Chapter < Publication
end
Now we can create the DB and try it out from the console:
$ rake db:migrate
$ script/console
Loading development environment (Rails 2.2.2)
>> a = Article.create!(:name => "Article")
=> #<Article id: 1, ...>
>> b = Book.create!(:name => "Book")
=> #<Book id: 2, ...>
>> a.citations.create(:reference => b)
=> #<Citation id: 1, publication_id: 1, reference_id: 2, created_at: "2009-02-15 14:13:15", updated_at: "2009-02-15 14:13:15">
>> a.citations
=> [#<Citation id: 1, ...>]
>> a.references
=> []
>> b.citations
=> []
>> b.references
=> [#<Citation id: 1, publication_id: 1, reference_id: 2, created_at: "2009-02-15 14:13:15", updated_at: "2009-02-15 14:13:15">]
>> a.cited_publications
=> [#<Book id: 2, type: "Book", name: "Book", created_at: "2009-02-15 14:11:00", updated_at: "2009-02-15 14:11:00">]
>> a.refered_publications
=> []
>> b.cited_publications
=> []
>> b.refered_publications
=> [#<Article id: 1, type: "Article", name: "Article", created_at: "2009-02-15 14:10:51", updated_at: "2009-02-15 14:10:51">]

Here's a solution that doesn't use Single Table Inheritance for the publications. That means that there are articles, books and chapters tables, instead of one publications table. Here are the commands to run to create the app:
$ rails myproject
$ cd myproject
$ script/generate model book name:string
$ script/generate model chapter name:string
$ script/generate model article name:string
$ script/generate model citation publication_type:string publication_id:integer reference_type:string reference_id:integer
Create this file in lib/acts_as_publication.rb:
module ActsAsPublication
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def acts_as_publication
has_many :citations, :as => :publication
has_many :references, :as => :reference, :class_name => "Citation"
end
end
end
Create this file in config/initializers/acts_as_publication.rb:
ActiveRecord::Base.send(:include, ActsAsPublication)
Then call that in each model, Article, Book and Chapter, like this:
class Article < ActiveRecord::Base
acts_as_publication
end
Then add these relationships in app/models/citation.rb:
class Citation < ActiveRecord::Base
belongs_to :publication, :polymorphic => true
belongs_to :reference, :polymorphic => true
end
Now we can create the DB and try it out from the console:
$ rake db:migrate
$ script/console
Loading development environment (Rails 2.2.2)
>> a = Article.create!(:name => "a")
=> #<Article id: 1, ...>
>> b = Article.create!(:name => "b")
=> #<Article id: 2, ...>
>> Citation.create!(:publication => a, :reference => b)
=> #<Citation id: 1, publication_type: "Article", publication_id: 1, reference_type: "Article", reference_id: 2, created_at: "2009-02-15 13:14:27", updated_at: "2009-02-15 13:14:27">
>> a.citations
=> [#<Citation id: 1, ...>]
>> a.references
=> []
>> b.citations
=> []
>> b.references
=> [#<Citation id: 1, ...>]
>> Book.create!(:name => "foo")
=> #<Book id: 1, name: "foo", created_at: "2009-02-15 13:18:23", updated_at: "2009-02-15 13:18:23">
>> a.citations.create(:reference => Book.first)
=> #<Citation id: 2, publication_type: "Article", publication_id: 1, reference_type: "Book", reference_id: 1, created_at: "2009-02-15 13:18:52", updated_at: "2009-02-15 13:18:52">
>> Book.first.references
=> [#<Citation id: 2, ...>]
>> a.citations
=> [#<Citation id: 1, publication_type: "Article", publication_id: 1, reference_type: "Article", reference_id: 2, created_at: "2009-02-15 13:14:27", updated_at: "2009-02-15 13:14:27">, #<Citation id: 2, publication_type: "Article", publication_id: 1, reference_type: "Book", reference_id: 1, created_at: "2009-02-15 13:18:52", updated_at: "2009-02-15 13:18:52">]

I have an incomplete answer over at http://github.com/francois/so-536261/tree/master
Basically, the DB schema does support your use case, but ActiveRecord doesn't. The solution will probably involve using find by sql or other tricks.

Related

Merge a column with results from an ActiveRecord query in Rails

I have the three following tables in Ruby on Rails 4:
The "Decision" table:
class Decision < ActiveRecord::Base
validates :title, presence: true, length: { maximum: 50 }, uniqueness: { case_sensitive: false }
validates :colour, presence: true, length: { maximum: 20 }, uniqueness: { case_sensitive: false }
has_many :decision_datafields, dependent: :destroy
has_many :datafields, through: :decision_datafields
def datafields
Datafield.where(id: self.decision_datafields.select("datafield_id"))
end
end
The "DecisionDatafield" table (linking table):
class DecisionDatafield < ActiveRecord::Base
validates :min_score, presence: true
validates_inclusion_of :min_score, :in => 1..10
belongs_to :decision
belongs_to :datafield
end
The "Datafield" table:
class Datafield < ActiveRecord::Base
validates :title, presence: true, length: { maximum: 100 }, uniqueness: { case_sensitive: false }
has_many :decision_datafields, dependent: :destroy
has_many :decisions, through: :decision_datafields
has_many :score_options, dependent: :destroy
def decisions
Decision.where(id: self.decision_datafields.select("decision_id"))
end
end
There is also a score options table, but it isn't necessary for the problem.
Anyway, what I'd like to be able to do is do a query like this:
Decision.first.datafields
... and have ActiveRecord retrieve a list of the first Decision's Datafields, along with the corresponding min_score value from the DecisionDatafields linking table.
Right now, the above query will return something like this:
#<ActiveRecord::Relation [#<Datafield id: 1, title: "DF1", created_at: "2015-03-28 09:59:26", updated_at: "2015-03-28 09:59:26">, #<Datafield id: 2, title: "DF2", created_at: "2015-03-28 09:59:26", updated_at: "2015-03-28 09:59:26">]>
... which is nice, but I want it to look like this:
#<ActiveRecord::Relation [#<Datafield id: 1, title: "DF1", created_at: "2015-03-28 09:59:26", updated_at: "2015-03-28 09:59:26", min_score: 5>, #<Datafield id: 2, title: "DF2", created_at: "2015-03-28 09:59:26", updated_at: "2015-03-28 09:59:26, min_score: 7">]>
The difference is that the min_score from the DecisionDatafield linking table has been joined to the records returned by the query.
Thanks!
This might work (haven't tested it):
class Decision < ActiveRecord::Base
...
def data_fields
Datafield.includes(:decision_datafield).select("datafields.*, decision_datafields.min_score").where(id: self.decision_datafields.select("datafield_id"))
end
end
Call it with Decision.first.data_fields
It seems that the following works:
Datafield.where(id: self.decision_datafields.select("datafield_id")).includes(:decision_datafields).joins("left join decision_datafields on datafields.id = decision_datafields.datafield_id").select("datafields.*, decision_datafields.min_score as min_score")
It would appear, however, that when using the Rails console, the min_score does not get added to the record.
However, calling Decision.first.datafields.first.min_score does return a value (it's just not shown when viewing the record via Decision.first.datafields.first.

self-referential has_many trouble

I getting troubles with a self referential table.
I got an orb model which can hold planets, stars and moons. I want to tell that one thing "orbit" another
i look at the rails guide, but i coulnd get it working
My model:
class Orb < ActiveRecord::Base
belongs_to :orb_type
has_and_belongs_to_many :books
belongs_to :orbit, :class_name => "Orb"
has_many :orbs, :class_name => "Orb", :foreign_key => "orb_id"
attr_accessible :descr, :nome, :orb_type_id, :book_ids, :orb_id
validates :nome, uniqueness: true, presence: true
end
I think i am using bad relations names (maybe in the wrong way around)
1.9.3-p448 :002 > earth = Orb.find(1)
Orb Load (0.2ms) SELECT "orbs".* FROM "orbs" WHERE "orbs"."id" = ? LIMIT 1 [["id", 1]]
=> #<Orb id: 1, nome: "Terra", descr: "123123", orb_type_id: 1, created_at: "2013-09-25 14:53:35", updated_at: "2013-09-25 14:57:40", orb_id: nil>
1.9.3-p448 :003 > moon = Orb.find(2)
Orb Load (0.2ms) SELECT "orbs".* FROM "orbs" WHERE "orbs"."id" = ? LIMIT 1 [["id", 2]]
=> #<Orb id: 2, nome: "Lua", descr: "asd", orb_type_id: 2, created_at: "2013-09-25 14:53:46", updated_at: "2013-09-25 14:55:31", orb_id: nil>
1.9.3-p448 :004 > sun = Orb.find(3)
Orb Load (0.2ms) SELECT "orbs".* FROM "orbs" WHERE "orbs"."id" = ? LIMIT 1 [["id", 3]]
=> #<Orb id: 3, nome: "Sol", descr: "asd", orb_type_id: 3, created_at: "2013-09-25 14:53:55", updated_at: "2013-09-25 14:53:55", orb_id: nil>
1.9.3-p448 :006 > moon.orbit=earth
=> #<Orb id: 1, nome: "Terra", descr: "123123", orb_type_id: 1, created_at: "2013-09-25 14:53:35", updated_at: "2013-09-25 14:57:40", orb_id: nil>
1.9.3-p448 :007 > earth.orbit=sun
=> #<Orb id: 3, nome: "Sol", descr: "asd", orb_type_id: 3, created_at: "2013-09-25 14:53:55", updated_at: "2013-09-25 14:53:55", orb_id: nil>
1.9.3-p448 :008 > earth
=> #<Orb id: 1, nome: "Terra", descr: "123123", orb_type_id: 1, created_at: "2013-09-25 14:53:35", updated_at: "2013-09-25 14:57:40", orb_id: nil>
1.9.3-p448 :009 > sun
=> #<Orb id: 3, nome: "Sol", descr: "asd", orb_type_id: 3, created_at: "2013-09-25 14:53:55", updated_at: "2013-09-25 14:53:55", orb_id: nil>
1.9.3-p448 :010 > moon
=> #<Orb id: 2, nome: "Lua", descr: "asd", orb_type_id: 2, created_at: "2013-09-25 14:53:46", updated_at: "2013-09-25 14:55:31", orb_id: nil>
in the end nothing get associated, the FK still nill.
The collun orb_id was added latter on the model. I setup a migration and added it in the model. I don't think it could be related to my problem...
EDIT:
Now everything is even odd. i change my model to:
class Orb < ActiveRecord::Base
belongs_to :orb_type
has_and_belongs_to_many :books
belongs_to :orbit, :class_name => "Orb"
has_many :orbits, :class_name => "Orb", :foreign_key => "orb_id"
attr_accessible :descr, :nome, :orb_type_id, :book_ids, :orb_id
validates :nome, uniqueness: true, presence: true
end
In rails console (rails c) i try:
1.9.3-p448 :008 > earth = Orb.find(1)
Orb Load (0.2ms) SELECT "orbs".* FROM "orbs" WHERE "orbs"."id" = ? LIMIT 1 [["id", 1]]
=> #<Orb id: 1, nome: "Terra", descr: "", orb_type_id: 1, created_at: "2013-09-25 17:51:26", updated_at: "2013-09-25 18:16:58", orb_id: 3>
1.9.3-p448 :009 > earth.orbit
=> nil
1.9.3-p448 :010 > earth.orbits
Orb Load (0.3ms) SELECT "orbs".* FROM "orbs" WHERE "orbs"."orb_id" = 1
=> [#<Orb id: 2, nome: "Lua", descr: "", orb_type_id: 2, created_at: "2013-09-25 17:51:40", updated_at: "2013-09-25 18:17:31", orb_id: 1>]
1.9.3-p448 :011 >
What the heck? orbits seen to return what i want, but it i try to use it:
1.9.3-p448 :004 > earth.orbits.nome
NoMethodError: Orb Load (0.4ms) SELECT "orbs".* FROM "orbs" WHERE "orbs"."orb_id" = 1
undefined method `nome' for #<ActiveRecord::Relation:0x00000003d593c0>
from /usr/local/rvm/gems/ruby-1.9.3-p448/gems/activerecord-3.2.12/lib/active_record/relation/delegation.rb:45:in `method_missing'
from /usr/local/rvm/gems/ruby-1.9.3-p448/gems/activerecord-3.2.12/lib/active_record/associations/collection_proxy.rb:100:in `method_missing'
from (irb):4
from /usr/local/rvm/gems/ruby-1.9.3-p448/gems/railties-3.2.12/lib/rails/commands/console.rb:47:in `start'
from /usr/local/rvm/gems/ruby-1.9.3-p448/gems/railties-3.2.12/lib/rails/commands/console.rb:8:in `start'
from /usr/local/rvm/gems/ruby-1.9.3-p448/gems/railties-3.2.12/lib/rails/commands.rb:41:in `<top (required)>'
from script/rails:6:in `require'
from script/rails:6:in `<main>'
Technically, it's not great that you have planets, stars, and moons all within the same model. They all behave differently and should each warrant their own model. E.g. a star will never orbit a planet, and a planet will never orbit a moon (by definition). By structuring your database the way you have, you're setting yourself up for corrupted data, which will always happen if you allow it to.
If you must have them all within the Orb model, I would recommend 2 models: 1 for the Orbs (stars, planets, moons), and 1 for the Orbits. The Orbit class would essentially be a table like so:
Orbit model:
| id | orbited_id | orbiter_id |
--------------------------------
| 0 | planet_id | moon_id |
| 1 | star_id | planet_id |
| 2 | planet_id | moon_id |
| ...| etc | etc |
And then you can set up the association in orb.rb:
has_and_belongs_to_many :orbits,
class_name: 'Orb',
join_table: :orbits,
foreign_key: :orbited_id,
association_foreign_key: :orbiter_id,
uniq: true
This will allow you to do things like
>> sun = Orb.find(1)
>> sun.orbits
=> [ <Orb mercury>, <Orb venus>, ..., <Orb pluto> ]
i figure it out. I should make the rom in the table withe the name of relation (orbit_id) instead of doing it the table name (orb_id)
Fixing that everything started to work nicely :)
has_many :orbters, class_name: "Orb", foreign_key: "orbit_id"
belongs_to :orbit, class_name: "Orb"

How to update nested data in Rails

I have some users who take quizzes. I track the result they chose. I need to figure out how to allow them to change their quiz submission. If I just associate the answer, they'll have answered the question twice. Building up the data is complex, does ActiveRecord provide a way to deal with this?
This whole example will run in a standalone file.
Here is my schema:
require 'active_record'
ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: ':memory:'
ActiveRecord::Schema.define do
self.verbose = false
create_table :users do |t|
t.string :name
end
create_table :questions do |t|
t.string :name
end
create_table :question_results do |t|
t.string :name
t.integer :question_id
end
create_table :question_results_users do |t|
t.integer :user_id
t.integer :question_result_id
end
end
here are my models:
class User < ActiveRecord::Base
has_and_belongs_to_many :question_results
has_many :questions, through: :question_results
end
class Question < ActiveRecord::Base
has_many :question_results
end
class QuestionResult < ActiveRecord::Base
belongs_to :question
has_and_belongs_to_many :users
end
Lets make three questions and some answers:
Question.create! name: "What's your favourite movie?" do |question|
question.question_results.build name: 'Gattaca'
question.question_results.build name: 'Super Troopers'
end
Question.create! name: 'Who do you want to be president?' do |question|
question.question_results.build name: 'Barack Obama'
question.question_results.build name: 'Mitt Romney'
question.question_results.build name: 'Mickey Mouse'
end
Question.create! name: "What's your favourite colour?" do |question|
question.question_results.build name: 'black'
question.question_results.build name: 'green'
end
Lets make a user:
jim = User.create! name: 'Jim'
jim.question_results << QuestionResult.find_by_name('Gattaca')
jim.question_results << QuestionResult.find_by_name('Barack Obama')
jim.question_results << QuestionResult.find_by_name('black')
jim.question_results # => [#<QuestionResult id: 1, name: "Gattaca", question_id: 1>, #<QuestionResult id: 3, name: "Barack Obama", question_id: 2>, #<QuestionResult id: 6, name: "black", question_id: 3>]
jim.question_results.map(&:question) # => [#<Question id: 1, name: "What's your favourite movie?">, #<Question id: 2, name: "Who do you want to be president?">, #<Question id: 3, name: "What's your favourite colour?">]
Now Jim changes his mind, he decides he likes Super Troopers and doesn't want to vote, but he still likes black. How do I update this without having him answer the question multiple times?
# pretend we're in a controller here (also note that I can change the format of the data, if there is something more convenient)
posted_from_form = {
questions: {
Question.all[0].id => 'Super Troopers',
Question.all[1].id => '',
Question.all[2].id => 'black',
}
}
First lets change the format of our data so that the results are in id form instead of name form:
posted_from_form = {
questions: {
Question.all[0].id => QuestionResult.find_by_name('Super Troopers').id.to_s,
Question.all[1].id => '',
Question.all[2].id => QuestionResult.find_by_name('black').id,
}
}
Then you can set the ids directly, and ActiveRecord will handle all of the complexity:
jim.question_result_ids = posted_from_form[:questions].values # => ["2", "", 6]
jim.question_results # => [#<QuestionResult id: 6, name: "black", question_id: 3>, #<QuestionResult id: 2, name: "Super Troopers", question_id: 1>]

Is it a bug of Mongoid or Rails3 ? Here are full codes that you can reproduce the strange 'bug'

$ rails -v
Rails 3.1.1
$ ruby -v
ruby 1.9.2p312 (2011-08-11 revision 32926) [i686-linux]
If you want to reproduce the problem, just follow me:
First, create these three model(just copy):
#school.rb
class School
include Mongoid::Document
include Mongoid::Timestamps
has_many :students
end
#student.rb
class Student
include Mongoid::Document
include Mongoid::Timestamps
has_many :books
belongs_to :school
accepts_nested_attributes_for :books
end
#book.rb
class Book
include Mongoid::Document
include Mongoid::Timestamps
field :name
belongs_to :student
validate :check
def check
# The calling for the 'school' method caused the issue
self.student.school
end
end
Second, run your console and paste:
ruby-1.9.2-head :001 > School.destroy_all;Student.destroy_all; Book.destroy_all; School.create
ruby-1.9.2-head :001 > Student.create school_id: School.first.id, 'books_attributes' => {'1' => {'name' => 'I am a book'}}
Then, let's see what happend:
ruby-1.9.2-head :002 > Book.count
MONGODB xxx_development['$cmd'].find({"count"=>"books", "query"=>{}, "fields"=>nil})
=> 2
And even more, if you set the 'student has_many books' relation to 'autosave: true':
class Student
......
has_many :books, autosave: true
......
end
Let's see what will happend:
ruby-1.9.2-head :001 > School.destroy_all;Student.destroy_all; Book.destroy_all; School.create
ruby-1.9.2-head :001 > Student.create school_id: School.first.id, 'books_attributes' => {'1' => {'name' => 'I am a book'}}
ruby-1.9.2-head :002 > Student.count
MONGODB xxx_development['$cmd'].find({"count"=>"students", "query"=>{}, "fields"=>nil})
=> 2
ruby-1.9.2-head :004 > Student.all.to_a
MONGODB xxx_development['students'].find({})
=> [#<Student _id: 4f62a8341d41c81bc6000002, _type: nil, created_at: 2012-03-16 02:40:52 UTC, updated_at: 2012-03-16 02:40:52 UTC, school_id: BSON::ObjectId('4f62a8341d41c81bc6000001')>, #<Student _id: 4f62a8341d41c81bc6000003, _type: nil, created_at: 2012-03-16 02:40:52 UTC, updated_at: 2012-03-16 02:40:52 UTC, school_id: nil>]
ruby-1.9.2-head :005 > Book.count
MONGODB xxx_development['$cmd'].find({"count"=>"books", "query"=>{}, "fields"=>nil})
=> 2
ruby-1.9.2-head :006 > Book.all.to_a
MONGODB xxx_development['books'].find({})
=> [#<Book _id: 4f62a8341d41c81bc6000003, _type: nil, created_at: 2012-03-16 02:40:52 UTC, updated_at: 2012-03-16 02:40:52 UTC, name: "I am a book", student_id: BSON::ObjectId('4f62a8341d41c81bc6000002')>, #<Book _id: 4f62a8341d41c81bc6000002, _type: nil, created_at: 2012-03-16 02:40:52 UTC, updated_at: 2012-03-16 02:40:52 UTC, name: nil, student_id: nil>]
This bug really run me crazy.
Why there are additional models when calling 'school' in a book validate method?
Or there is something I did wrong?
The code is fine here, you're not doing anything incorrect - but Mongoid has no issue with this same code on master or 2.4.x. See my suggestions here to find the culprit:
https://github.com/mongoid/mongoid/issues/1826

Rails :include doesn't include

My models:
class Contact < ActiveRecord::Base
has_many :addresses
has_many :emails
has_many :websites
accepts_nested_attributes_for :addresses, :emails, :websites
attr_accessible :prefix, :first_name, :middle_name, :last_name, :suffix,
:nickname, :organization, :job_title, :department, :birthday,
:emails_attributes
end
class Email < ActiveRecord::Base
belongs_to :contact
validates_presence_of :account
validates_format_of :account, :with => /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create
attr_accessible :contact_id, :account, :label
end
If I run the following query, the emails are returned as expected:
c = Contact.find(3)
Contact Load (3.2ms) SELECT `contacts`.* FROM `contacts` LIMIT 1
=> #<Contact id: 3, prefix: nil, first_name: "Micah", middle_name: nil, last_name: "Alcorn", suffix: nil, nickname: nil, organization: nil, job_title: nil, department: nil, birthday: nil, created_at: "2011-07-04 23:50:04", updated_at: "2011-07-04 23:50:04">
c.emails
Email Load (4.4ms) SELECT `emails`.* FROM `emails` WHERE `emails`.`contact_id` = 3
=> [#<Email id: 3, contact_id: 3, account: "not#real.address", label: "work", created_at: "2011-07-04 23:50:04", updated_at: "2011-07-04 23:50:04">]
However, attempting to :include the relationship does not:
c = Contact.find(3, :include => :emails)
Contact Load (0.5ms) SELECT `contacts`.* FROM `contacts` WHERE `contacts`.`id` = 3 LIMIT 1
Email Load (0.8ms) SELECT `emails`.* FROM `emails` WHERE `emails`.`contact_id` IN (3)
=> #<Contact id: 3, prefix: nil, first_name: "Micah", middle_name: nil, last_name: "Alcorn", suffix: nil, nickname: nil, organization: nil, job_title: nil, department: nil, birthday: nil, created_at: "2011-07-04 23:50:04", updated_at: "2011-07-04 23:50:04">
As you can see, the SQL is being executed, but the emails are not being returned. I intend to return all contacts with each containing email(s), so :joins won't do any good. What am I missing?
The emails are there. Did you try c.emails? You will find that the emails will be there without Rails doing an additional DB query.
The thing that :include does is called eager loading, which basically means Rails will try a best effort method of prepopulating your objects with their relations, so that when you actually ask for the relation no additional DB queries are needed.
See the section "Eager loading of associations" here:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
You might also want to check out this RailsCast:
http://railscasts.com/episodes/181-include-vs-joins