Rails custom query based on params - mysql

I have zero or many filter params being sent from a json request.
the params may contain:
params[:category_ids"]
params[:npo_ids"]
etc.
I am trying to retreive all Projects from my database with the selected ids. Here is what I have currently:
def index
if params[:category_ids].present? || params[:npo_ids].present?
#conditions = []
#ids = []
if params["category_ids"].present?
#conditions << '"category_id => ?"'
#ids << params["category_ids"].collect{|x| x.to_i}
end
if params["npo_ids"].present?
#conditions << '"npo_id => ?"'
#ids << params["npo_ids"].collect{|x| x.to_i}
end
#conditions = #ids.unshift(#conditions.join(" AND "))
#projects = Project.find(:all, :conditions => #conditions)
else ...
This really isn't working, but hopefully it gives you an idea of what I'm trying to do.
How do I filter down my activerecord query based on params that I'm unsure will be there.
Maybe I can do multiple queries and then join them... Or maybe I should put a filter_by_params method in the Model...?
What do you think is a good way to do this?

In rails 3 and above you build queries using ActiveRelation objects, no sql is executed until you try to access the results, i.e.
query = Project.where(is_active: true)
# no sql has been executed
query.each { |project| puts project.id }
# sql executed when the first item is accessed
The syntax you are using looks like rails 2 style; hopefully you are using 3 or above and if so you should be able to do something like
query = Project.order(:name)
query = query.where("category_id IN (?)", params[:category_ids]) if params[:category_ids].present?
query = query.where("npo_ids IN (?)", params[:npo_ids]) if params[:npo_ids].present?
#projects = query

I solved this. here's my code
def index
if params[:category_ids].present? || params[:npo_ids].present?
#conditions = {}
if params["category_ids"].present?
#conditions["categories"] = {:id => params["category_ids"].collect{|x| x.to_i}}
end
if params["npo_ids"].present?
#conditions["npo_id"] = params["npo_ids"].collect{|x| x.to_i}
end
#projects = Project.joins(:categories).where(#conditions)
else
basically it stored the .where conditions in #conditions, which looks something like this when there's both categories and npos:
{:categories => {:id => [1,2,3]}, :npo_id => [1,2,3]}
Then inserting this into
Project.joins(:categories).where(#conditions)
seems to work.
If you're filtering on a has_many relationship, you have to join. Then after joining, make sure to call the specific table you're referring to by doing something like this:
:categories => {:id => [1,2,3]}

Related

How to lazy-load or eager-load the sum of associated table?

We build some object in our controller:
#sites = Site.find(:all, :conditions => conditions, :order => 'group_id ASC')
And then in the view (currently), we are doing something like:
#sites.each do |site|
%p Site Name
= site.name
- actual = site.estimates.where('date<?', Date.today).sum(:actual_cost)
%p
= actual
end
Basically like that.
And of course this fires off a query for the Sites and then a query for N sites returned. I know about eager-loading associations with #sites = Site.find(:all, :include => :estimates) but in this case it didn't matter because we're doing the SUM on the query which is special it seems.
How would I eager load the SUMs in such that I don't get all the crazy queries? Currently it's over 600...
provide your conditions & ordering in this query only, which will push the result into a Hash.
sites = Site.includes(:estimates).where('estimates.date < ? ', Date.today)
.order('sites.id ASC')
actual = 0
sites.map { |site|
site.estimates.map { |estimate| actual = actual + estimate.actual_cost }
}
From your explanation, I am assuming actual_cost is a column in estimate table.

How do I protect this Rails find_by_SQL statement from SQL injection? [duplicate]

This question already has answers here:
Rails, how to sanitize SQL in find_by_sql
(6 answers)
Closed 8 years ago.
Thanks for your help with this one! I'm working on a legacy Rails application and I've checked a few sources but haven't been able to make them work for my situation. In Rails 2.3, I've got a search form (index.html.erb) and a controller (search.rb). The controller uses the search fields to build a string, which is saved in the variable #search_string.
#search_string = ""
To show the results of the search, the controller uses:
#location_matches = Location.paginate_by_sql("select * from locations where #{#search_string} order by nickname asc", :page => params[:page], :per_page => 20)
What is the simplest way for me to sanitize the above? I tried
#location_matches = Location.paginate_by_sql('SELECT * FROM locations WHERE #{#search_string} = ?', #search_string)
This throws the error ArgumentError (parameter hash expected). FYI, the #search_string is built with form fields like this:
FORM
<label for="city">City</label>
<input id="city" name="city" size="30" type="text" value="" />
CONTROLLER
if params[:city] != ""
#search_string << "and city like '%#{params[:city]}%' "
if #first_term == 'y'
#search_string = #search_string.gsub('and ', " ")
#first_term = 'n'
end
end
I'm pretty sure the individual fields aren't properly parameterized, but rather than go through every field and try and fix it, I was looking for a faster solution by just tweaking the paginate_by_sql statement (I'm new to Rails and am just trying to make a quick fix since the whole app will eventually need upgrading.)
EDIT
I followed the steps in Rails, how to sanitize SQL in find_by_sql and added an initializer. I also changed my statement to the below. However, when I run my search, I get no results. I also had to delete the ascending condition because it threw an error. Any ideas are very welcome!
Initializer
class ActiveRecord::Base
def self.escape_sql(clause, *rest)
self.send(:sanitize_sql_array, rest.empty? ? clause : ([clause] + rest))
end
end
Controller
query = Location.escape_sql(["SELECT * from locations WHERE #{#search_string} = ?", params[:#search_string]])
#location_matches = Location.paginate_by_sql(query, :page => params[:page], :per_page => 20)
Log (Looks the same as the code that returns results)
Parameters: {"city"=>"New York", "commit"=>"Search", "search"=>{"size_category"=>"", "state"=>""}}
The short answer is: you really can't do this quickly.
I'd recommend updating the search-string builder to, instead of creating just a single string, to build the array-style set of finders eg: ["field = ?", param]
eg:
search_string = ""
search_vals = []
if params[:city].present?
search_string << "and city like ? "
search_vals << "%#{params[:city]}%"
end
if params[:whatever].present?
search_string << "and whatever like ? "
search_vals << "%#{params[:whatever]}%"
end
Then when you want to do the final search, you could use it like this:
search_string = "SELECT * from locations WHERE #{search_string}"
# turns it into the array-with question-mark syntax
search_query = [search_string] + search_vals
# if paginate_by_sql takes array, you can use it straight up here:
#location_matches = Location.paginate_by_sql(search_query, :page => params[:page], :per_page => 20)
# otherwise use your "escape_sql" variant first
#location_matches = Location.paginate_by_sql(Location.escape_sql(search_query), :page => params[:page], :per_page => 20)

rails 2.3 convert hash into mysql query

I'm trying to find out how rails converts a hash such as (This is an example please do not take this literally I threw something together to get the concept by I know this query is the same as User.find(1)):
{
:select => "users.*",
:conditions => "users.id = 1",
:order => "username"
}
Into:
SELECT users.* FROM users where users.id = 1 ORDER BY username
The closest thing I can find is ActiveRecord::Base#find_every
def find_every(options)
begin
case from = options[:from]
when Symbol
instantiate_collection(get(from, options[:params]))
when String
path = "#{from}#{query_string(options[:params])}"
instantiate_collection(format.decode(connection.get(path, headers).body) || [])
else
prefix_options, query_options = split_options(options[:params])
path = collection_path(prefix_options, query_options)
instantiate_collection( (format.decode(connection.get(path, headers).body) || []), prefix_options )
end
rescue ActiveResource::ResourceNotFound
# Swallowing ResourceNotFound exceptions and return nil - as per
# ActiveRecord.
nil
end
end
I'm unsure as to how to modify this to just return what the raw mysql statement would be.
So after a few hours of digging I came up with an answer although its not great.
class ActiveRecord::Base
def self._get_finder_options options
_get_construct_finder_sql(options)
end
private
def self._get_construct_finder_sql(options)
return (construct_finder_sql(options).inspect)
end
end
adding this as an extension gives you a publicly accessible method _get_finder_options which returns the raw sql statement.
In my case this is for a complex query to be wrapped as so
SELECT COUNT(*) as count FROM (INSERT_QUERY) as count_table
So that I could still use this with the will_paginate gem. This has only been tested in my current project so if you are trying to replicate please keep that in mind.

How should I write case statement depend of parameters key?

For example I have index action:
def index
if params[:query]
#pharmaceutics = Pharmaceutic.where("name LIKE ?", params[:query])
elsif params[:code]
#pharmaceutics = Pharmaceutic.where("barcode LIKE ?", params[:code])
else
#pharmaceutics = Pharmaceutic.all
end
end
And when I send two params: code and query I would like to filter my Pharmaceutics using both of them. I have MySQL database.
I would probably use scoped method, like this:
def index
scope = Pharmaceutic.scoped # Pharmaceutic.all if you use Rails 4
scope = scope.where('name LIKE ?', params[:query]) if params[:query].present?
scope = scope.where('barcode LIKE ?', params[:code]) if params[:code].present?
#pharmaceutics = scope
end
You can also write your custom scopes and replace where(...) with them to make the code clearer.

Query Between Two Tables Using Ruby

My code is this
contact = UserContact.find(:all,:select=>"distinct app_id,number",:conditions=>"number ='1234'")
arr=[]
contact.each do|c|
arr << c.app_id
end
name=User.find(:all,:conditions=>"id in(#{arr.join(',')}")
I takes two much time Can i do this using join
Thanks
You should do smth like this
User.find(:all, :joins => :user_contacts, :conditions => "user_contacts.number = '1234'")
user should have association. In user.rb should be:
has_many :user_contacts, :foreign_key => :app_id
But it is bad style to name column "app_id" it should be "user_id"(convention over configuration). Renamed it. After rename you can remove ", :foreign_key => :app_id"
Unfortunately you can NOT do "includes" and "select" in the same Active record Query. So this will not work...
contacts = UserContact.includes(:users).where("number = ?", '1234').select(:app_id, :number).uniq
BUT.
FIRST: app_id looks like it should be called "user_id"
SECOND: What version of Rails are you using? You might want to use the "pluck" method if you are using rails 3.2. Hence
user_ids = UserContact.where("number = ?", '1234').pluck(:app_id).uniq
users = User.where(:id => user_ids)
THIRD: in ruby instead of doing this:
arr=[]
contact.each do|c|
arr << c.app_id
end
do this:
arr = contact.inject([]) {|arr, c| arr << c.app_id}
FOURTH: As in all my examples your syntax is mostly Rails 2. I assume you are using rails 2? If so you might need to upgrade.
FIFTH: use plural variable names if you return more than one object. Hence
contact = UserContact.....
name=User.....
should be
contacts = UserContact.find.....
users = User.find.....
LAST:
User.joins(:user_contacts).where("user_contacts.number = ?", '1234')
might be good