When is an ActiveRecord query executed? - ruby-on-rails

I had a confusion about how Ruby executes a query:
Lets suppose we have two methods:
def meet1
user = User.all
end
everytime I call this method I get a query running which says:
'User Load (18.3ms) SELECT "users".* FROM "users" INNER JOIN "roles" ON "roles"."user_id" = "users"."id" WHERE "users"."banned" = 'f' AND "roles"."description" = 'gogetter''
It means that it queries the users...
lets suppose I have another method:
def meet2
user = User.all
user.to_sql
end
when I call this it returned me the SQL format of that query:
So my question is in the first method the query gets executed but does the query gets executed in the second method? Or its just showing me the end result without executing the query because I never used it?

user = User.all does nothing but create a potential query, and spawn a copy of it into user. This follows the "Builder Pattern"; user could then say user.joins(...), or user.order(...), to build a different potential query. They both return a spawned copy of the query.
What you do with meet1 triggers the actual hit to the database. I suspect that something as innocuous as p meet1, or even your IRB shell, could evaluate the spawned potential database query as an Enumeration, which then hits the database.

I would like you too have a look at this answer
when you call User.all it returns User::ActiveRecord_Relation object, and by itself this object does not issue a database query. It's where you use this object that matters.
so when meet1 is called it issues the query but in case of meet2 User.all issues relational object and user.to_sql issues the query related to database.
You can see same thing happens when you try to chain multiple filters based on condition
u = User.all
u = u.where(name: "Hello") if any_condition?
u = u.where(last_name: 'world') if any_other_condition?
in such cases the in any_condition? and any_other_condition? is true it does only one query merging all 3 thing .all and all the where conditions.
I would like you have a look at blog here it shows some kickers methods which will make you a bit more clear about the way.

Related

How to perform atomic increments in rails [duplicate]

What's the best way to implement an atomic insert/update for a model with a counter in Rails? A good analogy for the problem I'm trying to solve is a "like" counter with two fields:
url : string
count : integer
Upon insert, if there is not currently a record with a matching url, a new record should be created with count 1; else the existing record's count field should be incremented.
Initially I tried code like the following:
Like.find_or_create_by_url("http://example.com").increment!(:count)
But unsurprisingly, the resulting SQL shows that the SELECT happens outside the UPDATE transaction:
Like Load (0.4ms) SELECT `likes`.* FROM `likes` WHERE `likes`.`url` = 'http://example.com' LIMIT 1
(0.1ms) BEGIN
(0.2ms) UPDATE `likes` SET `count` = 4, `updated_at` = '2013-01-17 19:41:22' WHERE `likes`.`id` = 2
(1.6ms) COMMIT
Is there a Rails idiom for dealing with this, or do I need to implement this at the SQL level (and thus lose some portability)?
You can use pessimistic locking,
like = Like.find_or_create_by_url("http://example.com")
like.with_lock do
like.increment!(:count)
end
I am not aware of any ActiveRecord method that implemts atomic increments in a single query. Your own answer is a far as you can get.
So your problem may not be solvable using ActiveRecord. Remember: ActiveRecord is just a mapper to simplify some things, while complicating others. Some problems just solvable by plain SQL queries to the database. You will loose some portability. The example below will work in MySQL, but as far as I know, not on SQLlite and others.
quoted_url = ActiveRecord::Base.connection.quote(#your_url_here)
::ActiveRecord::Base.connection.execute("INSERT INTO likes SET `count` = 1, url = \"#{quoted_url}\" ON DUPLICATE KEY UPDATE count = count+1;");
Here is what I have come up with so far. This assumes a unique index on the url column.
begin
Like.create(url: url, count: 1)
puts "inserted"
rescue ActiveRecord::RecordNotUnique
id = Like.where("url = ?", url).first.id
Like.increment_counter(:count, id)
puts "incremented"
end
Rails' increment_counter method is atomic, and translates to SQL like the following:
UPDATE 'likes' SET 'count' = COALESCE('count', 0) + 1 WHERE 'likes'.'id' = 1
Catching an exception to implement normal business logic seems rather ugly, so I would appreciate suggestions for improvement.
Rails supports Transactions, if that's what you're looking for, so:
Like.transaction do
Like.find_or_create_by_url("http://example.com").increment!(:count)
end
My favorite solution thus far:
Like.increment_counter(
:count,
Like.where(url: url).first_or_create!.id
)
For this use case and others, I think update_all approach is cleanest.
num_updated =
ModelClass.where(:id => my_active_record_model.id,
:service_status => "queued").
update_all(:service_status => "in_progress")
if num_updated > 0
# we updated
else
# something else updated in the interim
end
Not exactly you example, just pasted from the linked page. But you control the where condition and what is to be updated. Very nifty

Pluck data from query results without making another query

Ok, I got some records from a query, something like this.
works = Work.do_a_long_query_with_scope(....)
In my controller, I want to find something else base on this works' ids, then return this works in response
def index
works = Work.do_a_long_query_with_scope(....)
not_finished_tasks = Task.find_not_finished_tasks_of_user(works.ids, current_user.id)
# do some job with those tasks
render json: works
end
This works fine except that it created 2 query , 1 with SELECT works.id FROM works ... for that task function, 1 with SELECT works.* from works ... for serializer response.
So can I use works.ids without making another request to DB ?
You could just use #load to tell the relation to load everything from the database:
works = Work.do_a_long_query_with_scope(....)
works.load
not_finished_tasks = Task.find_not_finished_tasks_of_user(works.ids, current_user.id)
# do some job with those tasks
render json: works
After works.load, the works.ids call will pull the ids out of the already loaded Works instances rather than going back to the database.
Also, if your Task.find_not_finished_tasks_of_user call is doing something like:
where(id: array_of_work_ids)
then you could instead pass in the whole works relation and say:
where(id: works.select(:id))
to use a subquery instead of sending all the ids back to the database. The first one will do something like:
where id in (a_big_list_of_ids)
but the second will do:
where id in (select id from ...)
There may or may not be any noticeable difference depending on how big works.ids is. There's also a possibility that the subquery will give you different ids if something is changing the database between your load call and when the subquery executes.

ActiveRecord: Check Number of Database Calls

For demo purposes, suppose that I have a class called DemoThing with a method called do_something.
Is there a way that (in code) I can check the number of times that do_something hits the database? Is there a way that I can "spy" on active record to count the number of times that the database was called?
For instance:
class DemoThing
def do_something
retVal = []
5.times do |i|
retVal << MyActiveRecordModel.where(:id => i)
end
retVal
end
end
dt = DemoThing.new
stuff = dt.do_something # want to assert that do_something hit the database 5 times
ActiveRecord should be logging each query in STDOUT.
But for the above code, it's pretty obvious that you're making 5 calls for each iteration of i.
Queries can be made more efficient by not mixing Ruby logic with querying.
In this example, gettings the ids before querying will mean that the query isn't called for each Ruby loop.
ids = 5.times.to_a
retVal = MyActiveRecordModel.where(id: ids) # .to_a if retVal needs to be an Array
Sure is. But first you must understand Rails' Query Cache and logger. By default, Rails will attempt to optimize performance by turning on a simple query cache. It is a hash stored on the current thread (one for every active database connection - Most rails processes will have just one ). Whenever a select statement is made (like find or where etc.), the corresponding result set is stored in a hash with the SQL that was used to query them as the key. You'll notice when you run the above method your log will show Model Load statement and then a CACHE statement. Your database was only queried one time, with the other 4 being loaded via cache. Watch your server logs as you run that query.
I found a gem for queries count https://github.com/comboy/sql_queries_count

How to not evaluate query until certain point? (to have Relation object inspect)

Consider this:
1] pry(main)> User.joins(:tasks)
=> #<User::ActiveRecord_Relation:0x3fe8923d31cc>
[2] pry(main)> User.where(state: 'undefined')
User Load (6.8ms) SELECT "users".* FROM "users" WHERE "users"."state" = 'undefined'
=> []
So in first case it returns Relation object, what is "lazy" - I can apply other methods to it (like .where), and it doesn't fire query itself.
But where does the opposite thing - it immediately proceeds query. But what if I want to have here also active record relation, so I can chain other scopes to it, and then evaluate query?
I want to have query in separate method and then to apply to return of this method find_each.
Using where(...) is returning an ActiveRecord_Relation.
Try calling .class on the end of that line like so:
User.where(state: 'undefined').class and see what you get.
What you are seeing is simply the console calling something like .inspect on the relation.
Outside of the console, ActiveRecord Relations will lazily evaluate and not make a call to the DB until absolutely necessary.
See this post from the Odin Project for a fuller explanation:
https://www.theodinproject.com/courses/ruby-on-rails/lessons/active-record-queries

stop meta_search doing sql to early

in the docs it's said:
MyObject.search()
returns an instance of MetaSearch::Builder (something like ActiveRecord::Relation). But in my case when I do this I get a collection of objects because the sql-query is send to the database.
I would like to have something like this:
search = MyObject.search() # no sql-query should be done here
count = search.count # count sql done
objects = search.all # select sql done - maybee with pagination
does anyone know how to stop Meta_search from doing the the query to early?
-> ok, something mysterious going on in my shell:
search = MyObject.search() # queries the database
search = MyObject.search(); 0 # stores a MetaSearch-Object in search
the console seems to call an extra method after each comand
If you're testing in irb, be aware that returned objects are inspected. In the case of a MetaSearch builder, this means the relation gets inspected. If you have a look at ActiveRecord's inspect method, in relation.rb, you'll see that it calls to_a, which executes the query and returns the results.

Resources