Rails dynamic attribute in where LIKE clause - ruby-on-rails

I have a search method, which takes in a key value pair in argument and searches on an active record model via a LIKE query. But I am unable to get it to work. It doesn't take the key argument properly.
This is what my method looks like:
def search(key,value)
where('? LIKE ?',key,"%#{value}%")
end
The query it fires is ('name' LIKE '%air%') whereas it should fire (name LIKE '%air%')
Is there a way I could get this to work?

Warning: The solution proposed by #MKumar is very dangerous. If key is user-input, you just allowed SQL injection.
def search(key, value)
where("#{key} LIKE ?", "%#{value}%")
end
search("IS_ADMIN == 1 --", "")
Whoops!
The better way to do this would be to use Arel tables.
def search(key, value)
column = Model.arel_table[key.to_sym] # index into the columns, via a symbol
where(column.matches("%#{value}%"))
end
This cannot produce a SQL injection.

Try like this
def search(key,value)
where("#{key} LIKE ?","%#{value}%")
end

Related

Using column_names.include? to protect against SQL Injection?

I have a rails API that handles requests from my front end. These requests include query parameters in the url for refining and sorting results from the database. An example URL query looks like this:
http://localhost:8000/clients?_sort=name&_order=DESC&_start=0&_end=10
My index method in my controller grabs these params and uses them for filtering and sorting:
def index
#all_clients = Client.all
response.headers['X-Total-Count'] = #all_clients.count
if (Client.column_names.include?(params[:_sort]))
if (params[:_order] == 'ASC')
#clients = Client.filtered(params[:_start].to_i, params[:_end].to_i).order("#{params[:_sort]} asc")
else
#clients = Client.filtered(params[:_start].to_i, params[:_end].to_i).order("#{params[:_sort]} desc")
end
end
json_response(#clients || #all_clients)
end
the filtered method is a scope which looks like this:
scope :filtered, -> (_start, _end) { limit(_end-_start).offset(_start) }
My question is this: by using Client.column_names.include? to check if params[:_sort] is a valid attribute to sort by, am I effectively whitelisting against SQL Injection? If not, how could I alter this code to protect against SQL Injection?
The important thing to consider here is not "the whitelisting of params" (since you're already cherry-picking which parameters to use anyway, rather than blindly using the whole params hash for something), but rather how you are constructing the SQL.
There are two potential injection areas in the code:
limit(_end-_start)
Is this vulnerable? No. If _end or _start are anything besides integers, then the code will just fail with an error message - such as:
NoMethodError: undefined method `-' for "DROP_TABLE":String
or
ArgumentError: invalid value for Integer(): 3.14159
order("#{params[:_sort]} desc")
Is this vulnerable? Yes. (But not easily.) This page gives a concrete example:
params[:_sort] = "(CASE SUBSTR(password, 1, 1) WHEN 's' THEN 0 else 1 END)"
You should never use direct string interpolation in SQL, unless you are absolutely 100% sure that the string is "safe". In this case, you could just write it as:
order(params[:_sort] => :asc)

rails dynamic where sql query

I have an object with a bunch of attributes that represent searchable model attributes, and I would like to dynamically create an sql query using only the attributes that are set. I created the method below, but I believe it is susceptible to sql injection attacks. I did some research and read over the rails active record query interface guide, but it seems like the where condition always needs a statically defined string as the first parameter. I also tried to find a way to sanitize the sql string produced by my method, but it doesn't seem like there is a good way to do that either.
How can I do this better? Should I use a where condition or just somehow sanitize this sql string? Thanks.
def query_string
to_return = ""
self.instance_values.symbolize_keys.each do |attr_name, attr_value|
if defined?(attr_value) and !attr_value.blank?
to_return << "#{attr_name} LIKE '%#{attr_value}%' and "
end
end
to_return.chomp(" and ")
end
Your approach is a little off as you're trying to solve the wrong problem. You're trying to build a string to hand to ActiveRecord so that it can build a query when you should simply be trying to build a query.
When you say something like:
Model.where('a and b')
that's the same as saying:
Model.where('a').where('b')
and you can say:
Model.where('c like ?', pattern)
instead of:
Model.where("c like '#{pattern}'")
Combining those two ideas with your self.instance_values you could get something like:
def query
self.instance_values.select { |_, v| v.present? }.inject(YourModel) do |q, (name, value)|
q.where("#{name} like ?", "%#{value}%")
end
end
or even:
def query
empties = ->(_, v) { v.blank? }
add_to_query = ->(q, (n, v)) { q.where("#{n} like ?", "%#{v}%") }
instance_values.reject(&empties)
.inject(YourModel, &add_to_query)
end
Those assume that you've properly whitelisted all your instance variables. If you haven't then you should.

How to concat a where clause only if id is not null

I have a method which returns an ActiveRecord_Relation and I would like to concat a whereclause if the id received in params is not null. Something like this
query.where(item_id: params[:item_id]) # only if params[:item_id] is not null.
query # only if params[:item_id] is null.
It would be perfect a query like the first one:
query.where(item_id: params[:item_id])
in which, a nil value invalidates the whole where instead of searching an item_id with nil id.
Of course I can do this using a conditional. But I would like to know if there is a rails way to do this. A one-liner or something
Thanks!
Use a scope in your model - that's what they're for, to scope queries on models. Scopes always return a query, so you can do something like the following (assuming the model you're querying is called something like Product):
class Product < ActiveRecord::Base
scope :by_item_id, -> (item_id) { where(item_id: item_id) if item_id }
end
Then you can call Product.by_item_id(params[:item_id]).
Writing scope is one of the best thing if you would be repeating the query.
If not you can simply do:
query.where(item_id: params[:item_id]) if params[:item_id]
If you prefer not to use a scope, a one-liner can be accomplished with the then method:
query.then{|q| params[:item_id].blank? ? q : q.where(item_id: params[:item_id])}
Optional explanation
The value of query is passed to the block as q. The ternary statement determines whether to simply return the query q or concatenate the additional where clause. Then the return value of the block becomes the value of the overall expression.
This is conceptually equivalent to procedural code, like this:
q = query
if params[:item_id].blank?
q
else
q.where(item_id: params[:item_id])
end
The benefit of then is that it keep the code inline, and multiple thens can be concatenated together:
query.
.then{|q| params[:item_id].blank? ? q : q.where(item_id: params[:item_id])}
.then{|q| params[:item2_id].blank? ? q : q.where(item2_id: params[:item2_id])}
.then{|q| params[:item3_id].blank? ? q : q.where(item3_id: params[:item3_id])}
Object#then became available in Ruby 2.6 and is an alias for yield_self, available since Ruby 2.5.

Getting the name of Ruby method for a literal hash query

In a rails application, I have a number of attributes for a model called Record. I want to design a method that when called on an attribute, returns the name of the attribute (which is essentially a method on the Record object). This name is then passed to an Hash, which returns a number (for the sake of this example, say the number is a percentage which is then multiplied by the original attribute value to get a new value).
For example, say my Record has four attributes: teachers, students, principals, and parents. The method would then look like the following:
def name
**something here**
end
and the corresponding new_value method and PRECENTAGE hash would look like this:
def new_value
self * PERCENTAGE[self.name]
end
PERCENTAGE = {
"teachers" => 0.40,
"students" => 0.53,
"principals" => 0.21,
"parents" => 0.87
}
Then, to execute this whole thing, I would do Record.students.new_value, which would return new number of students according to the percentage obtained in the hash.
I know that to get the name of a method that is currently executing, you can do something like this: (found on http://ryat.la/7RDk)
def this_method
__method__
end
but that won't work for me, because I need the name of the previously executed method.
If you have any suggestions as to an alternative approach to accomplishing my goal, I'd be happy to try something else.
Ryan, I'm struggling to understand your question, but I think this is what you want, for record.teachers_percent, for example:
["teachers", "students", "principals", "parents"].each do |attrib|
Record.class_eval <<-RUBY
def #{attrib}_percent
#{attrib} * PERCENTAGE[#{attrib.inspect}]
end
RUBY
end
Although this is probably a cleaner solution, giving record.percent(:teachers) or record.percent("teachers"):
class Record
def percent(attrib)
self.send(attrib) * PERCENTAGE[attrib.to_s]
end
end

Rails - Conditional Query, with ActiveRecord?

Given a query like:
current_user.conversations.where("params[:projectid] = ?", projectid).limit(10).find(:all)
params[:projectid] is being sent from jQuery ajax. Sometimes that is an integer and the above works fine. But if the use selects "All Projects, that's a value of '' which rails turns into 0. which yields an invalid query
How with rails do you say search params[:projectid] = ? if defined?
Thanks
I think you may have mistyped the query a bit. "params[:projectid] = ?" shouldn't be a valid query condition under any circumstances.
In any case, you could do some sort of conditional statement:
if params[:project_id].blank?
#conversations = current_user.conversations.limit(10)
else
#conversations = current_user.conversations.where("project_id = ?", params[:project_id]).limit(10)
end
Although, I'd probably prefer something like this:
#conversations = current_user.conversations.limit(10)
#converstaions.where("project_id = ?", params[:project_id]) unless params[:project_id].blank?
Sidenotes:
You don't have to use .find(:all). Rails will automatically execute the query when the resultset is required (such as when you do #conversations.each).
Wherever possible, try to adhere to Rails' snakecasing naming scheme (eg. project_id as opposed to projectid). You'll save yourself and collaborators a lot of headaches in the long run.
Thanks but if the where query has lets say 3 params, project_id, project_status, ... for example, then the unless idea won't work. I'm shocked that Rails doesn't have a better way to handle conditional query params
EDIT: If you have multiple params that could be a part of the query, consider the fact that where takes a hash as its argument. With that, you can easily build a parameter hash dynamically, and pass it to where. Something like this, maybe:
conditions = [:project_id, :project_status, :something_else].inject({}) do |hsh, field|
hsh[field] = params[field] unless params[field].blank?
hsh
end
#conversations = current_user.conversations.where(conditions).limit(10)
In the above case, you'd loop over all fields in the array, and add each one of them to the resulting hash unless it's blank. Then, you pass the hash to the where function, and everything's fine and dandy.
I didn't understand why you put:
where("params[:projectid] = ?", projectid)
if you receive params[:project] from the ajax request, the query string shouldn't be:
where("projectid = ?", params[:projectid])
intead?
And if you are receiving an empty string ('') as the parameter you can always test for:
unless params[:projectid].blank?
I don't think i undestood your question, but i hope this helps.

Resources