I have a form which is used to filter queries. Some of the form attributes are optional; I'm just wondering how to append them as activerecord conditions if (and only if) they have a set value?
There's a fair few of them, so I'd rather not make a separate query for each pattern of potential values. Any suggestions?
To give a specific example:
people = People.paginate(
:all,
:include => [:people_postcodes, :people_specialties, :people_states],
:conditions => ["people_specialties.people_type_id = %s AND (people_postcodes.postcode_id = %s OR people_states.state_id = %s)" % [self.people_type_id, postcodeid.id, stateid]],
:page => page,
:per_page => 16
)
How would I best go about creating an extra condition (say 'nationality') only if the optional 'nationality' attribute is populated?
First off, your conditions are a little insecure. You're doing basic ruby text substitution, which will let site users inject whatever malicious sql they want. Instead, format it like this:
people = People.paginate(
:all,
:include => [:people_postcodes, :people_specialties, :people_states],
:conditions => ["people_specialties.people_type_id = ? AND (people_postcodes.postcode_id = ? OR people_states.state_id = ?)", self.people_type_id, postcodeid.id, stateid],
:page => page,
:per_page => 16
)
To answer your question, there's no natural way to tack on another condition in Rails 2.x. I would do this:
conditions = ["people_specialties.people_type_id = ? AND (people_postcodes.postcode_id = ? OR people_states.state_id = ?)", self.people_type_id, postcodeid.id, stateid]
if params[:nationality]
conditions.first += " and nationality = ?"
conditions.push params[:nationality]
end
people = People.paginate(
:all,
:include => [:people_postcodes, :people_specialties, :people_states],
:conditions => conditions,
:page => page,
:per_page => 16
)
In the example above, I'm assuming nationality is passed in as a parameter, but adjust as needed. I create the original conditions array, then append the first element (the actual condition string) and add one more element to the end of the array: the nationality value.
I hope this helps!
Related
I have a query of pictures like this
pictures = Picture.near([latitude, longitude], 6.8).where("created_at >= :time",{:time => time })
and I took out the tags which is associated with this model (every picture has_many :tags) like so
#tags = Tag.find(:all, :conditions => ["picture_id in (?)",pictures.collect(&:id)])
I used a query to pull out an attribute of each tag
#tags.map(&:tagcontent)
What I need to do is limit the results of the tags that come out. so I replaced
#tags = Tag.find(:all, :conditions => ["picture_id in (?)",pictures.collect(&:id)])
with this
numoftags = 6
tags = Tag.limit(numoftags).find(:all, :conditions => ["picture_id in (?)",pictures.collect(&:id)])
but this only yields 4 tags, when I know there is least 6 that can be pulled out. How should I modify this function.
In Rails 3.X you can do something like this (using Arel)
numoftags = 6
#tags = Tag.where(["picture_id in (?)",pictures.collect(&:id)]).limit(nooftags)
Rails 2.3.5
I've looked at a number of other questions relating to building conditions dynamically for an ActiveRecord find.
I'm aware there are some great gems out there like search logic and that this is better in Rails3. However, I'm using geokit for geospacial search and I'm trying to build just a standard conditions set that will allow me to combine a slew of different filters.
I have 12 different filters that I'm trying to combine dynamically for an advanced search. I need to be able to mix equality, greater than, less than, in (?) and IS NULLs conditions.
Here's an example of what I'm trying to get working:
conditions = []
conditions << ["sites.site_type in (?)", params[:site_categories]] if params[:site_categories]
conditions << [<< ["sites.operational_status = ?", 'operational'] if params[:oponly] == 1
condition_set = [conditions.map{|c| c[0] }.join(" AND "), *conditions.map{|c| c[1..-1] }.flatten]
#sites = Site.find :all,
:origin => [lat,lng],
:units => distance_unit,
:limit => limit,
:within => range,
:include => [:chargers, :site_reports, :networks],
:conditions => condition_set,
:order => 'distance asc'
I seem to be able to get this working fine when there are only single variables for the conditions expression but when I have something that is a (?) and has an array of values I'm getting an error for the wrong number of bind conditions. The way I'm joining and flattening the conditions (based on the answer from Combine arrays of conditions in Rails) seems not to handle an array properly and I don't understand the flattening logic enough to track down the issue.
So let's say I have 3 values in params[:site_categories] I'll the above code leaves me with the following:
Conditions is
[["sites.operational_status = ?", "operational"], ["sites.site_type in (?)", ["shopping", "food", "lodging"]]]
The flattened attempt is:
["sites.operational_status = ? AND sites.site_type in (?)", ["operational"], [["shopping", "food", "lodging"]]]
Which gives me:
wrong number of bind variables (4 for 2)
I'm going to step back and work on converting all of this to named scopes but I'd really like to understand how to get this working this way.
Rails 4
users = User.all
users = User.where(id: params[id]) if params[id].present?
users = User.where(state: states) if states.present?
users.each do |u|
puts u.name
end
Old answer
Monkey patch the Array class. Create a file called monkey_patch.rb in config/initializers directory.
class Array
def where(*args)
sql = args.first
unless (sql.is_a?(String) and sql.present?)
return self
end
self[0] = self.first.present? ? " #{self.first} AND #{sql} " : sql
self.concat(args[1..-1])
end
end
Now you can do this:
cond = []
cond.where("id = ?", params[id]) if params[id].present?
cond.where("state IN (?)", states) unless states.empty?
User.all(:conditions => cond)
I come across a small issue in my app. I'm currently using geokit to find objects near a given location, and I use the sort_by_distance_from on the found set.
See below:
#find = Item.find(:all, :origin =>[self.geocode.lat.to_f,self.geocode.lng.to_f], :within=>50, :include=>[:programs], :conditions=>["programs.name = ?", self.name])
#find.sort_by_distance_from([self.geocode.lat.to_f,self.geocode.lng.to_f]
Is there any way with geokit to paginate form the DB when sorting by distance?
AKA, not calling the full found set?
The distance column isn't working anymore:
"In the current version of geokit-rails, it is not possible to add a where clause using the distance column. I've tried many different ways to do this and didn't get it working."
It behaves the same way for where and order clauses.
One would expect to build a query like this :
scoped = Location.geo_scope(:origin => #somewhere)
scoped = scoped.where('distance <= 5')
results = scoped.all
This is not possible right now, it must be done in a single step like this:
scoped = Location.within(5, :origin => #somewhere)
results = scoped.all
github.com/geokit/geokit-rails
My approach to solve this, would use the :offset and :limit parameters for the find()
also, there is a distance field for geokit models, :order=>'distance asc'
eg.
page = 0 unless params[:page]
items_per_page = 20
offset = page * items_per_page
#find = Item.find(:all, :origin =>[self.geocode.lat.to_f,self.geocode.lng.to_f], :within=>50, :include=>[:programs], :conditions=>["programs.name = ?", self.name], :order => 'distance asc', :limit => items_per_page, :offset => page)
I have a view with a huge paginated list of records that need filtering.
Users can filter by records a few different ways (such as 'saved' records, 'read' records, and 'mark deleted' records) and I'd like for them to be able to combine these filters any possible way.
My current, flawed, non-functioning approach. The code below does not produce anything unless all of the params are specified and valid:
#view. Set the 'se' filter to true; leave all others as is
<%= link_to list_url(:id=>params[:id], :se=>"true", :st=>params[:st], :re=>params[:re]) do %>
<div class="button">Toggle SE</div>
<% end %>
#controller query. Add whichever params are passed into the conditions for the new page.
#query is paginated and sorted
#records = Record.where("user_id IN (?) AND see = ? AND star = ? AND delete = ? AND like = ?", #users.select("id"), params[:se], params[:st], params[:re]).paginate :page => params[:page], :order => (sort_column + " " + sort_direction)
What is the best way to create this filtering system?
I imagine a client-side sort would be faster than asking the server to become involved every time - is there a simple AJAX way to accomplish this kind of thing? Imagine filters the user can toggle on and off in any combination.
Try this:
conditions = {:user_id => #users.select("id")}
{
:se => :see,
:st => :star,
:del => :delete
}.each{|k1, k2| conditions[k2] = params[k1] unless params[k1].blank?}
#records = Record.where(conditions).paginate(...)
The conditions hash will be filled based on the values present in the params hash.
Edit 1
You can combine conditions hash and array.
#records = Record.where(conditions).where(
":created_at > ?", Date.today - 30).paginate(...)
You can change the user_id condition to what ever you want by specifying
conditions[:user_id] = #user.id
In the above statement, if the RHS is an array, rails automatically generates the IN clause. Otherwise, equality check(=) is performed.
Can also use Anonymous scopes: Combine arrays of conditions in Rails
I have an array of 'questions' ordered according to their number of votes, and want to show the question immediately before and immediately following the currently-selected question.
Let's say the currently-selected question is stored in the variable #question, and I'm showing a list of other questions associated with the same user. This code orders that list according to number of votes:
questions = Question.find(:all, :conditions => {:user => #question.user}).sort { |q1,q2| q2.votes.length <=> q1.votes.length}
Now how do I pick out just the question before and the question after #question in that list?
Updated my answer. I figured you guys would get the gist from the first post but here is a more elaborated example.
Can't you do something simple like:
#questions = user.questions.sort_by{ |q| -q.votes.length }
current_question_index = #questions.index(#question)
#prev_question = #questions[current_question_index-1]
#next_question = #questions[current_question_index+1]
It's more lines but just uses simple array manipulation
I would refer to time stamps.
prev = Question.first(:conditions => ["created_at < ?", #question.created_at], :order => "created_at DESC")
prev = Question.first(:conditions => ["created_at > ?", #question.created_at], :order => "created_at ASC")