Rails Automatic Scoping ala Userstamp

Posted by acts_as_flinn Sun, 11 Mar 2007 08:14:00 GMT

I am working on an app to keep track of my customer base, projects, tasks, communications with customers, and some other functionality that you might find in CRM & Project Management apps. So I have a simple interface with a select box of all customers which I have placed in the header. Selecting a customer there acts as a global filter meaning it should do the following:

  1. Limit the results I get in my lists of projects, contact reports, people, and tasks so that I can focus on one customer at a time.
  2. I would also like to be able to leave that variable empty so that if I need to look at complete lists I can.
  3. One last thing, it should do this without adding tons of code all over the place and without brute force.

Userstamp Method

The Userstamp plugin gives us a good example of setting a current_user variable using the application controller and a session variable.

class User < ActiveRecord::Base
  cattr_accessor :current_user
end

The following code allows our models to refer to the User model because the session variable cannot be accessed from within models.

class ApplicationController < ActionController::Base
  before_filter do |c|
    User.current_user = User.find(c.session[:user].id) unless c.session[:user].nil?
  end
end

Well, in my case I am not working with a User but a Customer. So we’ll take what we’ve learned then apply it. Since my method is a cheap and easy ripoff of Userstamp I’ll name my method Trampstamp (because it’s cheap and easy).

Trampstamp Method

In my method the first thing that needs to be setup is the model to receive the current_customer. I’ll set that up in my Customer class.

class Customer < ActiveRecord::Base
  cattr_accessor :current_customer

  has_many :people
  has_many :projects

  ...

Now I need a way to store that value.

class CustomersController < ApplicationController
  def filter
    if params[:customer_id]
      session[:customer] = Customer.find(params[:customer_id])
    else
      session[:customer] = nil
    end
    redirect_to :back
  end

  ...

And a way to retrieve the session value and set the current customer.

class ApplicationController < ActionController::Base
  before_filter do |c|
    Customer.current_customer = c.session[:customer] unless c.session[:customer].nil?
  end

  ...

And finally a way to limit my search results without having to add conditions all over the application.

class Project < ActiveRecord::Base
  belongs_to :customer

  def self.find(*args)
    unless Customer.current_customer.nil?
      self.with_scope(:find => { :conditions => [‘customer_id = ?’, Customer.current_customer.id] }) { super }
    else
      super
    end
  end
end

Result – Judo Style automatic scoping

When I have a current_customer set,
Project.find(:all)
will yield the following query:
SELECT * FROM projects WHERE (customer_id = 1)

When no current_customer is set, it is business as usual. I suppose it would be really easy to add another method to automatically add :customer_id to project.create, and others. Maybe I’ll release that as a plugin so it’s even DRYer than this.

Here is the complete contact report list with no customer set.

This is the same list with the customer set.

Comments

Leave a response

Comments


ss_blog_claim=746d258dc975cb7923cc57154dbf1d71