Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails: Sorting a query by params?

I am using running a simple find all and paginating with willpaginate, but I'd also like to have the query sorted by the user. The first solution that came to mind was just use a params[:sort]

http://localhost:3000/posts/?sort=created_at+DESC

@posts = Post.paginate :page => params[:page], :order => params[:sort]

But the problem with his approach is that the query is defaulting as sorting by ID and I want it to be created_at.

Is this a safe approach to sorting and is there a way to default to created_at?

like image 847
kush Avatar asked Oct 18 '08 14:10

kush


4 Answers

I’d use a named scope for providing the default order (available since Rails 2.1).

You’d add the scope in your Post model:

named_scope :ordered, lambda {|*args| {:order => (args.first || 'created_at DESC')} }

Then you can call:

@posts = Post.ordered.paginate :page => params[:page]

The example above will use the default order from the named_scope (created_at DESC), but you can also provide a different one:

@posts = Post.ordered('title ASC').paginate :page => params[:page]

You could use that with Romulo’s suggestion:

sort_params = { "by_date" => "created_at", "by_name" => "name" }
@posts = Post.ordered(sort_params[params[:sort]]).paginate :page => params[:page]

If params[:sort] isn’t found in sort_params and returns nil then named_scope will fall back to using the default order.

Railscasts has some great info on named_scopes.

like image 146
Matt Avatar answered Nov 09 '22 23:11

Matt


The Ruby idiom to set a default would be:

@posts = Post.paginate :page => params[:page], :order => params[:sort] || "created_at"

But the approach isn't safe. The paginate method will not bother with a parameter like "created_at; DROP DATABASE mydatabase;". Instead, you could use a dictionary of valid sort parameters (untested):

sort_params = { "by_date" => "created_at", "by_name" => "name" }

@posts = Post.paginate :page => params[:page], :order => sort_params[params[:sort] || "by_date"]

So that the URI becomes:

http://localhost:3000/posts/?sort=by_date
like image 38
Rômulo Ceccon Avatar answered Nov 10 '22 00:11

Rômulo Ceccon


In general, the way to supply default values for Hash and Hash-like objects is to use fetch:

params.fetch(:sort){ :created_at }

A lot of people just use || though:

params[:sort] || :created_at

I prefer fetch myself as being more explicit, plus it doesn't break when false is a legitimate value.

like image 44
Avdi Avatar answered Nov 09 '22 23:11

Avdi


I prefer this idiom:

@posts = Post.paginate :page=>page, :order=>order
...

def page
  params[:page] || 1
end

def order
  params[:order] || 'created_at ASC'
end
like image 1
Scott Avatar answered Nov 09 '22 23:11

Scott