What is the proper syntax for `!current_user` - ruby-on-rails

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.

Related

Ordering by specific value in Activerecord

In Ruby on Rails, I'm trying to order the matches of a player by whether the current user is the winner.
The sort order would be:
Sort by whether the current user is the winner
Then sort by created_at, etc.
I can't figure out how to do the equivalent of :
Match.all.order('winner_id == ?', #current_user.id)
I know this line is not syntactically correct but hopefully it expresses that the order must be:
1) The matches where the current user is the winner
2) the other matches
You can use a CASE expression in an SQL ORDER BY clause. However, AR doesn't believe in using placeholders in an ORDER BY so you have to do nasty things like this:
by_owner = Match.send(:sanitize_sql_array, [ 'case when winner_id = %d then 0 else 1 end', #current_user.id ])
Match.order(by_owner).order(:created_at)
That should work the same in any SQL database (assuming that your #current_user.id is an integer of course).
You can make it less unpleasant by using a class method as a scope:
class Match < ActiveRecord::Base
def self.this_person_first(id)
by_owner = sanitize_sql_array([ 'case when winner_id = %d then 0 else 1 end', id])
order(by_owner)
end
end
# and later...
Match.this_person_first(#current_user.id).order(:created_at)
to hide the nastiness.
This can be achived using Arel without writing any raw SQL!
matches = Match.arel_table
Match
.order(matches[:winner_id].eq(#current_user.id).desc)
.order(created_at: :desc)
Works for me with Postgres 12 / Rails 6.0.3 without any security warning
If you want to do sorting on the ruby side of things (instead of the SQL side), then you can use the Array#sort_by method:
query.sort_by(|a| a.winner_id == #current_user.id)
If you're dealing with bigger queries, then you should probably stick to the SQL side of things.
I would build a query and then execute it after it's built (mostly because you may not have #current_user. So, something like this:
query = Match.scoped
query = query.order("winner_id == ?", #current_user.id) if #current_user.present?
query = query.order("created_at")
#results = query.all

Find records where an attribute is present

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

Rails 3 Active Record relation order: Use hash instead of string

To sort a relation in Rails 3, we have to do this:
User.where(:activated => true).order('id ASC')
But I think this:
User.where(:activated => true).order(:id => :asc)
would make better sense because the way the field name be escaped should depend on the adapter (SqlLite vs Mysql vs PostgreSQL), right?
Is there something similar to that?
As far as I know there's no option for this syntax built into ActiveRecord, but it shouldn't be hard for you to add one. I found the order method defined in lib/active_record/relation/query_methods.rb. Theoretically, you should be able to do something like this:
module ActiveRecord
module QueryMethods
def order(*args)
args.map! do |arg|
if arg.is_a? Hash
# Format a string out of the hash that matches the original AR style
stringed_arg
else
arg
end
end
super
end
end
end
I think the key problem is: ActiveRecord API is not aware of ordering semantic. It just accepts a string and bypasses to the underlying database. Fortunately, Sqlite, MySQL and PostgreSQL has no difference in order syntax.
I don't think ActiveRecord can do this abstraction well, and it doesn't need to do it. It works well with relation databases, but is hard to integrate with NoSQL, eg. MongoDB.
DataMapper, another famous Ruby ORM, did better abstraction. Take a look at its query syntax:
#zoos_by_tiger_count = Zoo.all(:order => [ :tiger_count.desc ])
The API is aware of the ordering semantic. By default, DataMapper will generate SQL order statement:
https://github.com/datamapper/dm-do-adapter/blob/master/lib/dm-do-adapter/adapter.rb#L626-634
def order_statement(order, qualify)
statements = order.map do |direction|
statement = property_to_column_name(direction.target, qualify)
statement << ' DESC' if direction.operator == :desc
statement
end
statements.join(', ')
end
However, it's possible to override at DB adapter layer:
https://github.com/solnic/dm-mongo-adapter/blob/master/lib/dm-mongo-adapter/query.rb#L260-264
def sort_statement(conditions)
conditions.inject([]) do |sort_arr, condition|
sort_arr << [condition.target.field, condition.operator == :asc ? 'ascending' : 'descending']
end
end
TL;DR:
You don't need worry about syntax problem if you are only using SqlLite, Mysql and PostgreSQL.
For better abstraction, you can try DataMapper.
For this particular case, you could drop the 'ASC' bit as ordering in all database is implicitly ascending
Foo.order(:bar)
I am aware that this doesn't cover the case where you'd want to do order by bar desc but actually for order by this doesn't matter much unless you are using functions for the order by clause in which case maybe something like squeel would help

Does not equal conditional

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.

rails - activerecord ... grab first result

I want to grab the most recent entry from a table. If I was just using sql, you could do
Select top 1 * from table ORDER BY EntryDate DESC
I'd like to know if there is a good active record way of doing this.
I could do something like:
table.find(:order => 'EntryDate DESC').first
But it seems like that would grab the entire result set, and then use ruby to select the first result. I'd like ActiveRecord to create sql that only brings across one result.
You need something like:
Model.first(:order => 'EntryDate DESC')
which is shorthand for
Model.find(:first, :order => 'EntryDate DESC')
Take a look at the documentation for first and find for details.
The Rails documentation seems to be pretty subjective in this instance. Note that .first is the same as find(:first, blah...)
From:http://api.rubyonrails.org/classes/ActiveRecord/Base.html#M002263
"Find first - This will return the first record matched by the options used. These options can either be specific conditions or merely an order. If no record can be matched, nil is returned. Use Model.find(:first, *args) or its shortcut Model.first(*args)."
Digging into the ActiveRecord code, at line 1533 of base.rb (as of 9/5/2009), we find:
def find_initial(options)
options.update(:limit => 1)
find_every(options).first
end
This calls find_every which has the following definition:
def find_every(options)
include_associations = merge_includes(scope(:find, :include), options[:include])
if include_associations.any? && references_eager_loaded_tables?(options)
records = find_with_associations(options)
else
records = find_by_sql(construct_finder_sql(options))
if include_associations.any?
preload_associations(records, include_associations)
end
end
records.each { |record| record.readonly! } if options[:readonly]
records
end
Since it's doing a records.each, I'm not sure if the :limit is just limiting how many records it's returning after the query is run, but it sure looks that way (without digging any further on my own). Seems you should probably just use raw SQL if you're worried about the performance hit on this.
Could just use find_by_sql http://api.rubyonrails.org/classes/ActiveRecord/Base.html#M002267
table.find_by_sql "Select top 1 * from table ORDER BY EntryDate DESC"

Resources