Rails: Sorting a query by params? - ruby-on-rails

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?

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.

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.

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

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

Related

Rails 4 - Ransack - Default Sort

Consider the following code snippet from ransack
#search = Post.search(params[:q])
#search.sorts = 'name asc' if #search.sorts.empty?
#posts = #search.result.paginate(
:page => params[:page],
:per_page => 20
)
In my case, the "name asc" is actually a related field in another model.
I am trying to figure out how to sort based on that, while keeping the ransack feature in place.
search.sorts = 'comment_name asc' if #search.sorts.empty? should do it. Substitute comment with the other model name.

Rails 2.3.x - lazy db querying

I would like to construct a query in ActiveRecord based on GET params and using named_scope. I thought I'd chain some scopes and add conditions based on GET param availability, but I fear this will overwork the db, sending a new query on each query portion added:
# in model
named_scope :sorted, :order => 'title, author'
named_scope :archived, :conditions => { :is_archived => true }
named_scope :by_date, lambda { |date| { :conditions => [ 'updated_at = ?', date ] } }
# in controller / helper
#articles = Article.sorted.all
#articles = #articles.by_date(params[:date]) if params[:date]
#articles = #articles.archived if params[:archived] == '1'
Another option I've thought of is building a method chaining string that'll then be sent to the object using Object#send, but that seems a bit dirty and somewhat problematic when the named_scope receives arguments (like by_date). I realize I can construct a query string to use with :conditions => ... in ActiveRecord::Base#find, but I thought I'd try first with named_scope to see if it's possible to do lazy querying with the latter. Any suggestions on how to do this using the named_scope and not having the database bombarded by queries? thanks.
You can make lambda more smarter
# in model
named_scope :sorted, :order => 'title, author'
named_scope :archived, lambda { |is_archived| (is_archived == 1) ? {:conditions => {:is_archived => true}} : {}}
named_scope :by_date, lambda { |date| date.nil? ? {} : { :conditions => [ 'updated_at = ?', date ]}}
# in controller / helper
#articles = Article.by_date(params[:date]).archived(params[:archived]).sorted

Conditional "or" in Thinking Sphinx search

Using RoR 2.3.8.
Here's my controller code:
class CitiesController < ApplicationController
def show
#city = City.find(params[:id])
#shops = Shop.search #city.name, {
:conditions => {:country => #city.country && (:city => #city.name || :state => #city.state)},
:page => params[:page],
:per_page => 100
}
end
end
The :conditions => {:country => #city.country && (:city => #city.name || :state => #city.state)} obviously doesn't work because I am just trying to explain what I wanna achieve.
:city and :state will be columns from Spots table, not Cities table. I want results to return either one of them fulfills the condition. But have no clue how to do it.
Thanks.
Tass has got it right - with your TS search call, it should look something like this:
def show
#city = City.find(params[:id])
#shops = Shop.search "#{#city.name} #country #{#city.country} (#city #{#city.name} | #state #{#city.state})",
:match_mode => :extended,
:page => params[:page],
:per_page => 100
}
end
You'll note I've set the match mode - Thinking Sphinx will do this automatically if you're using the :conditions argument - but when constructing the query manually, you need to set it yourself.
Place your raw search to Sphinx - you should find the correct method in the TS docu. A reference for raw search. You probably want something like
"#city_country #{#city.country} (#city_name #{#city.name} | #city_state #{#city.state})"
(I'm not sure how TS names the indexes. Check that.)

RoR condition for collection

i want to fetch other than collect value
i used below code
#boys = People.find(:all,:conditions => ["id != ?", #boy_id],:order => "created_at DESC")
where, #boy_id is collection of ids
but its not fetching the values
when i use IN(?) it fetches id in collection...
i want to fetch id values which are not in collection.. please suggest contrary to IN(?)
Have you tried
#boys = People.find(:all, :conditions => ["id NOT IN (?)", #boy_id], :order => "created_at DESC")

Multiple conditions with will_paginate

I am using will_paginate for pagination but I can't seem to use more than one condition at a time. For example, if I want to have a sql query that ends in "Where office_id = 5", then it's pretty straight forward, I can do that. but what if I want to do "Where office_id = 5 AND primary_first = 'Mark'"? I can't do that. I have no idea how to enter multiple conditions. Can you help??
Below is an example of my code:
def self.search(search, page, office_id)
paginate :per_page => 5, :page => page,
:conditions => ['office_id', "%#{office_id}"], # + ' and primary_first like ?', "%#{params[:search]}%"],
#:conditions => ['primary_first', "%#{search}%"],
:order => 'created_at'
end
Thank you for your help!
If I understand correctly what you want, this should work:
def self.search(search, page, office_id)
paginate :per_page => :page => page,
:conditions => ["office_id LIKE ? and primary_first LIKE ?", "%#{office_id}", "%#{search}%"]
:order => :created_at
end
try :conditions => ["office_id = ? and primary_first = ?", office_id, search]
or using a dynamic finder:
paginate_by_office_id_and_primary_first(office_id, search, :per_page => 5, :page => page, :order => 'created_at')
These will only work for equivalency, however the db is defining it. If you need to use LIKE, see Jens Fahnenbruck's answer

Resources