I have a User model with the attributes username, email and name.
username and email are required upon signup, but not name.
What would be the query to find all users that have filled out name (i.e. it is no nil)?
The query should be at least Rails 3.2 and 4.0 compatible.
I'm thinking something in the lines of:
User.where(name: present?)
[UPDATED 13/5/2022]
To get all records where an attribute is not present in SQL, we would write
WHERE attr IS NULL or attr = ''
an easy mistake to make is then to negate this and write
WHERE attr is not null and attr != ''
However in SQL this equates to writing
WHERE attr != ''
since the NULL value is always ignored when using the equality operator.
So this translates to rails as follows:
User.where.not(name: '')
[MY OLD ANSWER]
An empty value in a database gets a special value NULL. Testing whether is set uses the special comparator IS NULL or IS NOT NULL.
Then there still remains the possibility that an empty string was filled in, so a complete test would be
#users = User.where("name is NOT NULL and name != ''")
[UPDATED for rails 4+]
Since rails 4 we can write:
User.where.not(name: [nil, ""])
which will generate the same query. Awesome :)
present?
present? is essentially not nil and not empty?:
class Object
def present?
!blank?
end
def blank?
respond_to?(:empty?) ? !!empty? : !self
end
end
ActiveRecord condition
In Rails 4, not conditions can be done without raw sql code.
# Both lines lead to the same result:
User.where.not(name: [nil, ""])
User.where.not(name: nil).where.not(name: "")
Since there is no raw sql code, you don't have to worry about if they work with every database adapter. In fact, this works fine for both, mysql and postgres.
to_sql
You can see how they translate to sql queries if you append .to_sql, for example in the rails console.
# rails console
User.where.not(name: [nil, ""]).to_sql
# => "SELECT \"users\".* FROM \"users\" WHERE (NOT ((\"users\".\"name\" = '' OR \"users\".\"name\" IS NULL)))"
User.where.not(name: nil).where.not(name: "").to_sql
# => "SELECT \"users\".* FROM \"users\" WHERE (\"users\".\"name\" IS NOT NULL) AND (\"users\".\"name\" != '')"
Further Reading
[1] Rails 4 guide "ActiveRecord Query Interface"
[2] Definition of present? on github
NOT SQL queries can be built by where.not
#users = User.where.not(name: nil)
Try this:
User.where("name IS NOT NULL AND name != ?", "")
I edited my answer as per #nathavanda comments, which his answer in my opinion should be the accepted one.
You can simply do this:
User.where.not(name: '')
Because of the nature of not, it won't include records where name is nil nor empty string. See this article for more details about not and nil
Related
I'm trying to rewhere or unscope a query, where the original condition cannot be written using hash condition:
Reservation.where('block_id IS NULL OR block_id != ?', 'something')
> SELECT `reservations`.* FROM `reservations` WHERE (block_id IS NULL OR block_id != 'something')
Trying to rewhere doesn't work:
Reservation.where('block_id IS NULL OR block_id != ?', 'something').rewhere(block_id: 'anything')
> SELECT `reservations`.* FROM `reservations` WHERE (block_id IS NULL OR block_id != 'something') AND `reservations`.`block_id` = 'anything'
But this example with hash condition would work:
Reservation.where.not(block_id: 'something').rewhere(block_id: 'anything')
> SELECT `reservations`.* FROM `reservations` WHERE `reservations`.`block_id` = 'anything'
I understand that this is probably because on the array condition rails doesn't know which column I'm invoking a where, and therefore rewhere won't find anything to replace.
Is there any way to explicitly tell which column I'm filtering in an array condition? or rewrite the first query (IS NULL OR != value) with hash condition?
Note: Please don't suggest unscoped, as I'm trying to unscope/rewhere only this specific condition, not the whole query.
Thanks!
Sorry it wasn't clear that you had other where clauses that you wanted to keep. You could access the array of where clauses using relations.values[:where] and manipulate it, something like:
Reservation.where('block_id IS NULL OR block_id != ?', 'something')
.tap do |relation|
# Depending on your version of Rails you can do
where_values = relation.where_values
# Or
where_values = relation.values[:where]
# With the first probably being better
where_values.delete_if { |where| ... }
end
.where(block_id: 'anything')
aka hacking
Is there a way to sanitize sql in rails method find_by_sql?
I've tried this solution:
Ruby on Rails: How to sanitize a string for SQL when not using find?
But it fails at
Model.execute_sql("Update users set active = 0 where id = 2")
It throws an error, but sql code is executed and the user with ID 2 now has a disabled account.
Simple find_by_sql also does not work:
Model.find_by_sql("UPDATE user set active = 0 where id = 1")
# => code executed, user with id 1 have now ban
Edit:
Well my client requested to make that function (select by sql) in admin panel to make some complex query(joins, special conditions etc). So I really want to find_by_sql that.
Second Edit:
I want to achieve that 'evil' SQL code won't be executed.
In admin panel you can type query -> Update users set admin = true where id = 232 and I want to block any UPDATE / DROP / ALTER SQL command.
Just want to know, that here you can ONLY execute SELECT.
After some attempts I conclude sanitize_sql_array unfortunatelly don't do that.
Is there a way to do that in Rails??
Sorry for the confusion..
Try this:
connect = ActiveRecord::Base.connection();
connect.execute(ActiveRecord::Base.send(:sanitize_sql_array, "your string"))
You can save it in variable and use for your purposes.
I made a little snippet for this that you can put in initializers.
class ActiveRecord::Base
def self.escape_sql(array)
self.send(:sanitize_sql_array, array)
end
end
Right now you can escape your query with this:
query = User.escape_sql(["Update users set active = ? where id = ?", true, params[:id]])
And you can call the query any way you like:
users = User.find_by_sql(query)
Slightly more general-purpose:
class ActiveRecord::Base
def self.escape_sql(clause, *rest)
self.send(:sanitize_sql_array, rest.empty? ? clause : ([clause] + rest))
end
end
This one lets you call it just like you'd type in a where clause, without extra brackets, and using either array-style ? or hash-style interpolations.
User.find_by_sql(["SELECT * FROM users WHERE (name = ?)", params])
Source: http://blog.endpoint.com/2012/10/dont-sleep-on-rails-3-sql-injection.html
Though this example is for INSERT query, one can use similar approach for UPDATE queries. Raw SQL bulk insert:
users_places = []
users_values = []
timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S')
params[:users].each do |user|
users_places << "(?,?,?,?)" # Append to array
users_values << user[:name] << user[:punch_line] << timestamp << timestamp
end
bulk_insert_users_sql_arr = ["INSERT INTO users (name, punch_line, created_at, updated_at) VALUES #{users_places.join(", ")}"] + users_values
begin
sql = ActiveRecord::Base.send(:sanitize_sql_array, bulk_insert_users_sql_arr)
ActiveRecord::Base.connection.execute(sql)
rescue
"something went wrong with the bulk insert sql query"
end
Here is the reference to sanitize_sql_array method in ActiveRecord::Base, it generates the proper query string by escaping the single quotes in the strings. For example the punch_line "Don't let them get you down" will become "Don\'t let them get you down".
I prefer to do it with key parameters. In your case it may looks like this:
Model.find_by_sql(["UPDATE user set active = :active where id = :id", active: 0, id: 1])
Pay attention, that you pass ONLY ONE parameter to :find_by_sql method - its an array, which contains two elements: string query and hash with params (since its our favourite Ruby, you can omit the curly brackets).
Rails 2.35
I'm may be wrong but I thought with an array in a paramater, rails was suppose to comman seperate the array when used like below for a query. I know I can break the param out into a single quoted and comma seperated string. I was just curious is this can be automatically done by Rails and how I might go about it if so. Thank You
Parameters being sent:
Parameters: {"method"=>:get, "id"=>["3", "1", "4"]}
The SQL statement in the controller I'm using:
sql = "SELECT user.user_alias from users " +
"where user.id in (#{params[:id]}) " +
"AND user.user_alias is NOT NULL "
aliases = User.find_by_sql(sql)
The SQL string Rails outputs (the query results in the IN statement are just all togather '314):
SELECT User.user_alias
from lte_users
where user.id in (314)
AND user.user_alias is NOT NULL
NEVER, never, do string concatenation in a SQL query, as someone might use this to perform an SQL Injection attack on your webapp.
You should be doing it like this:
sql = %Q{SELECT user.user_alias from users
where user.id in (?)
AND user.user_alias is NOT NULL }
aliases = User.find_by_sql([ sql, params[:id] ])
Unless you desperately need the performance of not creating ActiveRecord objects for the few records returned, I'd do this idiomatically like:
aliases = User.all(
:conditions => ['id IN (?) AND user_alias IS NOT NULL', params[:id]
).map(&:user_alias)
Try:
where user.id in (#{params[:id].join(',')})
I want to have a where clause with an equal and does not equal condition:
#user = User.where(:user_id => current_user.id, :author_id != current_user.id).nil? ? (render :something) : (render :somethingelse)
The above does not work:
syntax error, unexpected ')',
expecting tASSOC ...d, :user_id !=
current_user.id).nil? ? (render
:index) : (re...
If I change the second condition from != to => it will work, however.
How do I have both conditions in one where clase? Thank you
Here's how you would use Arel to generate the query "select * from users where user_id = ? and author_id != ?":
users = User.arel_table
User.where(users[:user_id]. eq(current_user.id).and(
users[:author_id].not_eq(current_user.id)))
Using Arel isn't as concise as using Hash conditions for simple conditions, but it's a lot more powerful!
Here's a link to the full list of predications (eq, not_eq, gt, lt, etc.) available with Arel.
I believe, it should be:
#user = User.where(['user_id = ? AND author_id <> ?', current_user.id, current_user.id])
render(#user ? :something : :somethingelse)
Rails 4 has this all figured out
Model.where.not(:colname => nil)
#=> returns all records whose :colname values are not nil
The syntax error is due to you attempting to use != instead of =>. The where method does not support inequality with hashed arguments, so your not equal will need to be written using array arguments.
User.where(:user_id => current_user.id).where(['users.author_id <> ?', current_user.id])
http://guides.rubyonrails.org/active_record_querying.html#hash-conditions
Only equality, range and subset checking are possible with Hash conditions.
You'll need to either drop down to straight SQL or invert and arel query, see Is there a way to invert an ActiveRecord::Relation query?
Not sure if you're aware, the not equal condition typically does not match (author_id) NULL values. You'll have to do an OR author_id IS NULL if you want that.
#users = User.where("user_id = ? AND (author_id != ? OR author_id IS NULL)",
current_user.id, current_user.id)
render(#users.present? ? :something : :somethingelse)
Also note that I'm using #users.present? because where finder returns an ActiveRecord::Relation array.
I am writing this statement here :
where :commentable_id => comments.map(&:id), :user_id => !current_user
The problem is it's returning only a user with the id of 0. Assuming this is a non-user, or more specifically a !user.
In this case, I am just trying to say anybody but the current user.
How would you write that?
Using "not #{current_user.id} returns :
SELECT "comments".* FROM "comments" WHERE ("comments"."user_id" = 0)
Right, I've written this in the other answer I gave.
The problem is that hashes in conditions are only good for making == or IN comparisons in SQL. You cannot do bigger or smaller comparisons or not-equals. You have to write a snippet of SQL yourself.
where(:commentable_id => comments.map(&:id)).where("user_id != ?", current_user.id)
You approach doesn't work, because it will send the results of !current_user to the SQL generator (which is the ARel gem). According to Ruby logic, any object that isn't nil or false is considered to be true. When you place a "bang" (exclamation mark) before it, it will make if false. And ARel will try to convert false in to whatever database understands as false. Most databases don't understand booleans and they use different other methods for that. SQLite uses 't' and 'f', and MySQL uses 1 and 0 (I believe).
where :commentable_id => comments.map(&:id), :user_id => "<> #{current_user.id}"
This will do it.