Reputation: 4802
I have 4 models, let's say:
class Photo < ActiveRecord::Base
belongs_to :photoable, polymorphic: true
end
class User < ActiveRecord::Base
has_one :photo, as: :photoable
end
class Company < ActiveRecord::Base
has_one :photo, as: :photoable
has_many :products
end
class Products < ActiveRecord::Base
belongs_to :company
end
So, the query Photo.all.includes(:photoable)
works.
But if I use Photo.all.includes(photoable: :products)
only works if all loaded photos belongs to Company. If the relation contains photos of users and companies, this error is raised:
ActiveRecord::ConfigurationError (Association named 'products' was not found; perhaps you misspelled it?):
This occurs because user hasn't relationship with products.
Is there any way to eager load users and companies with products for a relation of photos?
EDIT:
This question isn't duplicated of Eager load polymorphic. As I commented below, in this question I want to do eager load for polymorphic associations which has different associations(one has products and the other don't). In that question, the OP uses wrong names for table names.
Upvotes: 16
Views: 3099
Reputation: 18464
Rails 6 added support for this case, now nested associations are only preloaded if they are defined for the object, you can even mix associations from different classes:
Photo.all.includes(photoable: [:products, :posts, :some_undefined])
Full runnable example:
require "bundler/inline"
gemfile(!ENV['SKIP_INSTALL']) do
source "https://rubygems.org"
# rails 5 gives the error, run with RAILS5=1 to see
gem "activerecord", ENV['RAILS5'] && "~>5.0" || "~>6.0"
gem "sqlite3"
end
require "active_record"
require "minitest/autorun"
require "logger"
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Schema.define do
create_table(:photos){|t| t.references :photoable, polymorphic: true }
create_table(:users)
create_table(:companies)
create_table(:products){|t| t.references :company }
create_table(:posts){|t| t.references :user }
end
# --- Models:
class Photo < ActiveRecord::Base
belongs_to :photoable, polymorphic: true
end
class User < ActiveRecord::Base
has_one :photo, as: :photoable
has_many :posts
end
class Company < ActiveRecord::Base
has_one :photo, as: :photoable
has_many :products
end
class Product < ActiveRecord::Base
belongs_to :company
end
class Post < ActiveRecord::Base
belongs_to :user
end
# --- Test:
class SomeTest < Minitest::Test
def setup
2.times{
Company.create!.tap{|company|
company.products.create!
company.create_photo!
}
User.create!.tap{|user|
user.posts.create!
user.create_photo!
}
}
end
def test_association_stuff
rel = Photo.all.includes(photoable: [:products, :posts, :some_undefined])
arr = rel.to_a # fetch all records here
ActiveRecord::Base.logger.extend(Module.new{
define_method(:debug){|msg| raise "should not log from now on" }
})
assert_kind_of(Product, arr.first.photoable.products.first)
assert_kind_of(Post, arr.last.photoable.posts.first)
end
end
Upvotes: 4
Reputation: 1439
class Products < ActiveRecord::Base
belongs_to :company
end
Should be
class Product < ActiveRecord::Base
belongs_to :company
end
And then it will work.
Upvotes: 1
Reputation: 2860
You may add specific associations to Photo
model:
class Photo < ActiveRecord::Base
belongs_to :photoable, polymorphic: true
belongs_to :user, -> { where(photoable_type: 'User' ) }, foreign_key: :photoable_id
belongs_to :company, -> { where(photoable_type: 'Company' ) }, foreign_key: :photoable_id
end
After that you may preload nested specific associations:
photos = Photo.all.includes(:photoable, company: :products)
With access to records like this:
photos.each do |photo|
puts photo.photoable.inspect
puts photo.company.products.inspect if photo.photoable_type == "Company"
end
This approach loads company
association twice, but does not do n+1 queries.
Upvotes: 2
Reputation: 1495
It's quite strange your are joining companies to your photos and not the other way around. Why would you do that? And do you really need those company products in the view or controller?
SELECT * from photos
LEFT JOIN users AS u ON u.id = photos.photoable_id AND
photos.photoable_type = 'User'
LEFT JOIN companies AS c ON c.id = photos.phottoable_id AND
photos.photoable_type = 'Company'
LEFT JOIN products AS p ON p.company_id = c.id
Upvotes: 0
Reputation: 3371
I have same issues, and have found a solution.
You can call the eager load after the find :
photos = Photo.all.includes(:photoable)
photos_with_products_association = photos.select{|p| p.photoable.is_a?(Company)}
ActiveRecord::Associations::Preloader.new.preload(
photos_with_products_association,
:products
)
... or more generic
photos_with_products_association = photos.select do |p|
p.class.reflections.keys.include?(:products)
end
Upvotes: 0