Does anyone know a way to determine if a Rails association has been eager loaded?
My situation: I have a result set where sometimes one of the associations is eager loaded, and sometimes it isn't. If it isn't eager-loaded, then I want to look up associations using ActiveRecord's find. If it is eager loaded, I want to use detect.
For example, say that I have a "has_many" array of shipping_info objects in my item model. Then:
If item is eager loaded, most efficient load is:
item.shipping_infos.detect { |si| si.region == "United States" }
If item isn't eager loaded, most efficient load is:
item.shipping_infos.where(region: "United States").first
But unless I know whether it is eager loaded, I don't know which code to call to get the record efficiently. If I use the first method when it wasn't eager loaded, then I have to look up more DB records than necessary. And if I use the second method when it was eager loaded, then my eager loaded objects are ignored.
Use .association(name).loaded? on a record.
For Rails < 3.1 use loaded_foo?.
(It is deprecated since Rails 3.1. See: https://github.com/rails/rails/issues/472.)
item.shipping_infos.loaded? will tell you.
I gotta say, though: this path leads to madness... before writing code that tests loaded? to decide between #detect and #find, make sure this instance really matters, relative to everything else that's going on.
If this isn't the slowest thing your app does, adding extra code paths adds unnecessary complexity. Just because you might waste a little database effort doesn't mean you need to fix it - it probably doesn't matter in any measurable way.
I'd suggest using item.association_cache.keys that will provide a list of the eager loaded associations. So you item.association_cache.keys.include?(:name_of_association)
association_cached? might be a good fit:
item.association_cached?(:shipping_infos)
You can detect whether or not a single association has been loaded with loaded_foo?. For example, if shipping_info was a belongs_to association, then item.loaded_shipping_info? will return true when it's been eager-loaded. Oddly, it appears to return nil (rather than false) when it hasn't been loaded (in Rails 2.3.10 anyway).
Solution to this problem should be foo.association(:bla).loaded?, BUT it works incorrectly - it checks and marks association as dirty:
class Foo; has_one :bla, :autosave => true end
foo.association(:bla).loaded? #=> false
foo.save # saves foo and fires select * from bla
So I've added following extension to ActiveRecord:
module ActiveRecord
class Base
def association_loaded?(name)
association_instance_get(name).present?
end
end
end
and now:
class Foo; has_one :bla, :autosave => true end
foo.association_loaded?(:bla) #=> false
foo.save # saves foo
Have a look at the Bullet gem.. This will tell you when you should and should not use eager loading.
Related
I'm wanting to add a function to a model of mine to consolidate two records. If I have PlayerA and PlayerB for instance, and want to destroy PlayerB but assign all his current records to PlayerA. I was going to make a method that would look up all his records and replace with PlayerA's id like so...
#...
Note.where(player_id: PlayerB.id).update_all(player_id: PlayerA.id)
HighScores.where(player_id: PlayerB.id).update_all(player_id: PlayerA.id)
Friends.where(player_id: PlayerB.id).update_all(player_id: PlayerA.id)
#...
Now if I do it this way I would have to remember to add one for each association that gets created in the future for the Player's Model. Is there a way I could have this done dynamically, through perhaps some methods to pull all associations, so I wouldn't have to manually add for each new association?
Seems like a right place for ActiveRecord::Reflection.
Player.reflect_on_all_associations # returns an array of all associations
Player.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
So may be something like this could work for you:
associations = Player.reflect_on_all_associations(:has_many)
associations.each do |association|
associated_class = association.class_name.constantize # e.g. Note
foreign_key = association.foreign_key # e.g. player_id
associated_class.where(foreign_key => PlayerB.id).
update_all(foreign_key => PlayerA.id))
end
Note that this could be more tricky for complex associations like has_and_belongs_to_many though. For the sake of simplicity I do not cover that case here, but you could implement some conditional logics based on association.class which is a one of ActiveRecord::Reflection subclasses (BelongsToReflection, HasAndBelongsToManyReflection, HasManyReflection, HasOneReflection).
Not sure if there is a fast way for this, would love to hear if there is, but I did it the long way. I kept the old data around with an "imported" state so that it can be viewed when desired without getting in the way, and created a new version of each record, for each association type, with the new player's details.
So, I've inherited a medium sized legacy app that was quickly built to address a customer's need. Luckily it was well written for the most part but because it was rushed there are a lot of places that ActiveRecord relations were not eager loaded. As the traffic of the site starts to increase, these n+1 bugs are really starting to surface.
My question is how can I easily find these problems and write something to the logs or generate a report, anything to alert me and other devs while developing?
What I have so far is an object that can wrap around any view or template that is being rendered:
class EagerLoadIssueLogger
def track(&block)
# Start tracking eager load issues
result = block.call
# Stop tracking eager load issues
result
end
end
Then call in a layout or view like so:
<body>
<%= #eager_load_tracker.track { yield } %>
</body>
My issue is I can't figure out how to determine when an association is called that hasn't been eager loaded. I know there is a method loaded? that I can use to check any one relation like:
#team.users.loaded? # returns true or false
but I want to check any relation loaded while in my tracker block and if not loaded, log it, otherwise, good job, just ignore. I know I can probably accomplish this by monkey-patching into ActiveRecord::Relation or some other ActiveRecord class/module but have been fruitlessly searching for where to get started.
Any ideas?
The bullet gem can be used to log n+1 queries. It works pretty well for me.
I'm wanting to use UUIDs in an app I'm building and am running into a bit of a problem. Due to UUIDs (v4) not being sortable because they're randomly generated, I'm trying to override ActiveRecord::Base#first, but Rails isn't too pleased with that. It yells at me saying ArgumentError: You tried to define a scope named "first" on the model "Item", but Active Record already defined a class method with the same name. Do I have to use a different method if I want to sort and have it sort correctly?
Here's the sauce:
# lib/sortable_uuid.rb
module SortableUUID
def self.included(base)
base.class_eval do
scope :first, -> { order("created_at").first }
scope :last, -> { order("created_at DESC").first }
end
end
end
# app/models/item.rb
class Item < ActiveRecord::Base
include SortableUUID
end
Rails 4.2, Ruby 2.2.2
Reference:
http://blog.nakonieczny.it/posts/rails-support-for-uuid/
http://linhmtran168.github.io/blog/2014/03/17/postgres-uuid-in-rails/ ( Drawbacks section )
Rails 6 (currently in version 6.0.0rc1) comes to rescue with implicit_order_column!
To order by created_at and make .first, .last, .second etc. respect it is as simple as:
class ApplicationRecord < ActiveRecord::Base
self.implicit_order_column = :created_at
end
First of all, first and last aren't as simple as you seem to think they are: you're completely neglecting the limit argument that both of those methods support.
Secondly, scope is little more than a fancy way of adding class methods that are intended to return queries. Your scopes are abusing scope because they return single model instances rather than queries. You don't want to use scope at all, you're just trying to replace the first and last class methods so why don't you just override them? You'd need to override them properly though and that will require reading and understanding the Rails source so that you properly mimic what find_nth_with_limit does. You'd want to override second, third, ... and the rest of those silly methods while you're at it.
If you don't feel right about replace first and last (a good thing IMO), then you could add a default scope to order things as desired:
default_scope -> { order(:created_at) }
Of course, default scopes come with their own set of problems and sneaking things into the ORDER BY like this will probably force you into calling reorder any time you actually want to specify the ORDER BY; remember that multiple calls to order add new ordering conditions, they don't replace one that's already there.
Alternatively, if you're using Rails6+, you can use Markus's implicit_order_column solution to avoid all the problems that default scopes can cause.
I think you're going about this all wrong. Any time I see M.first I assume that something has been forgotten. Ordering things by id is pretty much useless so you should always manually specify the order you want before using methods like first and last.
After replacing id with uuid, I experienced some weirdness in the way associations were allocating foreign keys, and it wasn't that .last and .first, but instead because I simply forgot to add default: 'gen_random_uuid()' to one of the tables using a uuid. Once I fixed that, the problem was solved.
create_table :appointments, id: :uuid, default: 'gen_random_uuid()' do |t|
I'm using subclasses in mongoid and I would like to eager load relations of both the parent and its subclasses.
class Event
include Mongoid::Document
belongs_to :modifier
end
class Fixture < Event
belongs_to :club
end
When I run Event.includes(:modifier, :club) I get:
Mongoid::Errors::InvalidIncludes:
Problem:
Invalid includes directive: Event.includes(:modifier, :club)
Summary:
Eager loading in Mongoid only supports providing arguments to
Event.includes that are the names of relations on the Event model,
and only supports one level of eager loading. (ie, eager
loading associations not on the Event but one step away via
another relation is not allowed.
Resolution:
Ensure that each parameter passed to Event.includes is a valid name
of a relation on the Event model. These are: "modifier".
The error message is reasonable but I'd like to know if there is a workaround, short of running a separate query on each class?
I'd like to be able to also chain further criteria, i.e. Event.includes(:modifier, :club).desc(:updated_at) rather than sorting the result array in rails.
versions: Mongoid v3.16 & Rails 3.2.15
Edit: I'd better make it clearer what it is I want.
I want all events and all fixtures documents.
I also want all their relations: the modifiers on Events and Fixtures, and the clubs on Fixtures.
And I want those related documents to be retrieved from mongo all at once, i.e. typically done through 'eager loading' using the '.includes()' method .
There will be further subclasses, i.e. class Election < Events, class Seminar < Events. So I'd like to avoid querying each subclass separately.
I'd also like to chain further criteria, such as desc(:updated_at).
I am not exactly sure what you try to do, but I guess this could solve your problem.
Event.where(:modifier_id.ne => nil, :club_id.ne => nil).desc(:updated_at)
Let me know if that works for you.
so far MongoID doesn't support nested document eager loading. you better check out this gem
I have the following method in my Product model:
def has_feature? (feature_name)
self.features.where(:name=>feature_name).present?
end
This seems to be very slow. Is there a better way of checking to see if my product has a feature with a given name?
.present? isn't a standard method on an ActiveRecord::Relation which is what where returns. Calling .present? is fetching all of the features with the given name, changing into an array and then calling .present? on the array.
If you call .exists? instead then the database query that is performed is slightly more optimised to checking whether that feature exists or not.
However, I'm not sure you'll notice much of a performance differences between the two options. The most likely cause is that you're missing a database index. Assuming that your association is Product has_many :features the database query that will get generated would be improved by an index on both features.product_id and features.name.
One other way of writing this is using scope in your Feature model and see if it helps and do a present? on the resulting output of that scope
scope :includes_feature, lambda{ |feature|
{ :name => feature }
}
so finally what you can do is product.features.includes_feature(feature_name)