rails order parameterized query - sql

I know that order is not safe, so I want to refactor this code:
#tasks = #search.result.joins(user_application_status: {student_application: [student_profile: :student]})
.order(sort_column + ' ' + sort_direction).page(params[:page])
sort_column is reading from params directly and would be something like user_application_tasks.name and sort_direction would return somethig like asc, I tried refactoring it to:
.order("? ?", sort_column, sort_direction).page(page_params)
but I am getting an error
ActiveRecord::StatementInvalid - PG::SyntaxError: ERROR: syntax error at or near ","
LINE 1: ...HERE (application_statuses.id = 137) ORDER BY ? ?, user_app...
I have done this sort of thing before with where statements like
Thing.where("state = ?" ,params[:state])
Is there some special syntax I am omitting?
EDIT:
The thing I am most worried about is someone being able to inject sql here and do something harmful, as #spickermann mentioned order doesn't sanitize the data so
Thing.order("name; drop table users;")
will result in the users table being destroyed.

order doesn't sanitize attributes when they are provided in a list like where does.
But is accepts as hash like this:
order(sort_column => sort_direction)
See the Rails Guides About Ordering.

Related

? in ActiveRecord select

In a where statement, you can use variables like:
Order.where('employee_id = ?', params[:employee_id])
I'm trying to accomplish something similar with a select, but it's not working:
Order.select('amount FROM line_items WHERE employee_id = ? AS employee_line_items', params[:employee_id])
=> ERROR: syntax error at or near "1"
=> LINE 1: ...ployee_id" = ? AS employee_line_items, 1
What's going on here? Is it possible to use ? in select statement? If not, how can you insert an escaped sql string here? I'd like to just use #{params[:employee_id]}, but this bit of code would be vulnerable to sql injection.
You have to split your query and chain it:
Order.select('amount FROM line_items').where(['WHERE employee_id = ?', params[:employee_id]])
And also based on this question, I believe you cannot use AS in WHERE clause, only when selecting fields (and you can't use them in WHERE in any case)
Check the documentation to understand how select works on ActiveRecord models

Rails query to SQL statement

I'm trying to write an write this:
Team.last.players.sum("goals")
erb:
SELECT SUM("players"."goals")
FROM "players"
WHERE "players"."team_id" = $1 [["team_id", 2]]
how to rewrite this so that I could use it in a method:
def sql_search
sql = "SELECT SUM \"players\".\"goals\" FROM \"players\" WHERE \"players\".\"team_id\" = $1 [[\"team_id\", #{self.id}"
connection.execute(sql);
end
keep getting this error:
PG::SyntaxError: ERROR: syntax error at or near "."
LINE 1: SELECT SUM "players"."goals" FROM "players" WHERE "players"....
Any ideas would be appreciated
You don't need to add \" in sql statement, just remove them.
def sql_search
sql = "SELECT sum(goals) FROM players WHERE team_id = #{self.id};"
connection.execute(sql);
end
Is there some reason that you want to hard code the SQL query? It's generally bad practice to use string interpolation to insert parameters to SQL queries because of SQL injection attacks. Instead it's recommended to use ActiveRecord's SQL query parameter binding like this:
user_input = 5
Player.where('team_id = ?', user_input).sum(:goals)
Basically what this does is insert the parameter 5 after sanitization. This means you're safe from attacks where a hacker attempts to insert arbitrary SQL into parameter variables attempting to return sensitive data or delete data entirely!

Protecting against sql injection using activerecord

Following on the question how can I use like query in ruby with sinatra? I have the following problem securing my sql from injection.Here is my method to make a query from the type string, it receives a v(alue) to search for and a k(ey) (=field) to look in.
After that the various selctions are joined by selection.join(' and ')
def string_selector(k, v)
case
when v[/\|/]
v.scan(/([^\|]+)(\|)([^\|]+)/).map {|p| "lower(#{k}) LIKE '%#{p.first.downcase}%' or lower(#{k}) LIKE '%#{p.last.downcase}%'"}
when v[/[<>=]/]
v.scan(/(<=?|>=?|=)([^<>=]+)/).map { |part| p part; "#{k} #{part.first} '#{part.last.strip}'"}
else
# "lower(#{k}) LIKE '%#{v.downcase}%'" #(works)
("lower(#{k}) LIKE ?", '%#{v.downcase}%') #doesn't work
end
end
But i get the error
selectors.rb:38: syntax error, unexpected keyword_end, expecting $end
from C:/../1.9.1/rubygems/core_ext/kernel_require.rb:55:in `require'
What could i be doing wrong ?
There's got to be a better way to do what you are trying to do if you are using ActiveRecord... However, if you need to support your string_selector functionality for some reason, I would at least use Arel:
def string_selector(k, v)
tbl = Arel::Table.new(:test) # your table, or you could pass this in...
condition = case v
when /\|/
vals = v.split(/\|/)
first = vals.shift
vals.inject(tbl[k].matches("%#{first.strip}%")) do |acc, val|
acc.or(tbl[k].matches("%#{val.strip}%"))
end
when /<>/
tbl[k].not_eq(v.gsub(/<>/, '').strip)
when /\=/
tbl[k].eq(v.gsub(/\=/, '').strip)
else
tbl[k].matches(v.strip)
end
tbl.where(condition).to_sql
end
Please note that matches will perform a case insensitive query for you (e.g., by using ILIKE in PostgreSQL).

ORDER BY after a WHERE with a escape_string within a sql SELECT

I have the following select query, where I need to add an ORDER BY topic_id DESC. How can I add it?
$sql = "SELECT
top_id,
top_cat,
COUNT(top_id) AS topic
FROM
topics
WHERE
top_cat = ". mysql_real_escape_string($_GET['id']);
I cannot add the ORDER BY right before the last semicolon (;) since it will get an error and, of course, it cannot be placed right before the quotes ("). I also tried to place it before WHERE but again, shows errors later when I introduce if ($row = mysql_fetch_assoc($result)).
Any ideas?
Concat the " order by topic_id desc" before the semi-colon, just like you concat the ID. If that's what you were doing when you say "you can't place it right before the semicolon" I don't know why not.
It's easier to diagnose code errors if you show the code that created the errors.

How to specify multiple values in where with AR query interface in rails3

Per section 2.2 of rails guide on Active Record query interface here:
which seems to indicate that I can pass a string specifying the condition(s), then an array of values that should be substituted at some point while the arel is being built. So I've got a statement that generates my conditions string, which can be a varying number of attributes chained together with either AND or OR between them, and I pass in an array as the second arg to the where method, and I get:
ActiveRecord::PreparedStatementInvalid: wrong number of bind variables (1 for 5)
which leads me to believe I'm doing this incorrectly. However, I'm not finding anything on how to do it correctly. To restate the problem another way, I need to pass in a string to the where method such as "table.attribute = ? AND table.attribute1 = ? OR table.attribute1 = ?" with an unknown number of these conditions anded or ored together, and then pass something, what I thought would be an array as the second argument that would be used to substitute the values in the first argument conditions string. Is this the correct approach, or, I'm just missing some other huge concept somewhere and I'm coming at this all wrong? I'd think that somehow, this has to be possible, short of just generating a raw sql string.
This is actually pretty simple:
Model.where(attribute: [value1,value2])
Sounds like you're doing something like this:
Model.where("attribute = ? OR attribute2 = ?", [value, value])
Whereas you need to do this:
# notice the lack of an array as the last argument
Model.where("attribute = ? OR attribute2 = ?", value, value)
Have a look at http://guides.rubyonrails.org/active_record_querying.html#array-conditions for more details on how this works.
Instead of passing the same parameter multiple times to where() like this
User.where(
"first_name like ? or last_name like ? or city like ?",
"%#{search}%", "%#{search}%", "%#{search}%"
)
you can easily provide a hash
User.where(
"first_name like :search or last_name like :search or city like :search",
{search: "%#{search}%"}
)
that makes your query much more readable for long argument lists.
Sounds like you're doing something like this:
Model.where("attribute = ? OR attribute2 = ?", [value, value])
Whereas you need to do this:
#notice the lack of an array as the last argument
Model.where("attribute = ? OR attribute2 = ?", value, value) Have a
look at
http://guides.rubyonrails.org/active_record_querying.html#array-conditions
for more details on how this works.
Was really close. You can turn an array into a list of arguments with *my_list.
Model.where("id = ? OR id = ?", *["1", "2"])
OR
params = ["1", "2"]
Model.where("id = ? OR id = ?", *params)
Should work
If you want to chain together an open-ended list of conditions (attribute names and values), I would suggest using an arel table.
It's a bit hard to give specifics since your question is so vague, so I'll just explain how to do this for a simple case of a Post model and a few attributes, say title, summary, and user_id (i.e. a user has_many posts).
First, get the arel table for the model:
table = Post.arel_table
Then, start building your predicate (which you will eventually use to create an SQL query):
relation = table[:title].eq("Foo")
relation = relation.or(table[:summary].eq("A post about foo"))
relation = relation.and(table[:user_id].eq(5))
Here, table[:title], table[:summary] and table[:user_id] are representations of columns in the posts table. When you call table[:title].eq("Foo"), you are creating a predicate, roughly equivalent to a find condition (get all rows whose title column equals "Foo"). These predicates can be chained together with and and or.
When your aggregate predicate is ready, you can get the result with:
Post.where(relation)
which will generate the SQL:
SELECT "posts".* FROM "posts"
WHERE (("posts"."title" = "Foo" OR "posts"."summary" = "A post about foo")
AND "posts"."user_id" = 5)
This will get you all posts that have either the title "Foo" or the summary "A post about foo", and which belong to a user with id 5.
Notice the way arel predicates can be endlessly chained together to create more and more complex queries. This means that if you have (say) a hash of attribute/value pairs, and some way of knowing whether to use AND or OR on each of them, you can loop through them one by one and build up your condition:
relation = table[:title].eq("Foo")
hash.each do |attr, value|
relation = relation.and(table[attr].eq(value))
# or relation = relation.or(table[attr].eq(value)) for an OR predicate
end
Post.where(relation)
Aside from the ease of chaining conditions, another advantage of arel tables is that they are independent of database, so you don't have to worry whether your MySQL query will work in PostgreSQL, etc.
Here's a Railscast with more on arel: http://railscasts.com/episodes/215-advanced-queries-in-rails-3?view=asciicast
Hope that helps.
You can use a hash rather than a string. Build up a hash with however many conditions and corresponding values you are going to have and put it into the first argument of the where method.
WRONG
This is what I used to do for some reason.
keys = params[:search].split(',').map!(&:downcase)
# keys are now ['brooklyn', 'queens']
query = 'lower(city) LIKE ?'
if keys.size > 1
# I need something like this depending on number of keys
# 'lower(city) LIKE ? OR lower(city) LIKE ? OR lower(city) LIKE ?'
query_array = []
keys.size.times { query_array << query }
#['lower(city) LIKE ?','lower(city) LIKE ?']
query = query_array.join(' OR ')
# which gives me 'lower(city) LIKE ? OR lower(city) LIKE ?'
end
# now I can query my model
# if keys size is one then keys are just 'brooklyn',
# in this case it is 'brooklyn', 'queens'
# #posts = Post.where('lower(city) LIKE ? OR lower(city) LIKE ?','brooklyn', 'queens' )
#posts = Post.where(query, *keys )
now however - yes - it's very simple. as nfriend21 mentioned
Model.where(attribute: [value1,value2])
does the same thing