I have a problem trying to work with a NOT IN query (using Rails 4/Postgres, for reference) in an elegant way. I'm trying to get a list of all objects of a certain model that don't show up in a join table for a certain instance. It works , when you try a NOT IN query with an empty array, it throws an error because you can't look for NOT IN NULL.
The below code now works, but is there a better way than to use an unintuitive conditional to make a pseudo-null object?
def characters_selected
self.characters_tagged.pluck(:name)
end
def remaining_characters
characters = self.characters_selected
characters = ["SQL breaks if this is null"] if characters.empty?
# this query breaks on characters_selected == [] without the above line
Character.where("name NOT IN (?)", characters )
end
This is the ActiveRecord way:
def remaining_characters
characters = self.characters_selected
Character.where.not(:name => characters)
end
When characters.empty? the where clause becomes "WHERE (1=1)".
Related
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.
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
Here is the query I am trying in my controller
query = []
if id
query = "category_id: #{id}"
end
#posts = Post.where(query)
But throwing error as ERROR: syntax error at or near ":"
Why this is not working any other way to do it
if id
query << {sub_category_id: id}
end
if test
query << {test_id: test}
end
#posts = Post.where(query)
Is there any way of doing like this
Change query to a hash instead of string:
if id
query = { category_id: id }
end
#posts = Post.where(query)
The reason query = "category_id: #{id}" did not work is because the supplied string is literally used in the query generated by ActiveRecord, i.e. your select query will have category_id: 1 (assuming id is 1) in the where clause. And this is not a valid SQL syntax.
Please read on how you can use strings in conditions following this link. Thanks to #RustyToms for suggesting the link.
Update: ( Add extra conditions to the query hash )
if id
query[:sub_category_id] = id
end
if test
query[:test_id] = test
end
#posts = Post.where(query)
Another way to do this:
#posts = Post.scoped
#posts = #posts.where(category_id: id) if id
(in case you're playing codegolf)
Edit: (this is definitely a side note that isn't at all relevant)
Your original solution relies on one of my least favorite features of Ruby. Consider the following code:
if false
a = 4
end
puts a
I would expect the puts a to fail with a NameError (undefined local variable "a"), but no! The Ruby parser hits a = and then initalizes its value to nil. So, despite the fact that there is no way for the innards of that if statement to run, it still impacts the other code.
I have a table in Rails and I would like to find all records of a table where a certain function returns true.
What is the best way to do this? I could of course iterate over all the records in the table and use conditional statements to test whether the function, given the individual record, returns true and add it to a list.
Is there any easier way of doing this something along the lines of Model.find(:all, :conditions => {...}) maybe?
Thanks
Class MyModel < ActiveRecord
def self.targetted
find_each.select(&:predicate_method?)
end
def predicate_method?
#something that returns either true or false
end
end
this is a bit more Rails idiomatic :
find_each will fetch your record by batches of 1000. it is better than all for your memory
&:predicate_method : transforming a symbol into a Proc (with the # operator) will actually make your code call the method on each of the passed objects
def record_that_returns_true_for_xfunction
Model.all.select {|record| xfunction(record.some_column) == true}
end
This is seems like what you are looking for. This method will return an array of all the records where xfunction(record.some_column) == true.
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.