Unexpected behavior with ActiveRecord includes - sql

I'm using the AR includes method to execute a LEFT OUTER JOIN between objects User and Building, where a User may or may not have a Building association:
users = User.includes(:building).references(:buildings)
Since I'm using references, any associated Building objects will be eager loaded.
My expectation was that I would then be able to iterate through the list of users, and check whether a user had a building associated with them without triggering additional queries, but I see that in fact whenever I try to access the building property of a user that doesn't have one, AR makes another SQL call to try and retrieve that building (though on subsequent tries it will just return nil).
These queries are obviously redundant as the association would have been loaded during the initial join, and seems to defeat the whole purpose of eager loading with includes/references, as now I'm looking at N times the number of queries equal to the number of empty associations.
users.each do | user |
# This will trigger a new query when building is not present:
# SELECT "buildings".* FROM "buildings" WHERE "buildings"."address" = $1 LIMIT 1 [["address", "123 my street"]]
if user.building
puts 'User has building'
else
puts 'User has no building'
end
end
User class:
class User < ActiveRecord::Base
belongs_to :building, foreign_key: 'residence_id'
end
Is there a way to check the presence of the users' building association without triggering extra queries?
ON RAILS 4.2.0 / POSTGRES
UPDATE:
Thank you #BoraMa for putting together this test. Looks like we're getting different behavior across recent Rails versions:
OUTPUT (RAILS 4.2.0):
User 1 has building
User 2 has building
User 3 has no building
D, [2016-05-26T11:48:38.147316 #11910] DEBUG -- : Building Load (0.2ms) SELECT "buildings".* FROM "buildings" WHERE "buildings"."id" = $1 LIMIT 1 [["id", 123]]
User 4 has no building
OUTPUT (RAILS 4.2.6)
User 1 has building
User 2 has building
User 3 has no building
User 4 has no building
OUTPUT (RAILS 5.0.0)
User 1 has building
User 2 has building
User 3 has no building
User 4 has no building
Take aways:
This issue was limited to "dangling foreign keys (ie the residence_id
column is not nil but there is no corresponding building object)"
(THANKS #FrederickCheung)
The issue has been resolved as of Rails 4.2.6

Sounds like you got bit by a bug in Active Record, that was fixed in rails 4.2.3.
In the case where the column was nil Active Record already knows that it doesn't even need to try loading the associated object. The remaining cases were the ones impacted by this bug

Seems like a typo, please notice building instead of buildings: User.includes(:building).references(:buildings)
That should trigger the big query that uses the format of AS tX_rY for each association and table.

It seems that since rails 4.1 there are potential clashes with how just how implicit #includes should be, see the following open issue.
This code is all untested for syntax, but there would be two approaches I would try:
1/ Make the eager loading implicit
users = User.eager_load(:building).preload(:buildings)
2/ Separate out the two types of users, ones where the building is attached, meaning you don't even try and preload the building, removing the innefficiency.
users = User.includes(:building).where.not(residence_id: nil).references(:buildings)
users.each do | user|
puts "User has building: #{user} #{user.building}"
end
# No additional references needed to be eager-loaded.
users = User.where(residence_id: nil)
users.each do | user |
puts "#{User} has no building."
end

Related

Difference between update and update_attributes

In Rails 5, what is the difference between update and update_attributes methods. I'm seeing the following results for both the methods
Returns true/false
Checking for active record validation
Call backs are triggered
and also regarding update method a new thing was introduced in active record relation. I'm not able to understand it. What is the difference?
Moreover are we using update_attributes in Rails 5. It's not there in active record documentation.
I'm confused with all update methods. I need clarity
As of Rails 4.0.2, #update returns false if the update failed. Before Rails 4.0.2, #update returned the object that got updated. The main difference therefore was the return value. After this change, #update_attributes is just an alias of #update. It seems there are talks to deprecate #update_attributes in Rails 6 which is not released yet.
https://github.com/rails/rails/pull/31998
https://github.com/rails/rails/commit/5645149d3a27054450bd1130ff5715504638a5f5
From the rails 5 files it seems to me update can be used to update multiple objects(array of records) but update_attributes only work on single records otherwise both are same
From rails core files for update_attributes:
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.
This method raises an ActiveRecord::ActiveRecordError if the
attribute is marked as readonly.
def update_attribute(name, value)
name = name.to_s
verify_readonly_attribute(name)
public_send("#{name}=", value)
save(validate: false)
end
For Update
Updates an object (or multiple objects) and saves it to the database, if validations pass.
The resulting object is returned whether the object was saved successfully to the database or not.
==== Parameters
+id+ - This should be the id or an array of ids to be updated.
+attributes+ - This should be a hash of attributes or an array of hashes.
==== Examples
# Updates one record
Person.update(15, user_name: "Samuel", group: "expert")
# Updates multiple records
people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
Person.update(people.keys, people.values)
# Updates multiple records from the result of a relation
people = Person.where(group: "expert")
people.update(group: "masters")
Note: Updating a large number of records will run an UPDATE
query for each record, which may cause a performance issue.
When running callbacks is not needed for each record update,
it is preferred to use {update_all}[rdoc-ref:Relation#update_all]
for updating all records in a single query.
def update(id, attributes)
if id.is_a?(Array)
id.map { |one_id| find(one_id) }.each_with_index { |object, idx|
object.update(attributes[idx])
}
else
if ActiveRecord::Base === id
raise ArgumentError,
"You are passing an instance of ActiveRecord::Base to `update`. " \
"Please pass the id of the object by calling `.id`."
end
object = find(id)
object.update(attributes)
object
end
end
When we are working with update_column that time update is done on the database level there is no any contact with the rails ORM so whatever logic we have implemented like callbacks and validations all will be waste and wont be useful as this is going to be bypassed.
I found this article explained really well in just 30 seconds.
.update
Use update when you want to return false, for example in an if/else:
if record.update(params)
display_success
else
react_to_problem
end
.update!
Use update! when you want an error (for example: to avoid erroring silently, which could be very bad if an error was unexpected and you needed to know about it to fix it!):
record.update!(params) # raises is invalid
'update' respects the validation rules on model, while 'update_attributes' ignores validations.

Rails - Proper associations/data model for content display "cooldown"

I have a user model and a content model. When a user views a piece of content, I need to make sure the user does not see that content again for say, 48 hours.
What's the Rails way to model this out? I'd like to have a table with a user_id, content_id, and a timestamp that the view was recorded, then have a worker clear out entries with timestamps > 2 days. This way when a user requests more content, I can filter out content that has an entry where user_id and content_id match.
Don't think it should matter, but I'm using MySQL with Rails 3.2.
I think you can do the following in your model.
class User < ActiveRecord::Base
...
has_many :contents, -> { where(["EXTRACT(HOUR FROM last_viewed_at) > ? OR last_viewed_at IS ?", 48, nil)}
end
I used or condition to make it nil because when it is initialized or new record created so that user can be able to see it.
I am not sure how you are using your worker.
Please suggest me if I am missing anything. I am not intended to answer accurately, rather trying a way I can realize what is possible.

Finding Records using Rails without a Match (Fault without Fault Cleared)

I'm attempting to write a site in Rails where a user in a manufacturing plant can see what devices are failing. The program storing the alarm data stores one entry when a device faults, and then stores another entry when the device gets fixed. The entries are linked only by having the same value in the EventAssociationID column. How might I write a named scope in Rails to check which faults have been fixed and which ones haven't?
I wasn't able to do it in a named scope, however, I was able to define a method for the model that solved the problem:
def inAlarm
return ConditionEvent.count(:all, :conditions => ['EventAssociationID = ?', self.EventAssociationID]) == 1
end

How to get deeply nested errors to get to my REST API?

First, some background:
I have a Company model, a Project model and a Task model. A Project belongs to a company and a Task belongs_to a Project.
The Project model holds several attributes: company_id, date. These attributes uniquely identify a project
I am letting the users create a task by API by POSTing to a URL that contains the details necessary to identify the Project. For example:
POST /projects/<comnpany_name>/<date>/tasks/
In order to make life easier for the users, in case there is no project with the given details, I'd like to create the project on the fly by the given details, and then to create the task and assign it to the project.
...And my problem is:
When there is a problem to create the project, let's say that the company name is not valid, what is the right way to return the error message and communicate to the user?
I'll explain what I mean: I added a create_by_name_and_company_name method to the Project:
def self.create_by_name_and_company_name(name, company_name)
if company = Company.find_by_name(company_name)
project = Project.create(company_id: company.id,
name: name)
else # cannot create this project, trying to communicate the error
project = Project.new(name: name)
project.errors.add(:company, 'must have a valid name')
end
company
end
I was hoping that by returning an unsaved Company object, with errors set, will be a good way communicate the error (This is similar to how rails work when there's a validation error).
The problem is that when calling valid? on the company object, it removed the error I wrote there and adds the regular validation errors (in this case, company can't be blank).
And a bonus question...
And there is a conceptual problem as well: since I'm creating a model by providing parameters that are being used to create the actual attributes, they doesn't always map nicely to the errors[:attr] hash. In this case it is not so bad and I'm using the company field for the company name parameter, but I guess this can get messier when the parameters provided to the create method are less similar to the model attributes.
So what is the preferred approach to tackle that problem? Is there something basically wrong with that approach? if so, what is the preferred approach?
About overriding the default rails validation error message, you need to write your validation constraint like this:
validates_presence_of :name, :message => "must be a valid name"
I figure that it is best to avoid such nesting and stick to a shallower API.

finding id of nested attribute

I am very new to RoR so this may be very fundamental. My structure keeps getting a level deeper and I can't figure out how to find the id anymore.
First you have a Company which can have many Users. Users sign in and are authenticated and the current_user is saved in a cookie with the Session.
Since the User has one Company I can always find the Company.id through the current_user.
Next a Company has many Farms. In farms create I can get the company id from the user cookie and the farm id is new so that works, and in farm show Rails knows which farm it is supposed to show. So that level works.
Now I want to add that a Farm has many Blocks. I am adding Blocks through the associated Farm show page, but the Blocks_controller doesn't know what farm page it is on (as far as I can tell, if it can any info is appreciated).
Here is the FarmsController create that works:
def create
company_id = current_user.company_id
#company = Company.find(company_id)
#farm = #company.farms.build(params[:farm])
if #farm.save
flash[:success] = "farm created"
redirect_to root_path
else
render 'pages/home'
end
end
And this code just complains that it doesn't know what id I am talking about:
BlocksController
def create
#farm = Farm.find(params[:id])
#block = #farm.blocks.build(params[:block])
end
This is displaying on the associated Farm show page, so if there is a way to capture the id I would love to know what it is.
Thank you for your time.
The three easiest ways to get that id is to:
Pass in that farm_id using a hidden form field. When creating the link to your blocks/new form just pass in the farm_id ie use a path like new_blocks_path(:id => #farm.id) inside your blocks controller you will want to make sure that the farm_id is set on the Block model.
def new
#block = new Block
#block.farm_id = params[:farm_id]
end
Then if you are using form for the farm_id field (which should probably be of type hidden), it should contain the right id. Now change the first line in the "create" block method to
#farm = Farm.find(params[:block][:farm_id])
You can combine the process of adding the blocks and the farms using nested forms. Take a look at http://railscasts.com/episodes/196-nested-model-form-part-1 for how to do this.
You can use nested RESTful resources to make sure that within the blocks controller you always have access to the farm id. For more information about how to do this try take a look at http://railscasts.com/episodes/139-nested-resources