Rails changes postgres SELECT * to SELECT COUNT - ruby-on-rails

I am working with the RailsCast on token input and am trying to cleanup a query method for Postgres. I found this post for making my query DB-agnostic.
My method:
def self.tokens(query)
t = Language.arel_table
languages = Language.where(t[:name].matches("%#{query}%"))
if languages.empty?
[{id: "<<<#{query}>>>", name: "New: \"#{query}\""}]
end
end
Returns
:001 > Language.tokens('Ru')
(0.8ms) SELECT COUNT(*) FROM "languages" WHERE ("languages"."name" ILIKE '%Ru%')
But if I use return instead of language =, I get the correct query:
def self.tokens(query)
t = .arel_table
return Language.where(t[:name].matches("%#{query}%"))
end
:001 > Language.tokens('Ru')
Language Load (0.9ms) SELECT "languages".* FROM "languages" WHERE ("languages"."name" ILIKE '%Ru%')
It's probably something obvious, but I cannot figure out why the first method is selecting count instead of all of the rows in the `languages' table db. I would really like to store the result of that query in a variable.

It's because the where is resolved as lazily as it possibly can be (not until it is absolutely needed). In your case it needs it when you:
Explicitly return
Check empty?
The reason it is doing the count, is to determine via the count whether it is empty.

Related

Rails - How can I avoid using the database while performing a detect method?

When performing detect on a int array, it works:
#number = [1,2,3,4,5,6,7].detect{|n| n == 4}
Variable #number becomes 4.
But when I do something like this:
#categories = Category.all
#current_category = #categories.detect{|cat| cat.id == params[:category]}
The program outputs
Category Load (0.2ms) SELECT "categories".* FROM "categories"
Which means it's using the database to find it.
However, the element I'm trying to find is already in the collection #categories, I just want to find it to assign it to a variable.
Of course another solution would be to implement a linear search algorithm, but I just want to keep the code as clean as possible.
How can I avoid using the database for this search?
EDIT: I just realized that this could be lazy fetching. Because before detect, I never use #categories, so it does the query when I do detect. Could this be true?
Rails is actually performing a SELECT COUNT(*) query when you call #categories.all, essentially performing a lazy-fetch.
Your #categories object still needs to query the database for the data.
See the documentation here: http://apidock.com/rails/ActiveRecord/Scoping/Named/ClassMethods/all
posts = Post.all
posts.size # Fires "select count(*) from posts" and returns the count
posts.each {|p| puts p.name } # Fires "select * from posts" and loads post objects
fruits = Fruit.all
fruits = fruits.where(color: 'red') if options[:red_only]
fruits = fruits.limit(10) if limited?
In your case, you should use active record and SQL requesting.
#current_category = #categories.find_by(id: params[:category])
Using array methods on Active Record relations tend to fetch all the data then apply the algorithm in-memory, while SQL filtering is faster.
In you case I love to define the operator [] on my model:
#in category.rb
def self.[](x)
self.find_by(id: x)
end
# anywhere after:
if c = Category[params[:id]]
puts "Category found and it's #{c.name} !"
else
puts "Not found :("
end

Rails/Oracle: ActiveRecord find returning different results from console and app

I recently started moving my application from using sqlite to Oracle and began experiencing the following issue:
With rails 3.2.13, Oracle 11.2.0.3.0 and activerecord-oracle_enhanced-adapter (1.4.2), I have the following in my controller:
def show
if params.has_key?('user_id')
#user = User.find(params[:user_id])
else
#user = current_user
end
#user_id = #user.id
#activity_date = Date.parse(params[:id])
##activity_date = Activity.all.first.activity_date
#activities = Activity.where("user_id = ? AND activity_date = ?", #user.id, #activity_date)
logger.warn "----- count is #{#activities.count} ------"
return
The application finds exactly 0 records (as per the logger output and with the appropriate values for #user and #activity_date).
development.log reports the generated query as:
SELECT COUNT(*) FROM "ACTIVITIES" WHERE (user_id = 10594 AND to_date(activity_date) = TO_DATE('2013-06-05','YYYY-MM-DD HH24:MI:SS'))
and if I run this query from SQL*Plus, I get exactly 4 records - which I am expecting.
- so it appears there is a discrepancy between the results returned by AR and the results returned by the query AR is reporting to my log.
further, if I muck around and manually set the date before the where call with something like
#activity_date = Activity.all.first.activity_date
where the first activity date happens to be the "correct" activity date, Rails returns all 4 rows, so the console and app are pointing to the same database.
As far as I can tell:
Rails is generating a valid query, so the values going into the .where method are OK
said query returns the "right" number of results, *when run from SQL*Plus*
the console and the application are pointing to the same databases
but Rails seems to be "seeing" the wrong number of results.
What's going on ? I'm running out of hair to pull out.
EDIT Removed TO_DATE call around activity_date placeholder as per David Aldridge's suggestion below; still don't get the right result set.
EDIT As per David's suggestion:
#user_id=10594
#activity_date=Date.parse('2013-06-13')
####### **Returned wrong set of results**
####### Note class of #activity_date is Date
bundler-0.9.24 :083 > #activity_date.class
=> Date
bundler-0.9.24 :084 > Activity.where("user_id = ? AND activity_date = ?", #userid, #activity_date).explain
Activity Load (1.8ms) SELECT "ACTIVITIES".* FROM "ACTIVITIES" WHERE (user_id = NULL AND activity_date = TO_DATE('2013-06-13','YYYY-MM-DD HH24:MI:SS'))
EXPLAIN (7.8ms) EXPLAIN PLAN FOR SELECT "ACTIVITIES".* FROM "ACTIVITIES" WHERE (user_id = NULL AND activity_date = TO_DATE('2013-06-13','YYYY-MM-DD HH24:MI:SS'))
####### **Returned right set of results**
bundler-0.9.24 :089 > #activity_date=Activity.all.first.activity_date
Activity Load (1.5ms) SELECT "ACTIVITIES".* FROM "ACTIVITIES"
=> Wed, 05 Jun 2013 04:00:00 UTC +00:00
#### Note class of #activity_date is different from above**
bundler-0.9.24 :090 > #activity_date.class
=> ActiveSupport::TimeWithZone
bundler-0.9.24 :091 > Activity.where("user_id = ? AND activity_date = ?", #userid, #activity_date).explain
**And note generated query includes a time specification whereas previous query did not**
Activity Load (2.7ms) SELECT "ACTIVITIES".* FROM "ACTIVITIES" WHERE (user_id = NULL AND activity_date = TO_DATE('2013-06-05 04:00:00','YYYY-MM-DD HH24:MI:SS'))
SOLVED
When I was creating the activity_date field, I used Date.civil. I neglected to consider that what Oracle calls its "Date" type actually contains a Time component as well.
Since Date.civil does not take time zones, the activity_date I stored is essentially a DateTime, offset by the local timezone (since Date.civil does not take time zones). Since my application ignores the actual times, I solved this by computing the activity_date using DateTime.civil instead of Date.civil. Thanks to David Aldridge for his help, and apologies that I don't have enough rep to upvote him for it.
Is the activity_date column actually a date, or is it a character string that needs to be converted to a date?
If the former then the to_date is misplaced, and should not be present.
#activities = Activity.where("user_id = ? AND activity_date = ?", #user.id, #activity_date)
The reason why it might work in one environment and not the other is that the environments have different values for nls_date_parameter, either for the database or for the session.
A useful diagnostic here might be the explain plan, which should show implicit data conversions if rails is using DBMS_XPlan to get the plan. You should be able to get the plan through the console with:
Activity.where("user_id = ? AND activity_date = ?", #user.id, #activity_date).explain
Running the query in SQL*Plus and then running:
select * from table(dbms_xplan.display);
... ought to give you the plan that SQL*Plus is using.
If you post them in the question it might help future problem-encounterers.

Rails 3.2 force prepared statements

If i use expression Model.find(1) then rails executes it as prepared statement: SELECT "models".* FROM "models" WHERE "models"."id" = $1 LIMIT 1 [["id", 1]]
But when I use Model.where("id = ?", 1) it executes without prepared statement: SELECT "models".* FROM "models" WHERE (id = 1)
How to force rails to use prepared statement in this case too?
i'm not sure you can.
But
Model.where(:id => 1)
Should generate a prepared statement. Fragment string works differently, so you can generate exactly what you need, in custom cases.
Edit :
Try this, i'm not sure it works, i can' test for now, but it looks like what you need :
Client.where("created_at >= :start_date AND created_at <= :end_date",
{:start_date => params[:start_date], :end_date => params[:end_date]})
More here: http://guides.rubyonrails.org/active_record_querying.html#conditions
In the placeholder section. Moreover date range works in the array format :
:date => date_begenning..date_end
Edit 2 : indeed you can, but i seems where don't support building this by hand.
You might build your prepared queries by hand :
http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/DatabaseStatements.html#method-i-exec_query
exec_query(sql, name = 'SQL', binds = [])
There is also a bind method on your relation.
But both binds need that the value have a name method, they will crash if you give a fixnum, a hash, ect. I hadn't found any doc explaining how this works.
Bu for the where clause, as long it is of Class String, active ecord apply a .to_sql on it, so you don't get your prepared statement.

find vs find_by vs where

I am new to rails. What I see that there are a lot of ways to find a record:
find_by_<columnname>(<columnvalue>)
find(:first, :conditions => { <columnname> => <columnvalue> }
where(<columnname> => <columnvalue>).first
And it looks like all of them end up generating exactly the same SQL. Also, I believe the same is true for finding multiple records:
find_all_by_<columnname>(<columnvalue>)
find(:all, :conditions => { <columnname> => <columnvalue> }
where(<columnname> => <columnvalue>)
Is there a rule of thumb or recommendation on which one to use?
where returns ActiveRecord::Relation
Now take a look at find_by implementation:
def find_by
where(*args).take
end
As you can see find_by is the same as where but it returns only one record. This method should be used for getting 1 record and where should be used for getting all records with some conditions.
Edit:
This answer is very old and other, better answers have come up since this post was made. I'd advise looking at the one posted below by #Hossam Khamis for more details.
Use whichever one you feel suits your needs best.
The find method is usually used to retrieve a row by ID:
Model.find(1)
It's worth noting that find will throw an exception if the item is not found by the attribute that you supply. Use where (as described below, which will return an empty array if the attribute is not found) to avoid an exception being thrown.
Other uses of find are usually replaced with things like this:
Model.all
Model.first
find_by is used as a helper when you're searching for information within a column, and it maps to such with naming conventions. For instance, if you have a column named name in your database, you'd use the following syntax:
Model.find_by(name: "Bob")
.where is more of a catch all that lets you use a bit more complex logic for when the conventional helpers won't do, and it returns an array of items that match your conditions (or an empty array otherwise).
Model.find
1- Parameter: ID of the object to find.
2- If found: It returns the object (One object only).
3- If not found: raises an ActiveRecord::RecordNotFound exception.
Model.find_by
1- Parameter: key/value
Example:
User.find_by name: 'John', email: 'john#doe.com'
2- If found: It returns the object.
3- If not found: returns nil.
Note: If you want it to raise ActiveRecord::RecordNotFound use find_by!
Model.where
1- Parameter: same as find_by
2- If found: It returns ActiveRecord::Relation containing one or more records matching the parameters.
3- If not found: It return an Empty ActiveRecord::Relation.
There is a difference between find and find_by in that find will return an error if not found, whereas find_by will return null.
Sometimes it is easier to read if you have a method like find_by email: "haha", as opposed to .where(email: some_params).first.
Since Rails 4 you can do:
User.find_by(name: 'Bob')
which is the equivalent find_by_name in Rails 3.
Use #where when #find and #find_by are not enough.
The accepted answer generally covers it all, but I'd like to add something,
just incase you are planning to work with the model in a way like updating, and you are retrieving a single record(whose id you do not know), Then find_by is the way to go, because it retrieves the record and does not put it in an array
irb(main):037:0> #kit = Kit.find_by(number: "3456")
Kit Load (0.9ms) SELECT "kits".* FROM "kits" WHERE "kits"."number" =
'3456' LIMIT 1
=> #<Kit id: 1, number: "3456", created_at: "2015-05-12 06:10:56",
updated_at: "2015-05-12 06:10:56", job_id: nil>
irb(main):038:0> #kit.update(job_id: 2)
(0.2ms) BEGIN Kit Exists (0.4ms) SELECT 1 AS one FROM "kits" WHERE
("kits"."number" = '3456' AND "kits"."id" != 1) LIMIT 1 SQL (0.5ms)
UPDATE "kits" SET "job_id" = $1, "updated_at" = $2 WHERE "kits"."id" =
1 [["job_id", 2], ["updated_at", Tue, 12 May 2015 07:16:58 UTC +00:00]]
(0.6ms) COMMIT => true
but if you use where then you can not update it directly
irb(main):039:0> #kit = Kit.where(number: "3456")
Kit Load (1.2ms) SELECT "kits".* FROM "kits" WHERE "kits"."number" =
'3456' => #<ActiveRecord::Relation [#<Kit id: 1, number: "3456",
created_at: "2015-05-12 06:10:56", updated_at: "2015-05-12 07:16:58",
job_id: 2>]>
irb(main):040:0> #kit.update(job_id: 3)
ArgumentError: wrong number of arguments (1 for 2)
in such a case you would have to specify it like this
irb(main):043:0> #kit[0].update(job_id: 3)
(0.2ms) BEGIN Kit Exists (0.6ms) SELECT 1 AS one FROM "kits" WHERE
("kits"."number" = '3456' AND "kits"."id" != 1) LIMIT 1 SQL (0.6ms)
UPDATE "kits" SET "job_id" = $1, "updated_at" = $2 WHERE "kits"."id" = 1
[["job_id", 3], ["updated_at", Tue, 12 May 2015 07:28:04 UTC +00:00]]
(0.5ms) COMMIT => true
Apart from accepted answer, following is also valid
Model.find() can accept array of ids, and will return all records which matches.
Model.find_by_id(123) also accept array but will only process first id value present in array
Model.find([1,2,3])
Model.find_by_id([1,2,3])
The answers given so far are all OK.
However, one interesting difference is that Model.find searches by id; if found, it returns a Model object (just a single record) but throws an ActiveRecord::RecordNotFound otherwise.
Model.find_by is very similar to Model.find and lets you search any column or group of columns in your database but it returns nil if no record matches the search.
Model.where on the other hand returns a Model::ActiveRecord_Relation object which is just like an array containing all the records that match the search. If no record was found, it returns an empty Model::ActiveRecord_Relation object.
I hope these would help you in deciding which to use at any point in time.
Suppose I have a model User
User.find(id)
Returns a row where primary key = id. The return type will be User object.
User.find_by(email:"abc#xyz.com")
Returns first row with matching attribute or email in this case. Return type will be User object again.
Note :- User.find_by(email: "abc#xyz.com") is similar to User.find_by_email("abc#xyz.com")
User.where(project_id:1)
Returns all users in users table where attribute matches.
Here return type will be ActiveRecord::Relation object. ActiveRecord::Relation class includes Ruby's Enumerable module so you can use it's object like an array and traverse on it.
Both #2s in your lists are being deprecated. You can still use find(params[:id]) though.
Generally, where() works in most situations.
Here's a great post: https://web.archive.org/web/20150206131559/http://m.onkey.org/active-record-query-interface
The best part of working with any open source technology is that you can inspect length and breadth of it.
Checkout this link
find_by ~> Finds the first record matching the specified conditions. There is no implied ordering so if order matters, you should specify it yourself. If no record is found, returns nil.
find ~> Finds the first record matching the specified conditions , but if no record is found, it raises an exception but that is done deliberately.
Do checkout the above link, it has all the explanation and use cases for the following two functions.
I will personally recommend using
where(< columnname> => < columnvalue>)

Why is ActiveRelation returning an array when I call merge?

I have models called User, Activity, and Task and I can do the following
> puts Activity.joins(:task).to_sql
SELECT "activities".* FROM "activities" INNER JOIN "tasks" ON "tasks"."id" = "activities"."task_id" ORDER BY date DESC
> puts User.first.shared_tasks.to_sql
SELECT "tasks".* FROM "tasks" INNER JOIN "user_tasks" ON "tasks"."id" = "user_tasks"."task_id" WHERE "user_tasks"."user_id" = 1
But when I try to merge the two, I get an array:
> puts Activity.joins(:task).merge(User.first.shared_tasks).to_sql
NoMethodError: undefined method `to_sql' for []:Array
Why is that not returning a relation? I need to put a where clause on it.
Update:
Upon further inspection, it looks likes User.first.shared_tasks is being evaluated to an array of tasks immediately. I can gett the behavior I want by adding an order call:
> puts Activity.joins(:task).merge(User.first.shared_tasks.order()).to_sql
SELECT "activities".* FROM "activities" INNER JOIN "tasks" ON "tasks"."id" = "activities"."task_id" INNER JOIN "user_tasks" ON "tasks"."id" = "user_tasks"."task_id" WHERE "user_tasks"."user_id" = 1 ORDER BY date DESC
Is there a better way to prevent that relation from being evaluated besides adding an empty order?
Whenever you call a method on a relation object that it doesn't respond to, it delegates that method to some other type of object. In the case of merge, it will be converted to an array. This is done via the ActiveRecord::Delegation module. Some methods such as each are explicitly delegated, and others such as merge are done through method_missing.
For example, if you do:
Activity.joins(:task).each { |activity| puts activity }
the relation delegates .each to an array (Enumerator), essentially doing a conversion.
Alright, I still have not figured out why User.first.shared_tasks is being immediately evaluated, but I have figured out a work around. I can just call scoped:
> User.first.shared_tasks.class
=> Array
> User.first.shared_tasks.scoped.class
=> ActiveRecord::Relation
Now when I try to do the merge:
Activity.joins(:task).merge(User.first.shared_tasks.scoped)
It uses ActiveRecord::Relation#merge instead of Array#merge
For relation you can try this
puts (Activity.joins(:task) & User.first.shared_tasks).to_sql
merge helps in create a scope and it returns an array of resulted objects.

Resources