views:

285

answers:

3

I have 3 models: Category, Account, and SubAccount
The relations are:
Accounts has_many :sub_accounts
Categories has_many :sub_accounts

I wanted to get a list of all Categories that are not used by a given account. My method in the Category model currently looks like:

class Category < ActiveRecord::Base  
  def self.not_used_by(account)
      Category.find_by_sql("select * from categories where id not in(select category_id from sub_accounts where account_id = #{account.id})")
  end
end

My question is, is there a cleaner alternative than using SQL?

NB. I am currently using Rails 3(beta)

+1  A: 

You could move the method to the account model and use more of ActiveRecord by doing something like:

class Account < ActiveRecord::Base  
  def unused_categories
    Category.where("id NOT IN (?)", sub_accounts.map(&:category_id))
  end
end

Then you could do something like:

Account.first.unused_categories
Beerlington
Hmm.By using sub_accounts.map, is that creating 2 queries where before there was one? (i.e. one to fetch the list of sub accounts, and then another to peform the Category.where filter?But regardless, i do like how the code looks.
Snorkpete
I suppose it's technically 3 queries since Account.first, or however you are fetching that account, is also one. There are certainly trade-offs to doing things in pure SQL vs ActiveRecord. With pure SQL, you will get better performance if you were looping through a ton of accounts where the additional query is actually making a difference. But if it's only one account or just a couple, you would never notice a difference. sub_accounts is also cached the first time it is called on an account object when using activerecord, so that's another thing to consider.
Beerlington
@Beerlington.Point taken about performance. And, you somewhat predicted what i'd actually use the Category.not_used_by method for.... the reality is that i actually want an instance method on Account called unused_categories.Currently, that method delegates to my Category.not_used_by method, but that's simply because i thought the logic fit there better.In any case, this is the approach i'm going to use. THANKS!
Snorkpete
A: 

AR does not do this out of the box. You may also want to check the excellent SearchLogic gem for a programatic approach.

search = Category.search
search.id_not_in sub_accounts.map(&:category_id)
search.name_equals "auto"
search. ..# other conditions
search.all
Vlad Zloteanu
Oh, now i see that you use Rails 3 :D Searchlogic is not (yet) compatible with Rails 3.
Vlad Zloteanu
+1  A: 

Try MetaWhere. http://metautonomo.us/projects/metawhere

You'll need my fork of Arel installed until the changes get merged (soon!) but with it installed you can do something like:

Category.where(:id.not_in => sub_accounts.map(&:category_id))

Ernie Miller