Run a business? Check out LessAccounting, our bookkeeping system that'll save you hours per week.

Announcing:

We built the entrepreneur's bookkeeping system, it's called LessAccounting. You'll probably love it.

Includes and Conditions in Named Scope

written by Steven on November 10, 2008

I was making a change to "NewsMilk":http://newsmilk.com the other day where I needed to make sure that once a user sees a story, it is not shown to that user again. To do this I added a has many between stories and people, and I wanted to make a nice named scope to handle this.

Currently the code that gets new stories looks something like this:

story = viewable.first

where viewable is a named scope that adds the proper conditions to the find. I wanted to make a little something something, like this:

story = viewable.not_viewed(person).first

where the person argument is the person that should not have seen this story before. My first attempt at this was:


 named_scope :not_viewed, lambda { |person|
    if person
      {:include=>:viewed_stories, :conditions=>{:person_id=>person.id}}
    else
      {}
    end
  }

Which outputs this sql:


SELECT *
FROM `stories`
WHERE (person_id != 1)

And this error:


ActiveRecord::StatementInvalid (Mysql::Error: Unknown column 'person_id' in 'where clause': ....

This doesn't work because the rails eager loading optimization stuff takes the query and transforms it into two queries. The first query gets the stories and the second the viewed_stories. Since the person_id column does not exist in the stories table, the sql bombs. So I made one small change, I went back to old school conditions array and used the qualified column name for person_id:


  named_scope :not_viewed, lambda { |person|
    if person
      {:include=>:viewed_stories, :conditions=>["viewed_stories.person_id != ?", person.id]}
    else
      {}
    end
  }

Which outputs this sql:


SELECT DISTINCT `stories`.id
FROM `stories`
LEFT OUTER JOIN `viewed_stories` ON viewed_stories.stories_id = stories.id
WHERE (viewed_stories.person_id != 1)
ORDER BY published_at desc LIMIT 1

Notice how now Rails is only doing one query with a join instead of two. And for those of you wondering why I check the existence of person in the named scope, it's just to avoid the join if it is not needed, because without a person there is nothing to where on.

Business Owners: save hours per week with LessAccounting. It's like Quickbooks, just not total shit.

1 Comment

Lourens Naude
Lourens Naude said on November 11, 2008

General best practice being to always use the full table.field syntax.

It’s fairly common in performance reviews to optimize certain sections with :include, which WILL blow up in the manner you mentioned in the post.Same with custom joins.

Initial additional keystrokes being king to the WTF moment later, in a likely stressful and timestrapped scenario.

Leave a Comment

About Steven
Steven Bristol has written code for the past 20 years. He like green vegetables and kittens, oh and butterflies too. He loves to throw ninja stars at his enemies.

You Should...

Follow Steven on Twitter
Friend Steven on Facebook
Subscribe
LessEverything Copyright 2011 LessEverything.com
We don't like footers, they're kinda boring