10

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?

4 Answers 4

16

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.

Sign up to request clarification or add additional context in comments.

1 Comment

If anyone else happens across this, the updated syntax for the scope is like so: scope :ordered, ->(*args) { order(args.first || 'created_at DESC') } Method itself is still useful!
2

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.

Comments

2

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

Comments

1

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.