Active record create query in multiple steps - ruby-on-rails

I'm a bit confused by active record, it just seems to fire the query at any time you stop, ie.
#model.where( :store_id => #sid )
Which is fine, but what if I want to build a query like this:
query = #model.where( :store_id => #sid )
if(some_condition)
query.offset(50)
and then execute the query (not actually what I'm doing but a very simple example). Is there a way to put together the query in steps and then tell it to execute?

Actually, ActiveRecord will do exactly what you want. It's called lazy loading. You might be getting confused by the rails console, which calls .inspect behinds the scenes on the result of the line.
Check out this question: Lazy loading in Rails 3.2.6

This already works like you want it too.
where() returns an instance of ActiveRecord::Relation.
The relation won't execute it's database call until it needs to. The reason you might be experiencing otherwise is that you're testing it in the console, which prints the output of each statement (thus loading the relation). You can test whether a relation has been loaded via the loaded() method.
Try this on the console:
m = #model.where(:store_id => #sid); # the semicolon will silence the output
m.loaded? # nil
m # executes db call, will print out the contents of the relation
m.loaded? # true

Related

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

Rails Active Record Omitting Where Clause

I have an Active Record query that sits inside of a gem. Database used is postgres.
Client.where(date:#date,client:#business_id)
The gem uses a get request to pull this data. When there are too many values in #business_id, the URI is too long. Gem does not have post requests.
Workaround:
The business problem is when all the #business_id get passed to the app. I could have an "all" button, that triggers all the client values to show. I would need to ignore the client:#business_id part of the query.
How could I construct the query so that when all of the #business_id need to be passed, it ignores the client:#business_id part of the query?
chain the where clause and then conditionally include or exclude the #business_id part:
relation = Client.where(date:#date)
if your_conditional_is_true
relation = relation.where(client:#business_id)
end
the_clients = relation.all
The reason this works is that where actually returns an ActiveRelation, not the results of executing the SQL. The SQL is not built and executed until you do something to work on the results of the relation.

ActiveRecord: update_all shows query in debug log, but doesn't execute in DB?

I've got the following update query running in a function called by a before_destroy callback in a Rails model:
Annotation.joins(:annotation_groups)
.where({'annotation_groups.group_id' => self.id})
.update_all({qc_approved: false}) if doc.in_qc?`
(I've also tried the following simpler version to see if another angle works: self.annotations.update_all({qc_approved: false}))
Both generate the below SQL query in "Server development log" (debugging in RubyMine):
UPDATE "annotations" SET "qc_approved" = 'f' WHERE "annotations"."id" IN (SELECT "annotations"."id" FROM "annotations" INNER JOIN "annotation_groups" ON "annotation_groups"."annotation_id" = "annotations"."id" WHERE "annotation_groups"."group_id" = 159)
However, as far as I can tell, that SQL never causes a DB update, even though the destroy process afterwards works fine. I can set a breakpoint directly after the statement and look at the database, and the qc_approved fields are still true. However, I can copy and paste the statement into a Postgres console and run it, and it updates the fields correctly.
Is anyone aware as to what would cause this behavior? Does before_destroy exist in its own strange alternate transactional universe that causes odd behavior like this? What scenario would cause the SQL to show up in the server log but not make it to the DB?
Thanks to the quick and helpful comments above confirming the nature of the callback inside the larger transaction, I figured it out.. despite the name, before_destroy was actually executing after dependent destroy calls, so that the joined annotation_group table row was destroyed before the UPDATE statement that relied on it was called in the transaction.
To be more specific, I added :prepend => true to the before_destroy definition so that it ran before the destroys as intended.

Does this hit the db everytime?

If I have in:
config/initializers/foobar.rb
which has:
$foos = User.where(:foo => true).pluck(:id)
In my app when i call $foo, does it hit the db everytime?
Or does $foo only hits the db, on server start?
You're storing a scope (i.e. a set of criteria). However that scope will cache what it has loaded, so doing (for example) $foos.each {|user| ...} will only execute a query once, at the point of first use (not when assigned).
Some operations will always trigger a query though, for example $foos.count will always do a select count(*)... and others will depend on whether the scope has been loaded.
Lastly, further refinements on that scope would also hit the db, for example
$foos.order('blah').first
would trigger an new query each time it was used. It should be easy to see whether this is happening, although be wary when testing in irb: irb calls inspect on the results of expressions which causes the scope to be loaded.
$foos is just a variable so it will only hit the database when it gets assigned.

How to preview a delete_all or destroy_all query in Rails

You know the drill: some invalid data pops up in the production database and you have to get rid of it. You fire up your Rails console on the production server and type in the query:
Foo.where(bar: 'baz').all
You review the returned data and it's what you need to remove. Then you type:
Foo.where(bar: 'baz').destroy_all
And your heart stops for a second. You just want to see the query before it runs.
Is there any way to do that in Rails? I'm looking for a method similar to
Foo.where(bar: 'baz').to_sql
but the one that will return the DELETE query.
Just off the top of my head, you could run the console in sandbox mode and run the delete query to see the sql. The changes would just be rolled back on exit.
The problem is that destroy_all does not run a single SQL query. It iterates through a hash of objects, instantiates them, runs their callbacks, and then calls that object's destroy method. Rails has no built-in way of producing an array of these queries.
Cdesroisiers is right that you can test the query in sandbox mode, but the real problem is that you're second-guessing your decision to run delete_all, even though you've verified whatever data is being targeted.
Consider using an ActiveRecord versioning gem like PaperTrail if you're not willing to trust ActiveRecord to properly delete objects.
the destroy_all method is the same as doing:
Foo.where(bar: 'baz').each { |object| object.destroy }
So the sql becomes
DELETE FROM foo WHERE foo.id = your_objects_id_attribute
From docs:
def destroy_all(conditions = nil)
find(:all, :conditions => conditions).each { |object| object.destroy }
end

Resources