views:

1762

answers:

4

I have a few models that need to have custom find conditions placed on them. For example, if I have a Contact model, every time Contact.find is called, I want to restrict the contacts returned that only belong to the Account in use.

I found this via Google (which I've customized a little):

def self.find(*args)
  with_scope(:find => { :conditions =>  "account_id = #{$account.id}" }) do
    super(*args)
  end
end

This works great, except for a few occasions where account_id is ambiguous so I adapted it to:

def self.find(*args)
  with_scope(:find => { :conditions =>  "#{self.to_s.downcase.pluralize}.account_id = #{$account.id}" }) do
    super(*args)
  end
end

This also works great, however, I want it to be DRY. Now I have a few different models that I want this kind of function to be used. What is the best way to do this?

When you answer, please include the code to help our minds grasp the metaprogramming Ruby-fu.

(I'm using Rails v2.1)

+8  A: 

You don't tell us which version of rails you are using [edit - it is on rails 2.1 thus following advice is fully operational], but I would recommand you use the following form instead of overloading find yourself :

account.contacts.find(...)

this will automatically wrap the find in a scope where the user clause is included (since you have the account_id I assume you have the account somewhere close)

I suggest you check the following resources on scopes

Jean
Thanks Jean, that will work a treat and is probably the right way to do it. The introduction of the Account is a new addition to existing code, so I guess I was over complicating things by coming from that perspective. I'll just go through and modify the existing code to be scoped by the Account.
Dan Harper - Leopard CRM
A: 

to give a specific answer to your problem, I'd suggest moving the above mentioned method into a module to be included by the models in question; so you'd have

class Contact
  include NarrowFind
  ...
end

PS. watch out for sql escaping of the account_id, you should probably use the :conditions=>[".... =?", $account_id] syntax.

It is most likely a bad idea to completely override the base finder of a model as it would break expectations from anyone outside the team including plugin authors (think search plugin) not to mention the risk of breaking rails itself (unlikely but not mpossible)
Jean
+4  A: 

Jean's advice is sound. Assuming your models look like this:

class Contact < ActiveRecord::Base
  belongs_to :account
end

class Account < ActiveRecord::Base
  has_many :contacts
end

You should be using the contacts association of the current account to ensure that you're only getting Contact records scoped to that account, like so:

@account.contacts

If you would like to add further conditions to your contacts query, you can specify them using find:

@account.contacts.find(:conditions => { :activated => true })

And if you find yourself constantly querying for activated users, you can refactor it into a named scope:

class Contact < ActiveRecord::Base
  belongs_to :account
  named_scope :activated, :conditions => { :activated => true }
end

Which you would then use like this:

@account.contacts.activated
Nathan de Vries
A: 

I know its a bit old :) but you should override .count too.

radiospiel