Custom Ordering Using Named Scopes

Jun 02, 2009 08:58PM

Today we're going to solve the problem of implementing searching and sorting in an index view. By providing your users with an easy method of sorting and searching though their views you are increasing their happiness and productivity, and by implementing searching and sorting in an easy to understand and maintainable manner you are ensuring your own sanity.

All of the utilities required to accomplish searching and sorting your data is already provided to you in your SQL database. The real trick is to get queries to the database that are both injection safe and are generated in a manner that is easy for you or your eventual replacements to understand.

It's very tempting to rush ahead and implement some fancy AJAX features right away, but I urge you to stop and think about first implementing legacy support, and than dealing with more advanced features. It's always necessary to ensure graceful degradation in all the applications that you write.

Fat Models, Skinny Controllers

Implementing business logic in your controllers is one of the most tempting and messy practices that you can fall into the habit of. Just `getting it working` and than `cleaning it up later` is not a good way to write code, and I urge everyone to always design twice, and code once.

Our user will have to pass to the Controller how they would like the data searched and sorted, the controller will ask the appropriate Model to carry out the work, and the view will display the result. The real trick is to figure out how the Model should implement and return the different scopes that the data can be ordered in, and how should we indicate to the view how that data should be displayed.

Project Setup

A project can have many programmers, but only one manager. The following models will be assumed throughout the all of the examples.

class Person < ActiveRecord::Base end class ProjectProgrammer < ActiveRecord::Base belongs_to :project belongs_to :person end class Project < ActiveRecord::Base belongs_to :manager, :foreign_key => :manager_id, :class_name => "Person" has_many :project_programmers has_many :programmers, :through => :project_programmers, :source => :person, :class_name => "Person" end

Named Scopes

Named scopes allow for some pretty amazing things, and will be the underpinning of our sorting and searching. There are many places on the Internet that better explain exactly what named scopes do. I am going to explain how to used named scopes to quickly and cleanly sort and search your data.

class Project < ActiveRecord::Base #.. Scopes = { :manager_asc => { :order => "manager.`last_name` ASC, manager.`first_name` ASC", :joins => "LEFT JOIN people AS manager ON manager.`id` = projects.`manager_id`" } }, :manager_desc => { :order => "manager.`last_name` DESC, manager.`first_name` DESC", :joins => "LEFT JOIN people AS manager ON manager.`id` = projects.`manager_id`" } } } named_scope :by_scope, lambda { |scope| return {} unless Project::Scopes[scope.to_sym]; Project::Scopes[scope.to_sym] } #.. end

Load up the console:

>> Project.by_scope('manager_asc') >> Project.by_scope('manager_desc')

This is a basic example, but you get the idea. You can define any number of pre-canned views in your Model, and call them from your views automatically by passing the appropriate scope. Injection safe, easy to understand!

I'll leave it up to you as to how you want to implement searching, but from the above example I'm sure I've already given you a few ideas.

Small disclaimer: The above code hasn't actually been tested, and was done from memory, so you may or may not have syntax errors. The essence of the method is there, however.

-Butch