Custom Ordering Using Named Scopes
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