How can I get ActiveRecord to bind in raw statements? - ruby-on-rails

So I'm basically doing this:
::OuterModel.where(%{
EXISTS(SELECT * FROM inner_model
WHERE outer_model.id = inner_model.outer_model_id)
AND inner_model.parameter = ?)
}, 1)
Now the issue becomes that this does text replacement in ActiveRecord, it doesn't bind ? to 1, which in turn is rendering ActiveRecords prepared statements pretty meaningless since every query has a different value of 1.
How can I get bind on my EXIST statements?
This is also of course true when doing something simple like:
::OuterModel.where('state = ?', 'active')
The alternative here isn't to do .join or generate IN, the performance of that is much worse, or wouldn't work in my actual use-case.

If you sure that this param is safe and you did not receive it from user side - then you just can use string interpolation #{1}. If not - you can use interpolation with sanitize that value #{ActiveRecord::Base.connection.quote(1)}

In my particular case I'm able to achieve this using arel.exists, rails will then prepare and bind with the OuterModel query.
inner_models = ::InnerModel.where("outer_model.id = inner_model.outer_model_id")
.where(paramter: 1)
::OuterModel.where(inner_models.arel.exists)
Although it doesn't really answer the in-general question on binding against raw SQL.

Related

What is the best possible way to avoid the sql injection?

I am using ruby 1.8.7 and rails 2.3.2
The following code is prone to sql injection
params[:id] = "1) OR 1=1--"
User.delete_all("id = #{params[:id]}")
My question is by doing the following will be the best solution to avoid sql injection or not. If not then what is the best way to do so?
User.delete_all("id = #{params[:id].to_i}")
What about:
User.where(id: params[:id]).delete_all
Ok sorry for Rails 2.x its:
User.delete_all(["id = ?", params[:id]])
Check doc
Btw, be sure you want to use delete_all instead of destroy_all, the former doesn't trigger callbacks.
You can use this also
User.delete(params[:id])
The other answers answer this well for Rails and it'll work fine if you follow their suggestions. In a more generic setting when you have to handle this yourself you can typically use a regular expression to extract a value that's in an expected format. This is really simple with an integer id. Think of it like this:
if params[:id] =~ /(\d+)/
safe_id = $1.to_i
# do something with safe_id now
end
That gets a little more complicated when you're handling strings and arbitrary data. If you have to handle such data then you can use the quoting methods available for the database adapters. In Rails this is ultimately rolled into a consistent interface:
safe_string = ActiveRecord::Base.connection.quote(unsafe_string)
For most database systems this will handle single quotes and backslashes in a special manner.
If you're outside of Rails you will have to use the quoting methods specific to your database adapter, but usage is quite similar.
The takeaway:
If your data has a particular format, enforce the format with a regular expression
Otherwise, use your database adapter's quoting function to make the data "safe" for use in a query
Rails will handle most of this for you if you properly use the various methods and "conditions"
Use the rails methods to pass your where options. You can always hardcode them, as in the example that you give, but the usual way would be something like:
User.where(:id => params[:id]).delete_all
User.where("id = ?", params[:id]).delete_all
User.where("id = :id", :id => params[:id]).delete_all
They are well tested and in case a new vulnerability is detected, an update will fix the problem and your code will not need to be changed.
By the way, if you just want to delete 1 record based on its id, what I would do is:
User.find(params[:id]).destroy

Adding additional conditions to find_by_id in Rails 3

I'm using Rails 3.2.11 on a Mac
I have this statement that works fine:
object= Object.find_by_id(params[:id])
I'm trying to add a condition to that statement so I did this:
object = Object.where("id = :id AND level <= :level",{:id => params[:id], :level => current_user.level})
Will there be any risk in this method? Any alternatives?
There's no risk presented by this statement, provided that ActiveRecord continues to uphold the contract of sanitizing input. An alternative would be a scope, but that's really just doing the same thing in a different syntax.
One thing you could do is set a default scope that defines the level restriction, then you could just do a standard find_by_id. But if that's undesirable, just use the syntax properly:
Object.where(id: params[:id], level: current_user.level)
There is no risk but the way it is defined is not easy to understand.
The simple statement will work:
object = Object.where("id = ? AND level <= ?",{params[:id], current_user.level})
as long as you let rails handle the sanitizing of your values, you'll have no issues. what i mean by this is run a sql command from rails using user input like
Object.where("id = #{params[:id]} AND level <= #{current_user.level}")
will be vulnerable to sql injection

Using Rails ActiveRecord to Query Unsaved Objects

Is it possible to query unsaved changes using Rail's ActiveRecord or another similar approach?
An example of a Ruby interactive session is below. What I'd like to see, is the fourth line show a result of '999' instead of '10'. I'm use to using .NET and Entity Framework where something similar to this was possible. Perhaps in Ruby there is a better or different way to do the same thing. I could obviously add up the sum in a loop, but I find the query syntax more elegant. Any help is appreciated.
i = Inventory.where(:product_id => 1)
i.sum(:available) => 10
i.first.available = 999
i.sum(:available) => 10
No, since sum() is actually translated to SQL and run on the db, you must save the record to the db in order for the query to return the result you want.
Alternatively, you can use the Enumerable#sum method in ActiveSupport, which takes a block, like so:
all = Inventory.where(:product_id => 1).to_a
all.first.available = 999
all.sum(&:available)

Rails: Getting column value from query

Seems like it should be able to look at a simple tutorial or find an aswer with a quick google, but I can't...
codes = PartnerCode.find_by_sql "SELECT * from partner_codes where product = 'SPANMEX' and isused = 'false' limit 1"
I want the column named code, I want just the value. Tried everything what that seems logical. Driving me nuts because everything I find shows an example without referencing the actual values returned
So what is the object returned? Array, hash, ActiveRecord? Thanks in advance.
For Rails 4+ (and a bit earlier I think), use pluck:
Partner.where(conditions).pluck :code
> ["code1", "code2", "code3"]
map is inefficient as it will select all columns first and also won't be able to optimise the query.
You need this one
Partner.where( conditions ).map(&:code)
is shorthand for
Partner.where( conditions ).map{|p| p.code}
PS
if you are often run into such case you will like this gem valium by ernie
it gives you pretty way to get values without instantiating activerecord object like
Partner.where( conditions ).value_of :code
UPDATED:
if you need access some attribute and after that update record
save instance first in some variable:
instance=Partner.where( conditions ).first
then you may access attributes like instance.code and update some attribute
instance.update_attribute || instance.update_attributes
check documentation at api.rubyonrails.org for details

Arel: How to cleanly join multiple conditions with OR?

In my Rails app, I loop through an array to create a list of conditions that must be joined by OR. Below is the basic flow of how I currently do so.
conditions = nil
set.each do |value|
condition = value.to_condition
conditions = conditions ? conditions.or(condition) : condition
end
Obviously, it's not beautiful, but I still don't fully know my way around Arel. Does it offer any better way of OR-joining a set of dynamically-generated conditions?
This is a perfect fit for an inject which will give you a one-liner you can use within something else as well:
conditions = set.inject { |conds, cond| conds.or(cond) } which can even be written: set.inject(&:or) which is very nice.
There is also a useful plugin for this.
conditions_helper
It helps to generate complex conditions.
I think that's basically it. I'd initialize conditions to the base object to avoid the ternary:
scope = Article
set.each{|v| scope = scope.or(v.to_condition)}

Resources