ActiveRecord .where() method ignored - ruby-on-rails

I've got a weird issue... In a model User, that has no relation with the model Interest, I try to call this query:
# file model.rb
def self.my_func
Interest.where('id IN (?)', [1,2])
end
But it's completely ignored... and if I replace by this:
# file model.rb
def self.my_func
Interest.find(1)
end
the .find() method is triggered and I get result.
If I directly call Interest.where('id IN (?)', [1,2]) in rails console it works...
I'm on RoR 3.2.13
Any idea?
Thank you all.

Rails doesn't evaluate the query until you actually try to access the results. Calling Model.where just returns an ActiveRecord::Relation onto which you can chain additional where/order/etc calls.
Try this (.all forces the query to be evaluated and returns an array of results):
def self.my_func
Interest.where(id: [1,2]).all
end
Note that should shouldn't actually do this. It's much better for your model to just allow the ActiveRecord::Relation to be returned from the method, so the calling code can apply additional scoping/ordering methods to it.
Also note that, instead of manually building an id in (?) query, Rails is smart enough to do it for you if you just use where(id: [1,2]).
Bonus notes:
On the terminal, the results get evaluated from Model.where immediately because IRB invokes inspect on the result of each expression you enter so it has something to print, and the returned ActiveRecord::Relation evaluates its query when inspected. You can bypass this to prove the point to yourself by adding ;nil, so that your statement evaluates to nil; note that the SELECT doesn't happen until I manually call x.inspect:
irb(main):008:0> x = User.where("name like 'bob'"); nil
=> nil
irb(main):009:0> x.inspect
User Load (0.5ms) SELECT "users".* FROM "users" WHERE (name like 'bob')
=> "[]"

Related

Rails console not running multiple lines of code

So when I write this in my server rails console:
irb(main):001:0> Member.all do |member|
irb(main):002:1* member.identify
irb(main):003:1> end
It doesn't run the identify function, it just returns this:
Member Load (1.7ms) SELECT "members".* FROM "members" ORDER BY members.created_at DESC
...
Is there something I'm doing wrong syntax wise? How do you run multiline code in a server console?
If you want to apply the .identify SQL function to Member, you don't need a multiline statement with do and end, you can just write:
Member.identify
Member.all will return an array of objects that go with the query. You typically do not need to iterate over them, as it makes more sense to write a separate query that would automatically give you what you wanted . You can put a separate function in your Model (either a class method or scope) that would give you the desired results that you want.
i.e.:
Member model:
self.some_method
where(":member_name => "Jack")
end
and from here you can call it in your views or controllers by writing #member = Member.some_method
For the future if you wanted to iterate over each element within a collection (array, hash) you need to chain the object/class to an iterator such as each, or map. An iterator is basically a method that will go over each element of your collection, and apply certain methods/operations to each item in the collection:
i.e.:
some object = [1,2,3]
some_object.each do |x|
puts x
#will go over each element in array and print it
end
You should try using the rails console (inside the app directory):
rails c
or
rails console
There you multiline will work.

ActiveRecord none? method fires query

I have this code
def evaluate(collection)
if collection.none?
[]
else
collection.group(#group).pluck(*#columns)
end
end
The collection is an ActiveRecord::Relation object - for e.g. User.where(:name => 'Killer')
Now sometimes I also pass the Rails 4 none relation Users.none, that's why the check for none. If I do not check for none?, the call to pluck throws an arguments exception.
The problem is whenever I query any relation for none? it executes the query. See here:
> User.where(id: 1).none?
User Load (0.2ms) SELECT "users".* FROM "v1_passengers" WHERE "users"."id" = 1
=> false
> User.where(id: 1).none.none?
=> true
I do not want to execute to query just to check for none. Any workarounds?
Update: The none? method is actually array method thats why the query is executed. It's like calling to_a on the relation. What I want to know is how to figure out if the relation is a none
Found one method to do this without firing query. When you call none on a relation it appends the ActiveRecord::NullRelation to the extending_values array of the relation:
> User.where(id: 1).extending_values.include?(ActiveRecord::NullRelation)
=> false
> User.where(id: 1).none.extending_values.include?(ActiveRecord::NullRelation)
=> true
Not sure you can, there's no distinguishing feature between a Null Relation and an Actual Relation. Perhaps go down the rescue route:
begin
collection.group(#group).pluck(*#columns)
rescue #add exact Exception to catch
[]
end
Not exactly clean but gets round the problem

Are the .order method parameters in ActiveRecord sanitized by default?

I'm trying to pass a string into the .order method, such as
Item.order(orderBy)
I was wondering if orderBy gets sanitized by default and if not, what would be the best way to sanitize it.
The order does not get sanitized. This query will actually drop the Users table:
Post.order("title; drop table users;")
You'll want to check the orderBy variable before running the query if there's any way orderBy could be tainted from user input. Something like this could work:
items = Item.scoped
if Item.column_names.include?(orderBy)
items = items.order(orderBy)
end
They are not sanitized in the same way as a .where clause with ?, but you can use #sanitize_sql_for_order:
sanitize_sql_for_order(["field(id, ?)", [1,3,2]])
# => "field(id, 1,3,2)"
sanitize_sql_for_order("id ASC")
# => "id ASC"
http://api.rubyonrails.org/classes/ActiveRecord/Sanitization/ClassMethods.html#method-i-sanitize_sql_for_order
Just to update this for Rails 5+, as of this writing, passing an array into order will (attempt to) sanitize the right side inputs:
Item.order(['?', "'; DROP TABLE items;--"])
#=> SELECT * FROM items ORDER BY '''; DROP TABLE items;--'
This will trigger a deprecation warning in Rails 5.1 about a "Dangerous query method" that will be disallowed in Rails 6. If you know the left hand input is safe, wrapping it in an Arel.sql call will silence the warning and, presumably, still be valid in Rails 6.
Item.order([Arel.sql('?'), "'; DROP TABLE items;--"])
#=> SELECT * FROM items ORDER BY '''; DROP TABLE items;--'
It's important to note that unsafe SQL on the left side will be sent to the database unmodified. Exercise caution!
If you know your input is going to be an attribute of your model, you can pass the arguments as a hash:
Item.order(column_name => sort_direction)
In this form, ActiveRecord will complain if the column name is not valid for the model or if the sort direction is not valid.
I use something like the following:
#scoped = #scoped.order Entity.send(:sanitize_sql, "#{#c} #{#d}")
Where Entity is the model class.
Extend ActiveRecord::Relation with sanitized_order.
Taking Dylan's lead I decided to extend ActiveRecord::Relation in order to add a chainable method that will automatically sanitize the order params that are passed to it.
Here's how you call it:
Item.sanitized_order( params[:order_by], params[:order_direction] )
And here's how you extend ActiveRecord::Relation to add it:
config/initializers/sanitized_order.rb
class ActiveRecord::Relation
# This will sanitize the column and direction of the order.
# Should always be used when taking these params from GET.
#
def sanitized_order( column, direction = nil )
direction ||= "ASC"
raise "Column value of #{column} not permitted." unless self.klass.column_names.include?( column.to_s )
raise "Direction value of #{direction} not permitted." unless [ "ASC", "DESC" ].include?( direction.upcase )
self.order( "#{column} #{direction}" )
end
end
It does two main things:
It ensures that the column parameter is the name of a column name of the base klass of the ActiveRecord::Relation.
In our above example, it would ensure params[:order_by] is one of Item's columns.
It ensures that the direction value is either "ASC" or "DESC".
It can probably be taken further but I find the ease of use and DRYness very useful in practice when accepting sorting params from users.

Rails 3 - Expression-based Attribute in Model

How do I define a model attribute as an expression of another attribute?
Example:
Class Home < ActiveRecord::Base
attr_accessible :address, :phone_number
Now I want to be able to return an attribute like :area_code, which would be an sql expression like "substr(phone_number, 1,3)".
I also want to be able to use the expression / attribute in a group by query for a report.
This seems to perform the query, but does not return an object with named attributes, so how do I use it in a view?
Rails Console:
#ac = Home.group("substr(phone_number, 1,3)").count
=> #<OrderedHash {"307"=>3, "515"=>1}>
I also expected this to work, but not sure what kind of object it is returning:
#test = Home.select("substr(phone_number, 1,3) as area_code, count(*) as c").group("substr(phone_number, 1,3)")
=> [#<Home>, #<Home>]
To expand on the last example. Here it is with Active Record logging turned on:
>Home.select("substr(phone_number, 1,3) as area_code, count(*) as c").group("substr(phone_number, 1,3)")
Output:
Home Load (0.3ms) SELECT substr(phone_number, 1,3) as area_code, count(*) as c FROM "homes" GROUP BY substr(phone_number, 1,3)
=> [#<Home>, #<Home>]
So it is executing the query I want, but giving me an unexpected data object. Shouldn't I get something like this?
[ #<area_code: "307", c: 3>, #<area_code: "515", c: 1> ]
you cannot access to substr(...) because it is not an attribute of the initialized record object.
See : http://guides.rubyonrails.org/active_record_querying.html "selecting specific fields"
you can workaround this this way :
#test = Home.select("substr(phone_number, 1,3) as phone_number").group(:phone_number)
... but some might find it a bit hackish. Moreover, when you use select, the records will be read-only, so be careful.
if you need the count, just add .count at the end of the chain, but you will get a hash as you already had. But isn't that all you need ? what is your purpose ?
You can also use an area_code column that will be filled using callbacks on create and update, so you can index this column ; your query will run fast on read, though it will be slower on insertion.

Rails: Why can't you set an association to nil in a where clause?

I have photos that belong to collections and users. Photos always belong to a user, but may not be assigned to a collection.
In my controller, this works perfectly:
#collection_photos = Photo.where( :collection => #collection, :user => current_user )
However, this fails...
#other_photos = Photo.where( :collection => nil, :user => current_user )
...but this works:
#other_photos = Photo.where( :collection_id => nil, :user => current_user )
When collection is set to nil I get this error message: No attribute named 'collection' exists for table photos.
If I pass an object, it knows to search for collection_id from the symbol :collection, but if I don't pass an object it doesn't seem to be aware of the association.
Am I understanding this correctly? Could anyone explain a little better why :collection=>nil doesn't work?
when you use pass in the conditions into ActiveRecord, it actually tries to analyze the objects that you passed in, is it a string? an array? a hash? and what's in the string, array or hash?
and in your case, a hash, so it's trying to analyze what's in the hash, in the first statement (which works), you passed in a model instance as the value, so it tries to find if there are any associations that mapped to the key your specified and voila, it found it and everything works as planned
in the second case, you passed in nil as the value, now, ActiveRecord sees that it's a nil object, so it decided that it's not an association. note that it doesn't look at the key, but it only looked at the value, thus it tries to find if there's any column that mapped to the key, but it couldn't find, returning an error
in the last case, you passed in nil as the value, same thing, it tried to find a column which mapped to :collection_id, thus it passed in nil as the value in the SQL statement, and it returned successfully
so it's just an unfortunate considerations taken by ActiveRecord that makes the second case not working =)
hope this clarifies! =D
My guess is that it's like the famous rails .find vs .find_by_id.
.find is designed to throw an exception if it cannot find any association.
where as .find_by_id will just return nil if doesn't find any association.
so in your .where statement, when you search for the collection it's probably treating that like a .find and when you search by collection_id it will return nil just like .find_by_id does if it can't find any associated collection.
I'm not sure how these two methods differ in Activerecord's inner workings, but they are designed to react differently to nil results.
I think your answer is in ActiveRecord::PredicateBuilder.build_from_hash. There is a case statement in there that checks the class of each value in the hash, and it specifically looks for ActiveRecord::Relation
This seems to no longer be an issue in Rails 4. For instance the following code
#other_photos = Photo.where( :collection => nil, :user => User.first )
would run
User Load (Xms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1
Photo Load (Xms) SELECT "photos".* FROM "photos" WHERE "photos"."collection_id" IS NULL AND "photos"."user_id" = 1
*Tested in Rails 4.1.1

Resources