Retrieving "grandchild" data

If I have three models:

Library has_many :shelves
Shelf belongs_to :library and has_many :books
Book belongs_to :shelf

What is the most efficient way to get all of the books in a Library?

Of course the models could be designed so that there is a direct
library-book shortcut relationship as well, but assuming that doesn’t
exist…

This does not work but I’m looking for something equivalent:

@books = @library.shelves.books

Is it best to start with books and include shelf? Like this:

@books = Book.find(:all, :include => :shelf, :conditions =>
[‘shelves.library_id = ?’, @library.id]

I don’t believe the above works with great-grandchildren however so I’m
curious about that as well. For example if Book has_many :pages and I
want to know all the pages in a certain library. I know it’s always
possible with multiple database calls but wondering what is the most
efficent way.

Thanks!!

@books = Book.find(:all, :conditions =>
[‘shelves.library_id = ?’, @library.id],
:include=>{:shelf=>{:books=>:pages}}

You can nest this as much as you like.

Ah perfect! I knew there must be a Rails way to do the joins. Thanks
very much!

On 10/9/06, Carl J. [email protected] wrote:

Of course the models could be designed so that there is a direct
[‘shelves.library_id = ?’, @library.id]
No, like this:

@books = Book.find(:all, :conditions =>
[‘shelves.library_id = ?’, @library.id],
:include=>{:shelf=>{:books=>:pages}}

You can nest this as much as you like. Just be aware of the slightly
quirky
naming conventions for the table aliases, in case you want to add
conditions
to the child or grandchild tables. Have a look at the Rails doco for
Association.

Cheers,
Max

On 10/9/06, Carl J. [email protected] wrote:

Of course the models could be designed so that there is a direct
[‘shelves.library_id = ?’, @library.id]

I don’t believe the above works with great-grandchildren however so I’m
curious about that as well. For example if Book has_many :pages and I
want to know all the pages in a certain library. I know it’s always
possible with multiple database calls but wondering what is the most
efficent way.

Thanks!!

This is a classic example of why :through was added to has_many in 1.1 -
see the excellent tutorials at:

http://blog.hasmanythrough.com/articles/2006/02/28/association-goodness
http://blog.hasmanythrough.com/articles/2006/03/01/association-goodness-2

Essentially, you add an additional declaration to Library:
has_many :books, :through => :shelves

And then you can say

@books = @library.books

and everything should work normally.

Not sure about the next level (library->shelf->book->page) - any hm:t
experts
want to offer some advice?


Matt J.
[email protected]
President/Technical Director, Acme Art Company (acmeartco.org)

Carl J. <rails-mailing-list@…> writes:

Be careful though. As you join more tables together, if you don’t have
enough
indexing on the columns which are being joined then your queries will
really
start to drag as more data is added.

Max hit the fastest solution on it with find() and :include, but at the
end of the day, these are all Ruby enumerables, so you can fall back
onto Ruby operators (albeit at the cost of more SQL queries):

@pages = library.shelves.map { |s| s.books.map { |b| b.pages } }
}.flatten