Jeff Pritchard wrote:
Interesting, so I guess one can't get away from learning some SQL syntax
afterall.
Well, technically, you *can* do this without "getting dirty with SQL"
. You could fetch *all* of the articles and then kick out any that are
in review. It could look something like this
articles = Article.find(:all, :include => :articles_in_review).reject {
article> article.articles_in_review.any? }
[Note that I'm assuming an article could potentially be "in review" more
than one time, possibly by multiple "reviewers". If this is not the
case, adjust accordingly.]
But doing it this way will pull back every single article *and* all the
articles_in_review *and* require the Ruby code to loop over the entire
collection to see which ones are under review.
On the other hand, "getting a little dirty" with SQL off loads that
decision to the database server which:
1) can whip through the data considerably faster than Ruby can
2) does not require all of the data to be sent from database server to
Ruby, which
3) has a lower memory impact
4) is just a more optimal solution
I'd like to understand this:
left outer join articles_in_review on article_id = articles.id
where articles_in_review.id IS NULL
a little better. Is this basically saying "find me the
article_in_review in which article_id = articles.id", and then applying
the condition that the id of the returned article_in_review is NULL (in
other words, there weren't any?)
Basically, yes. In SQL parlance, a LEFT [OUTER] JOIN (the keyword OUTER
not typically necessary) means to return all records of the primary
table regardless of the existence of a row in the secondary table. So in
this case, all articles will be returned regardless of the existence of
articles_in_review. Once the join has been completed, the WHERE then
further filters the resultset on the condition that the joined
artciles_in_review lacks a value for article_id. Since that was the
column we joined on, that indicates that the join failed and therefore
an articles_in_review record for that article does not exist.
This should be adequate to explain why I was hoping there was a way to
do this with just the associations. 
SQL is an acquired taste
I've been doing it in one form or another
for many years. Rails does such a good job with most of it that I don't
usually have to think about it. But there are enough places in which
something a bit more complicated is required that I construct SQL
statements dynamically.
I do want to add one note to Fred's comment about the difference between
:include and :join. Whereas using association names in :include will
produce LEFT joins and :joins will produce INNER joins, :include also
loads the associated objects, while :joins is just there for the SQL
processing. In this case it would not have made a difference because you
are specifically looking for those that do not have associated objects,
so there'd be nothing to load. This may not be the case at other times,
and you could possibly be returning and loading more data than is
necessary. However, by using :joins and providing the statement
explicitly, you get the advantage of knowing it's a LEFT join without
worry about any associated objects being built (and taking up memory
unnecessarily).
thanks,
jp
No problem.
Peace,
Phillip