How can write this code which execute only one query? - ruby-on-rails

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

Related

How to prevent multiple queries when updating a record?

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")

Rails `find_by` returning huge ID

Curious if anyone knows the intricacies of find_by since I've checked documentation and been unable to find info.
I know that find is used to find by primary keys like:
#user = User.find(params[:id]), returning the correct user.
Before I corrected my code it was #user = User.find_by(params[:id]) and returned a user with an ID way above the number of users in my DB.
Can anyone help me understand what is happening under the hood? What does find_by search by default when a parameter is omitted that is returning this strange user object?
find_by_field(value) is equivalent to where(field: value) but is not supposed to be used without appending a field name to the method like you mentioned. Moreover it returns only the first matching value. For example instead of doing User.where(name: 'John').limit(1) you can use: User.find_by_name 'John'.
On my side, using find_by with postgresql raises an error, when find_by_id does work:
User.find_by(1)
SELECT "users".* FROM "users" WHERE (1) ORDER BY "users"."email" ASC LIMIT 1
PG::DatatypeMismatch: ERROR: argument of WHERE must be type boolean, not type integer
User.find_by_id 1
SELECT "users".* FROM "users" WHERE "users"."id" = 1 ORDER BY "users"."email" ASC LIMIT 1
<User id: 1, ...
User.where(id: 1) # note that there is no LIMIT 1 in the generated SQL
SELECT "users".* FROM "users" WHERE "users"."id" = 1 ORDER BY "users"."email" ASC
You can use gem query_tracer to see the generated SQL or check this thread.
Please take a look here.
Here is an excerpt for 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.
Post.find_by name: 'Spartacus', rating: 4
Post.find_by "published_at < ?", 2.weeks.ago
Is that what you were looking for?
UPDATE
User.find_by(3) is equivalent to User.where(3).take
Here's the output from the console
pry(main)> User.where(3).take
#=> User Load (0.3ms) SELECT `users`.* FROM `users` WHERE (3) LIMIT 1
It's look like rails return to you an id of object in memory. It's like query User.find(params[:id]).object_id. But why it's happens? I try did same on my app with 4.2.4 version and all goes fine

Compare associations on different ActiveRecords without fetching from the DB

I would like to be able to compare associated records on ActiveRecords, without actually fetching from the database. The following will do that comparison, but hits the DB when I make the comparison
employee1 = Employee.find_by(name: 'Alice')
DEBUG Employee Load (92.0ms) SELECT "employees".* FROM "employees" WHERE "employees"."name" = 'Alice' LIMIT 1
employee2 = Employee.find_by(name: 'Bob')
DEBUG Employee Load (92.0ms) SELECT "employees".* FROM "employees" WHERE "employees"."name" = 'Bob' LIMIT 1
employee1.manager == employee2.manager
DEBUG Employee Load (697.9ms) SELECT "employees".* FROM "employees" WHERE "employees"."id" = $1 ORDER BY "employees"."id" ASC LIMIT 1 [["id", 53]]
DEBUG Employee Load (504.1ms) SELECT "employees".* FROM "employees" WHERE "employees"."id" = $1 ORDER BY "employees"."id" ASC LIMIT 1 [["id", 53]]
=> true
I can compare the values of the foreign columns directly, but that's less idiomatic and can be difficult to refactor later on:
employee1.manager_id == employee2.manager_id
=> true
EDIT: I've added my own answer as a solution to this question below
If you know you're going to be needing/using the Manager for the Employee during the operation, you can make sure you load that object when the employee is loaded, that will prevent the trip back to the database:
employee1 = Employee.includes(:manager).find_by(name: 'Alice')
employee2 = Employee.includes(:manager).find_by(name: 'Bob')
employee1.manager == employee2.manager
=> true # database hit not needed...
That or just compare the IDs, but make a helper method on Employee like
class Employee
def same_manager?(other_employee)
other_employee.manager_id == self.manager_id
end
end
At least that way it's given a name and the operation within it makes sense in context.
I'm going to post my own answer for the time being. I've monkey-patched Active Record to include a new method, compare_association, which allows you to compare foreign objects on different ActiveRecords without hitting the DB.
module ActiveRecord
class Base
def compare_association(association_name, record)
association = self.class.reflect_on_association(association_name)
foreign_key = association.foreign_key
return self.read_attribute(foreign_key) == record.read_attribute(foreign_key)
end
end
end
Example:
# Compare the 'manager' association of `employee1` and `employee2`
# Equivalent to `employee1.manager_id == employee2.manager_id` but without
# referencing the DB columns by name.
employee1.compare_association(:manager, employee2)

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

Resources