ActiveRecord requerying depending on order of operations - ruby-on-rails

New to Ruby and Rails so forgive me if my terminology is off a bit.
I am working on optimizing some inherited code and watching the logs I am seeing queries repeat themselves due to lines like this in a .rabl file:
node(:program) { |user| (!user.programs.first.nil?) ? user.programs.first.name : '' }
user and program are both active record objects
Moving to the rails console, I can replicate the problem, but I can also get the expected behavior, which is only one query:
>u = User.find(1234)
User Load (0.3ms) SELECT `users`.* FROM `users` WHERE [...]
> (!u.programs.first.nil?) ? u.programs.first.name : ''
Program Load (0.2ms) SELECT `programs`.* FROM `programs` [...]
Program Load (0.3ms) SELECT `programs`.* FROM `programs` [...]
=> "Output"
Note that repeating the ternary statement in the console will always give me 2 queries.
I can get the expected behavior like so:
> newu = User.find(12345)
User Load (3.8ms) SELECT `users`.* FROM `users` WHERE [...]
> newu.programs
Program Load (0.6ms) SELECT `programs`.* FROM `programs` [...]
> (!newu.programs.first.nil?) ? newu.programs.first.name : ''
=> "Output"
Repeating the ternary statement now won't requery at all.
So the question is: why does calling newu.programs change the behavior? Shouldn't calling u.programs.first.nil? also act to load all the program records in the same way?

With an association, first is not sugar for [0].
If the association is loaded, then it just returns the first element of the array. If it is not loaded, it makes a database query to load just that one element. It can't stick that in the association cache (at least not without being smarter), so the next query to first does the query again (this will use the query cache if turned on)
What Rails is assuming is that if the association is big, and you are only using one element of it then it would be silly to load the whole thing. This can be a little annoying when this isn't the case and you are just using the one item, but you're using it repeatedly.
To avoid this you can either assign the item to a local variable so that you do genuinely only call first once, or do
newu.programs[0]
which will load the whole association (once only) and return the first element.
Rails does the same thing with include?. Instead of loading the whole collection, it will run a query that tests whether a specific item is in the collection (unless the collection is loaded)

Related

Rails Inconsistent database results

I have a Rails application where there is a request to create elements on a table and another one, waiting, that reads if such element was created or not.
Right now, I check the live Data and the waiting process never sees any new record for the table.
Any ideas how to force a reconnection or anything that will update the model reference?
The basic idea:
One user, through a POST, is creating a record (that i can see directly on the database).
Another piece of code, that was running before the requests, is waiting for that record, but does not find it.
I'd appreciate any insights.
The waiting request is probably using the ActiveRecord cache. Rails caches queries for the duration of a request, so if you run the exact same query multiple times in the same request, it will only hit the database the first time.
You can simulate this in the console:
Entity.cache { Entity.first; Entity.first }
In the logs, you'll see something like this (notice the second request says CACHE):
[2018-12-06T10:51:02.436 DEBUG (1476) #] Entity Load (4.9ms) SELECT "entities".* FROM "entities" LIMIT 1
[2018-12-06T10:51:02.450 DEBUG (1476) #] CACHE (0.0ms) SELECT "entities".* FROM "entities" LIMIT 1
To bypass the cache, you can use:
Entity.uncached { Entity.where(key: #key)&.last }
Using uncached will disable the cache inside the block, even if the enclosing scope is running inside a cached block.
It seems that, for a running Rails application, if a running piece of code is looking for anything that has been updated by a new request, after the first began its execution, the updated data would not show, using active_record models.
The solution: Just run a raw query.
sql = "SELECT * FROM entities WHERE key = '"+key+"' ORDER BY ID DESC LIMIT 1;"
records_array = ActiveRecord::Base.connection.execute(sql).values
I know it's a huge oversimplification and that there is probably an underlying problem, but it did solved it.

many cache calls in development log - how to prevent them

I have a Rails 4.2 app that has literally hundreds of calls like this:
CACHE (0.0ms) SELECT "albums".* FROM "albums" WHERE (albumable_type='ItemProfile' and albumable_id=33333) ORDER BY "albums"."id" ASC LIMIT 1
CACHE (0.0ms) SELECT COUNT(*) FROM "assets" WHERE "assets"."album_id" = $1 AND "assets"."is_enabled" = $2 [["album_id", 19182], ["is_enabled", true]]
CACHE (0.0ms) SELECT "albums".* FROM "albums" WHERE (albumable_type='ItemProfile' and albumable_id=33333) ORDER BY "albums"."id" ASC LIMIT 1
CACHE (0.0ms) SELECT "assets".* FROM "assets" WHERE "assets"."album_id" = $1 AND "assets"."is_enabled" = $2 ORDER BY "assets"."position" ASC LIMIT 1 [["album_id", 19182], ["is_enabled", true]]
Two questions:
Are these calls actually being made (my understanding is no due to "CACHE")?
Is there a way to tell Rails that it already has this info and doesn't need to check for it? Like hundreds of times seems liek a bug on my end
edit #1
so the problem is that there is stuff like the following:
def instore_image
album.enabled_assets.first
end
and
def image_600w
Rails.logger.info("#9797 here is instore_image_url")
instore_image.asset.url(:fixed_width600)
Rails.logger.info("#9898 here is instore_image_url")
end
The image_600w is calling a round of sql calls (whether cached or not). It seems like to prevent this from happening I need to set an instance variable on the object.
You should check out the section on Caching with Rails in Rails Guides:
1.5 SQL Caching
Query caching is a Rails feature that caches the result set returned
by each query so that if Rails encounters the same query again for
that request, it will use the cached result set as opposed to running
the query against the database again.
So these queries are actually being made by your application, however ActiveRecord intercepts these queries and prevents SQL query by directly delivering from Query cache.
Is there a way to tell Rails that it already has this info and doesn't need to check for it? Like hundreds of times seems liek a bug on my end.
Yes, Rails is already doing the best it can by reusing cached query results. You can use bullet to identify N+1 queries and other similar inefficient database usage patterns in your application.

Rails Model.select(attribute * attribute AS new_attribute) - new_attribute not in hash?

I was wondering if there was any way in rails to return a new_attribute in a select as statement in rails.
For simplified example Books.select("'tuds' as new_attribute").first where new_attribute isn't in the DB, just returns a bunch of empty active record objects.
Seems like this should work, but I'm not having any luck. Any thoughts!?
Thanks!
-Mario
`
UPDATE: I'm a goof. I wasn't actually looking at the actual object and was just looking at the log in my console.
Works for me using Rails 3.2:
irb(main):001:0: User.select('full_name as whatever').first.whatever
User Load (0.5ms) SELECT id, full_name as whatever FROM `users` LIMIT 1
=> "Zap Brannigan"
Is this what you want to achieve?

Activerecord can't find model by attribute, even though the query is correct

I have a Model called Invitation which has an attribute called code. The application's goal is to find the correct invitation with a code that is entered somewhere.
My problem is, however, that even though the input is correct, ActiveRecord can't seem to find any results while querying in the database. I've created this small test to illustrate the problem:
ruby-1.9.2-p290 :003 > code = Invitation.first.code
Invitation Load (0.4ms) SELECT "invitations".* FROM "invitations" LIMIT 1
=> "86f50776bf"
So at this point I've loaded this invitation's code in a variable
ruby-1.9.2-p290 :004 > i = Invitation.where(:code => code)
Invitation Load (0.2ms) SELECT "invitations".* FROM "invitations" WHERE "invitations"."code" = '86f50776bf'
=> []
And the response of the query is an empty array, even though the code comes straight from the database. When using code == Invitation.first.code to see if the values are equal, it returns true. I already checked both the Ruby and database's data types, they're all Strings.
What can cause this? and how can I fix this?
Based on your comment, it could be the case that the column is not VARCHAR but CHAR, or it contains trailing spaces that are being trimmed off by the ActiveRecord ORM layer or the database driver. 'foo' and 'foo ' are not equivalent, but they are LIKE enough to match.
You may want to switch that column to variable length, or to adjust your query to test: RTRIM(code)=?
I found the solution when I stumbled upon this answer:
In Ruby 1.9, all strings are now encoded. By default, all strings should be UTF-8, however, SecureRandom.hex(30) returns an encoding of ASCII-8BIT.
Adding .force_encoding('UTF-8') to the key when it's being executed solves the problem :)
#Marco,
How you declare the code variable? As String?
Example:
code = "86f50776bf"
or
code = '86f50776bf'
?

Slow find with including multiple associations

In a Rails controller action I call
#grid = Grid.find(params[:id], :include => [:grid_properties, :grid_entities => [:entity => :properties]])
Unfortunately it takes really long. Benchmark.realtime (in development mode) tells me 1.8812830448150635, so about 2 seconds (Rails 3.0.7, Postgres 8.4).
When turning on the SQL logging I get the following for this line of code:
Grid Load (0.9ms) SELECT "grids".* FROM "grids" WHERE "grids"."id" = 2 LIMIT 1
GridProperty Load (2.5ms) SELECT "grid_properties".* FROM "grid_properties" WHERE ("grid_properties".grid_id = 2) ORDER BY row_order
GridEntity Load (1.3ms) SELECT "grid_entities".* FROM "grid_entities" WHERE ("grid_entities".grid_id = 2) ORDER BY row_order
Entity Load (3.0ms) SELECT "entities".* FROM "entities" WHERE ("entities"."id" IN (28,7,3,6,25,11,2,12))
Property Load (6.4ms) SELECT "properties".* FROM "properties" WHERE ("properties".entity_id IN (28,7,3,6,25,11,2,12))
Every database access seems to be in the low millisecond range. Why does it take so long in the end?
I guess that the time is spent on creating the object by Ruby. How many Properties are in that Grid? The time of 'SELECT FROM properties' looks relatively high.
You could further investigate the problem, if you have some functions where you could place checkpoints - logger.debug "CHECKPOINT: #{Time.now} #{caller(0).first}"
Do you have any 'on_load' callbacks, maybe?

Resources