I've been playing around with sorting and scoping to neatly and easily sort and filter an index page. I'm finally happy with a generic set of scoping/sorting methods I can quickly apply to a new resource. It uses the autoscope plugin I discussed earlier, as well as the smart column sorting I played with.
Scope up
To start with, drop some scoping into your model object - and add the three required methods below (with suitably appropriate modifications for your purposes).
auto_scope \ :active => {:find => {:conditions => ['destroyed_at IS NULL']}}, :archived => {:find => {:conditions => ['destroyed_at IS NOT NULL']}} # Available scopes - shouldn't this be available via autoscope? def self.scopes [:all, :active, :archived] end # Defaults (put into the model object) def self.default_scope :active end def self.default_sort '(destroyed_at IS NULL) DESC, login ASC' end
Next, drop these methods into your application.rb
# Generate a quick-and-dirty description of the chosen sort order (for displaying in the template) def sort_desc "#{params[:dir] == 'down' ? 'descending' : ''} by #{params[:col_name] || params[:col]}" end # Generates a SQL order-by snippet based on requested sort criteria (or given default). # # Adapted from the following blog post: # http://garbageburrito.com/blog/entry/447/rails-super-cool-simple-column-sorting def sort_order(model, default) orderby = "#{params[:col]} #{params[:dir] == 'down' ? 'DESC' : 'ASC'}" return sort_desc, orderby end # Uses model scoping to generate a scoped subset of the required objects # for use in the index view. # Returns a sorted list of the object . # Assumes existance of three methods on the model: # scopes:: returns an array of acceptable scopes for this model # default_scope:: returns the scope to use when none has been selected # default_sort:: represents an SQL-appropriate string for this model # representing the default way of sorting this model object def scoped_search(model, scope) scope = model.default_scope unless model.scopes.include?(scope.to_sym) sort_desc, orderby = sort_order(model, model.default_sort) # a description of the search/sort to display in the view filter_desc = "#{scope.to_s} #{model.name.pluralize.downcase} #{sort_desc}" return filter_desc, model.find(:all, :order => orderby) if scope.to_sym == :all return filter_desc, model.send(scope.to_sym).find(:all, :order => orderby) end
Using it is now easy. Drop something like this into your controller and call on it for all your collection-based actions.
# applies user-specified filters and sorting to the specified collection def filter_users scope = params[:user_scope].to_sym if params[:user_scope] @filter_desc, @users = scoped_search(User,scope) end
If you need to paginate, you can always paginate-collection, or add the code into the scoped_search function.