diff --git a/app/models/alchemy/essence_page.rb b/app/models/alchemy/essence_page.rb index c5b083372a..ad369cd975 100644 --- a/app/models/alchemy/essence_page.rb +++ b/app/models/alchemy/essence_page.rb @@ -6,11 +6,15 @@ class EssencePage < BaseRecord acts_as_essence( ingredient_column: :page, - preview_text_method: :name + preview_text_method: :name, + belongs_to: { + class_name: 'Alchemy::Page', + foreign_key: :page_id, + inverse_of: :essence_pages, + optional: true + } ) - belongs_to :page, class_name: 'Alchemy::Page', optional: true - def ingredient=(page) case page when PAGE_ID diff --git a/app/models/alchemy/essence_picture.rb b/app/models/alchemy/essence_picture.rb index 611c4fe6c7..013b088797 100644 --- a/app/models/alchemy/essence_picture.rb +++ b/app/models/alchemy/essence_picture.rb @@ -25,9 +25,13 @@ module Alchemy class EssencePicture < BaseRecord - acts_as_essence ingredient_column: 'picture' + acts_as_essence ingredient_column: :picture, belongs_to: { + class_name: 'Alchemy::Picture', + foreign_key: :picture_id, + inverse_of: :essence_pictures, + optional: true + } - belongs_to :picture, optional: true delegate :image_file_width, :image_file_height, :image_file, to: :picture before_save :fix_crop_values before_save :replace_newlines diff --git a/app/models/alchemy/picture.rb b/app/models/alchemy/picture.rb index 9248d44b1c..c9c51644e9 100644 --- a/app/models/alchemy/picture.rb +++ b/app/models/alchemy/picture.rb @@ -30,7 +30,11 @@ class Picture < BaseRecord include Alchemy::Picture::Transformations include Alchemy::Picture::Url - has_many :essence_pictures, class_name: 'Alchemy::EssencePicture', foreign_key: 'picture_id' + has_many :essence_pictures, + class_name: 'Alchemy::EssencePicture', + foreign_key: 'picture_id', + inverse_of: :ingredient_association + has_many :contents, through: :essence_pictures has_many :elements, through: :contents has_many :pages, through: :elements diff --git a/lib/alchemy/essence.rb b/lib/alchemy/essence.rb index ed472b5926..92e0c6e655 100644 --- a/lib/alchemy/essence.rb +++ b/lib/alchemy/essence.rb @@ -3,6 +3,18 @@ require 'active_record' module Alchemy #:nodoc: + # A bogus association that skips eager loading for essences not having an ingredient association + class IngredientAssociation < ActiveRecord::Associations::BelongsToAssociation + # Skip eager loading if called by Rails' preloader + def klass + if caller.any? { |line| line =~ /preloader\.rb/ } + nil + else + super + end + end + end + module Essence #:nodoc: def self.included(base) base.extend(ClassMethods) @@ -31,6 +43,8 @@ def acts_as_essence(options = {}) ingredient_column: 'body' }.update(options) + @_classes_with_ingredient_association ||= [] + class_eval <<-RUBY, __FILE__, __LINE__ + 1 attr_writer :validation_errors include Alchemy::Essence::InstanceMethods @@ -66,8 +80,31 @@ def preview_text_column '#{configuration[:preview_text_column] || configuration[:ingredient_column]}' end RUBY + + if configuration[:belongs_to] + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + belongs_to :ingredient_association, #{configuration[:belongs_to]} + + alias_method :#{configuration[:ingredient_column]}, :ingredient_association + alias_method :#{configuration[:ingredient_column]}=, :ingredient_association= + RUBY + + @_classes_with_ingredient_association << self + end end + # Overwrite ActiveRecords method to return a bogus association class that skips eager loading + # for essence classes that do not have an ingredient association + def _reflect_on_association(name) + if name == :ingredient_association && !in?(@_classes_with_ingredient_association) + OpenStruct.new(association_class: Alchemy::IngredientAssociation) + else + super + end + end + + private + # Register the current class as has_many association on +Alchemy::Page+ and +Alchemy::Element+ models def register_as_essence_association! klass_name = model_name.to_s @@ -231,4 +268,5 @@ def has_tinymce? end end end -ActiveRecord::Base.class_eval { include Alchemy::Essence } if defined?(Alchemy::Essence) + +ActiveRecord::Base.include(Alchemy::Essence) diff --git a/lib/alchemy/test_support/essence_shared_examples.rb b/lib/alchemy/test_support/essence_shared_examples.rb index 8a53a38917..e1a2af7c30 100644 --- a/lib/alchemy/test_support/essence_shared_examples.rb +++ b/lib/alchemy/test_support/essence_shared_examples.rb @@ -10,6 +10,18 @@ let(:content) { Alchemy::Content.new(name: 'foo') } let(:content_definition) { {'name' => 'foo'} } + describe 'eager loading' do + before do + 2.times { described_class.create! } + end + + it 'does not throw error if eager loaded' do + expect { + described_class.all.includes(:ingredient_association).to_a + }.to_not raise_error + end + end + it "touches the content after update" do element = FactoryBot.create(:alchemy_element) content = FactoryBot.create(:alchemy_content, element: element, essence: essence, essence_type: essence.class.name) diff --git a/spec/models/alchemy/essence_page_spec.rb b/spec/models/alchemy/essence_page_spec.rb index a320bc0a0b..0606e2a88c 100644 --- a/spec/models/alchemy/essence_page_spec.rb +++ b/spec/models/alchemy/essence_page_spec.rb @@ -10,6 +10,15 @@ let(:ingredient_value) { page } end + describe 'eager loading' do + let!(:essence_pages) { create_list(:alchemy_essence_page, 2) } + + it 'eager loads pages' do + essences = described_class.all.includes(:ingredient_association) + expect(essences[0].association(:ingredient_association)).to be_loaded + end + end + describe 'ingredient=' do subject(:ingredient) { essence.page } diff --git a/spec/models/alchemy/essence_picture_spec.rb b/spec/models/alchemy/essence_picture_spec.rb index cec5801fc5..24694cae50 100644 --- a/spec/models/alchemy/essence_picture_spec.rb +++ b/spec/models/alchemy/essence_picture_spec.rb @@ -9,6 +9,15 @@ module Alchemy let(:ingredient_value) { Picture.new } end + describe 'eager loading' do + let!(:essence_pictures) { create_list(:alchemy_essence_picture, 2) } + + it 'eager loads pictures' do + essences = described_class.all.includes(:ingredient_association) + expect(essences[0].association(:ingredient_association)).to be_loaded + end + end + it_behaves_like "has image transformations" do let(:picture) { build_stubbed(:alchemy_essence_picture) } end