How to Determine if Rails Association is Eager Loaded?
Asked Answered
S

7

80

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.

Sectional answered 3/9, 2009 at 22:3 Comment(0)
A
74

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.

Amaliaamalie answered 4/9, 2009 at 5:50 Comment(4)
Thanks Bryan. I understand where you're coming from. This particular code happens to be in the most frequently called partial in our entire app, though, so shaving off even microseconds makes a measurable difference. I'll give #loaded? a try.Sectional
This will only work for has_many associations! In case of belongs_to this call will actually load the other object (so it will be always true)Uro
@Uro to check for this in has_one/belongs_to associations you can do item.association(:shipping_info).loaded? which works similarly. Added examples and more detail in my answer here: https://mcmap.net/q/260728/-how-to-check-if-has_one-exists-without-loading-associated-modelNewmodel
good point, although 12 years too late! :DUro
C
88

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

Crescentia answered 7/6, 2011 at 7:19 Comment(0)
A
74

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.

Amaliaamalie answered 4/9, 2009 at 5:50 Comment(4)
Thanks Bryan. I understand where you're coming from. This particular code happens to be in the most frequently called partial in our entire app, though, so shaving off even microseconds makes a measurable difference. I'll give #loaded? a try.Sectional
This will only work for has_many associations! In case of belongs_to this call will actually load the other object (so it will be always true)Uro
@Uro to check for this in has_one/belongs_to associations you can do item.association(:shipping_info).loaded? which works similarly. Added examples and more detail in my answer here: https://mcmap.net/q/260728/-how-to-check-if-has_one-exists-without-loading-associated-modelNewmodel
good point, although 12 years too late! :DUro
E
27

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)

Exclamation answered 26/2, 2013 at 21:9 Comment(3)
Perfect solution. Dabogert you're the king :)Cavern
Wow, can't believe that I had been missing this. This works for cases where the association is NOT loaded, but there IS an in-memory cache (e.g. when you build object using the association).Burtburta
This syntax no longer works in rails 5 (not sure when it changed). It's now item.association_cached? :name_of_association.Chickenhearted
W
14

association_cached? might be a good fit:

item.association_cached?(:shipping_infos)
Whereunto answered 4/7, 2018 at 12:19 Comment(0)
I
3

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

Innuendo answered 21/4, 2011 at 19:34 Comment(0)
M
3

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
Miquelmiquela answered 10/7, 2012 at 16:1 Comment(4)
You should double check that this is still a problem and update the answer to scope it to the appropriate Rails versions. I was unable to reproduce.Ardeen
Still a problem as of Rails 4.2.6. Not sure if Rails 5 has it.Weslee
What's the consequence of marking it as dirty? Just one extra select statement? Or is there more harm to it?Alcina
The consequence of the relation being marked incorrectly as dirty is that rails normally no-ops save calls on models that have no dirty attributes. So you will introduce extra saves and if you're using rails timestamps on the model it'll cause the updated_at times to update when no attributes in the model were actually changed.Tunnel
D
1

Have a look at the Bullet gem.. This will tell you when you should and should not use eager loading.

Delectation answered 15/9, 2009 at 14:26 Comment(2)
Bullet has proven really useful, even though I've done very little other DB optimisation yet. At least it gets rid of some unnecessary lines in the console output. ;)Timework
Bullet plugin link is dead.Ardeen

© 2022 - 2024 — McMap. All rights reserved.