How to prevent multiple queries when updating a record? - ruby-on-rails

I have a table customers with a couple of belong_to's: a customer belongs to a country, a sector, and a type.
I got the following result, when updating a customer:
> Customer.first.update(notes: "Some extra notes")
Customer Load (1.4ms) SELECT `customers`.* FROM `customers` ORDER BY `customers`.`id` ASC LIMIT 1
Country Load (1.5ms) SELECT `countries`.* FROM `countries` WHERE `countries`.`id` = '26' LIMIT 1
Sector Load (1.6ms) SELECT `sectors`.* FROM `sectors` WHERE `sectors`.`id` = 89 LIMIT 1
Type Load (1.6ms) SELECT `types`.* FROM `types` WHERE `types`.`id` = 8 LIMIT 1
Customer Update (0.3ms) UPDATE `customers` SET `notes` = "Some extra notes", `updated_at` = '2019-06-27 08:52:56' WHERE `customers`.`id` = 1
I think the extra queries are there to check if the relations are still valid. But it's extremely slow when mass updating all customers. How can I prevent those extra queries?

You can use update_attribute instead, that doesn't run any validations on your model.
Customer.first.update_attribute(:notes, 'Some extra notes')
Read more about update_attribute and other nice methods
Updates a single attribute and saves the record. This is especially useful for boolean flags on existing records. Also note that
Validation is skipped.
Callbacks are invoked.
updated_at/updated_on column is updated if that column is
available.
Updates all the attributes that are dirty in this object.

Your can use update_columns to skip the callbacks if you really sure you don't need it.
try
Customer.first.update_columns(notes: "Some extra notes")

Related

Rails 4: Using PostgreSQL function in order causes error in query due to the includes table not being joined

I found an odd problem with Rails 4 Active Record queries where the includes table is not joined if I use a PostgreSQL function in the order. This same query works fine if I remove the PostgreSQL function.
This works fine...
Widget.includes(:sprocket).order("sprockets.name").all
This fails because the includes relationship is not joined...
Widget.includes(:sprocket).order("lower(sprockets.name)").all
Notice the only thing different is the lower(sprockets.name).
I know I can add .references like this...
Widget.includes(:sprocket).references(:sprockets).order("lower(sprockets.name)").all
That will work, but then what is the purpose of the includes?
I've found that replacing includes with eager_load works also, but again, what is the purpose of includes then?
Widget.eager_load(:sprocket).order("lower(sprockets.name)").all
In Rails 3, just using includes works fine. I guess I have a lot of code to change, but just hoping there is an easier fix?
Thank you
Suppose you need to get the user name of first five post. You quickly write the query below and go enjoy your weekend.
posts = Post.limit(5)
posts.each do |post|
puts post.user.name
end
Good. But let's look at the queries
Post Load (0.5ms) SELECT `posts`.* FROM `posts` LIMIT 5
User Load (0.3ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
User Load (0.3ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
User Load (0.3ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 2 LIMIT 1
User Load (0.3ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 2 LIMIT 1
User Load (0.3ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
1 query to fetch all posts and 1 query to fetch users for each post results in a total of 6 queries. Check out the solution below which does the same thing, just in 2 queries:
posts = Post.includes(:user).limit(5)
posts.each do |post|
puts post.user.name
end
#####
Post Load (0.3ms) SELECT `posts`.* FROM `posts` LIMIT 5
User Load (0.3ms) SELECT `users`.* FROM `users` WHERE `users`.`id` IN (1, 2)
There’s one little difference. Add includes(:posts) to your query, and problem solved. Quick, nice, and easy.
But don’t just add includes in your query without understanding it properly. Using includes with joins might result in cross-joins depending on the situation, and you don’t need that in most cases.
If you want to add conditions to your included models you’ll have to explicitly reference them. For example:
User.includes(:posts).where('posts.name = ?', 'example')
Will throw an error, but this will work:
User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
Note that includes works with association names while references needs the actual table name.

Rails validates uniqueness while updating object? - very strange

So, I have some simple User Model, and form for updating password.
#user.update_attributes(:password=>params[:password])
But this didn't work, and I figured out:
User Load (1.0ms) SELECT "users".* FROM "users" WHERE "users"."auth_token" = 'z7KU4I0IXLjiRMpdF6SOVQ' LIMIT 1
User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."password_reset_token" = 'aMOjTN0ikPUOJo2JMVoDtQ' LIMIT 1
(0.0ms) BEGIN
User Exists (1.0ms) SELECT 1 AS one FROM "users" WHERE (LOWER("users"."email") = LOWER('somemail#mail.ru') AND "users"."id" != 1) LIMIT 1
(0.0ms) ROLLBACK
Redirected to http://localhost:3000/edit_user_by_reset?reset_token=aMOjTN0ikPUOJo2JMVoDtQ
By 3rd select I can tell, that here is uniqueness validation failed! And that is reason for ROLLBACK.
But it doesn't make sense, of course there is such row in DB, as it is UPDATE action. What should I do? I don't want pass :validate=>false here.
If you want to update password field only, you should not use mass_assignment method update_attributes, you should use update_attribute(:password, params[:user][:password]).
There is possible error with your params[:password] hash: if you use form_for #user then you should have params[:user][:password] and in common params[:user] for other fields.
You should check if the given user is valid (did you save him to DB without validation).
Check that your object is valid before you call update_attributes.
#user.valid?
I spent a long time trying to debug a similar issue only to find that my data was bad to start with. It had nothing to do with update_attributes. After modifying the culprit DB entry, my model became valid again after calling #user.find(id) and update_attributes worked as expected.

What is the difference between using .exists?, and .present? in Ruby?

I want to make sure I'm using them for the correct occasion and want to know of any subtleties. They seem to function the same way, which is to check to see if a object field has been defined, when I use them via the console and there isn't a whole lot information online when I did a google search. Thanks!
To clarify: neither present? nor exists? are "pure" ruby—they're both from Rails-land.
present?
present? is an ActiveSupport extension to Object. It's usually used as a test for an object's general "falsiness". From the documentation:
An object is present if it’s not blank?. An object is blank if it’s false, empty, or a whitespace string.
So, for example:
[ "", " ", false, nil, [], {} ].any?(&:present?)
# => false
exists?
exists? is from ActiveResource. From its documentation:
Asserts the existence of a resource, returning true if the resource is found.
Note.create(:title => 'Hello, world.', :body => 'Nothing more for now...')
Note.exists?(1) # => true
The big difference between the two methods, is that when you call present? it initializes ActiveRecord for each record found(!), while exists? does not
to show this I added after_initialize on User. it prints: 'You have initialized an object!'
User.where(name: 'mike').present?
User Load (8.1ms) SELECT "users".* FROM "users" WHERE "users"."name" = $1 ORDER BY users.id ASC [["name", 'mike']]
You have initialized an object!
You have initialized an object!
User.exists?(name: 'mike')
User Exists (2.4ms) SELECT 1 AS one FROM "users" WHERE "users"."name" = $1 ORDER BY users.id ASC LIMIT 1 [["name", 'mike']]
There is a huge difference in performance, and .present? can be up to 10x slower then .exists? depending on the relation you are checking.
This article benchmarks .present? vs .any? vs .exists? and explains why they go from slower to faster, in this order.
In a nutshell, .present? (900ms in the example) will load all records returned, .any? (100ms in the example) will use a SQLCount to see if it's > 0 and .exists? (1ms in the example) is the smart kid that uses SQL LIMIT 1 to just check if there's at least one record, without loading them all neither counting them all.
SELECT COUNT(*) would scan the records to get a count.
SELECT 1 would stop after the first match, so their exec time would be very different.
The SQL generated by the two are also different.
present?:
Thing.where(name: "Bob").present?
# => SELECT COUNT(*) FROM things WHERE things.name = "Bob";
exists?:
Thing.exists?(name: "Bob")
# => SELECT 1 AS one from things WHERE name ="Bob" limit 1;
They both seem to run the same speed, but may vary given your situation.
You can avoid database query by using present?:
all_endorsements_11 = ArtworkEndorsement.where(user_id: 11)
ArtworkEndorsement Load (0.3ms) SELECT "artwork_endorsements".* FROM "artwork_endorsements" WHERE "artwork_endorsements"."user_id" = $1 [["user_id", 11]]
all_endorsements_11.present?
=> true
all_endorsements_11.exists?
ArtworkEndorsement Exists (0.4ms) SELECT 1 AS one FROM "artwork_endorsements" WHERE "artwork_endorsements"."user_id" = $1 LIMIT 1 [["user_id", 11]]
=> true

active record relations – who needs it?

Well, I`m confused about rails queries. For example:
Affiche belongs_to :place
Place has_many :affiches
We can do this now:
#affiches = Affiche.all( :joins => :place )
or
#affiches = Affiche.all( :include => :place )
and we will get a lot of extra SELECTs, if there are many affiches:
Place Load (0.2ms) SELECT "places".* FROM "places" WHERE "places"."id" = 3 LIMIT 1
Place Load (0.3ms) SELECT "places".* FROM "places" WHERE "places"."id" = 3 LIMIT 1
Place Load (0.8ms) SELECT "places".* FROM "places" WHERE "places"."id" = 444 LIMIT 1
Place Load (1.0ms) SELECT "places".* FROM "places" WHERE "places"."id" = 222 LIMIT 1
...and so on...
And (sic!) with :joins used every SELECT is doubled!
Technically we cloud just write like this:
#affiches = Affiche.all( )
and the result is totally the same! (Because we have relations declared). The wayout of keeping all data in one query is removing the relations and writing a big string with "LEFT OUTER JOIN", but still there is a problem of grouping data in multy-dimentional array and a problem of similar column names, such as id.
What is done wrong? Or what am I doing wrong?
UPDATE:
Well, i have that string Place Load (2.5ms) SELECT "places".* FROM "places" WHERE ("places"."id" IN (3,444,222,57,663,32,154,20)) and a list of selects one by one id. Strange, but I get these separate selects when I`m doing this in each scope:
<%= link_to a.place.name, **a.place**( :id => a.place.friendly_id ) %>
the marked a.place is the spot, that produces these extra queries.
UPDATE 2:
And let me do some math. In console we have:
Affiche Load (1.8ms) SELECT affiches.*, places.name FROM "affiches" LEFT OUTER JOIN "places" ON "places"."id" = "affiches"."place_id" ORDER BY affiches.event_date DESC
<VS>
Affiche Load (1.2ms) SELECT "affiches".* FROM "affiches"
Place Load (2.9ms) SELECT "places".* FROM "places" WHERE ("places"."id" IN (3,444,222,57,663,32,154,20))
Comes out: 1.8ms versus 4.1ms, pretty much, confusing...
Something is really strange here because :include option is intended to gather place_id attribute from every affiche and then fetch all places at once using select query like this:
select * from places where id in (3, 444, 222)
You can check that in rails console. Just start it and run that snippet:
ActiveRecord::Base.logger = Logger.new STDOUT
Affiche.all :include => :place
You might be incidentally fetching affiches without actually including places somewhere in your code and than calling place for every affiche making rails to perform separate query for every one of them.

How can write this code which execute only one query?

How to write this code which execute only one query instead of two and fire validations also ? update_all bypasses all validations defined in model.
model = ModelName.find(params[:id])
success = model.update_attribute(:column_name, nil)
You can not. Running the validations does include at least one step: Loading the database record into a ruby object (which takes one query). Updating the database of course takes another query. So in any case, you will have two queries for your task.
You can use the update method:
# Updates one record
User.update(1, :name => 'testtesttest')
http://apidock.com/rails/ActiveRecord/Relation/update
but it's still two queries as #mosch said.
User Load (0.0ms)[0m SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1
AREL (0.0ms)[0m [1mUPDATE "users" SET "name" = 'testtesttest', "updated_at" = '2011-05-03 11:41:23.000000' WHERE "users"."id" = 1

Resources