diff --git a/lib/miq_preloader.rb b/lib/miq_preloader.rb index 6b3bcb1f4eb1..4e1fde98f1af 100644 --- a/lib/miq_preloader.rb +++ b/lib/miq_preloader.rb @@ -1,4 +1,26 @@ module MiqPreloader + # If you want to preload an association on multiple records + # or want to only load a subset of an association + # + # @example Preloading vms on a set of emses + # vms_scope = Vm.where(:ems_id => emses.id) + # preload(emses, :vms, vms_scope) + # emses.map { |ems| ems.vms } # cached - no queries + # vms_scope.first.ems # cached - the reversed association is cached + # + # @example Programmatically determine the reverse association name + # Going from Ems#association(:vms) and going to Vm#association(:ems) + # + # reverse_association_name = record.class.reflect_on_association(association).inverse_of.name + # reverse_association = result.association(reverse_association_name) + # + # @param record [relation|ActiveRecord::Base|Array[ActiveRecord::Base]] + # @param association [Symbol|Hash|Array] name of the association(s) + # @param preload_scope [Nil|relation] Relation of the records to be use for preloading + # For all but one case, default behavior is to use the association + # Alternatively a scope can be used. + # Currently an array does not work + # @return [Array] records def self.preload(records, associations, preload_scope = nil) preloader = ActiveRecord::Associations::Preloader.new preloader.preload(records, associations, preload_scope) @@ -10,12 +32,15 @@ def self.preload(records, associations, preload_scope = nil) # orchestration_stack.subtree.flat_map(&:direct_vms) # use instead: # preload_and_map(orchestration_stack.subtree, :direct_vms) + # + # @param records [ActiveRecord::Base, Array, Object, Array] + # @param association [Symbol] name of the association def self.preload_and_map(records, association) Array.wrap(records).tap { |recs| MiqPreloader.preload(recs, association) }.flat_map(&association) end # @param records [ActiveRecord::Base, Array, Object, Array] - # @param association [String] an association on records + # @param association_name [Symbol] Name of the association def self.preload_and_scope(records, association_name) records = Array.wrap(records) unless records.kind_of?(Enumerable) active_record_klass = records.respond_to?(:klass) ? records.klass : records.first.class diff --git a/spec/lib/miq_preloader_spec.rb b/spec/lib/miq_preloader_spec.rb index 88ad890905d3..fefe48b5a3f0 100644 --- a/spec/lib/miq_preloader_spec.rb +++ b/spec/lib/miq_preloader_spec.rb @@ -4,22 +4,35 @@ ems = FactoryBot.create(:ems_infra) expect(ems.vms).not_to be_loaded expect { preload(ems, :vms) }.to make_database_queries(:count => 1) + expect(ems.vms).to be_loaded - expect { preload(ems, :vms) }.to_not make_database_queries + expect { preload(ems, :vms) }.not_to make_database_queries + expect { ems.vms.size }.not_to make_database_queries end - it "preloads from an array" do + it "preloads an array" do emses = FactoryBot.create_list(:ems_infra, 2) expect { preload(emses, :vms) }.to make_database_queries(:count => 1) - expect(emses[0].vms).to be_loaded + + expect(emses.first.vms).to be_loaded + expect { emses.first.vms.size }.not_to make_database_queries end - it "preloads from an association" do + it "preloads a relation (record is a relation)" do + FactoryBot.create_list(:vm, 2, :ext_management_system => FactoryBot.create(:ems_infra)) + emses = ExtManagementSystem.all + expect { preload(emses, :vms) }.to make_database_queries(:count => 2) + + expect { expect(emses.first.vms.size).to eq(2) }.not_to make_database_queries + end + + it "preloads with a relation (records is a relation)" do ems = FactoryBot.create(:ems_infra) FactoryBot.create_list(:vm, 2, :ext_management_system => ems) - emses = ExtManagementSystem.all - expect { preload(emses, :vms) }.to make_database_queries(:count => 2) + emses = ExtManagementSystem.all.load + vmses = Vm.where(:ems_id => emses.select(:id)) + expect { preload(emses, :vms, vmses) }.to make_database_queries(:count => 1) end def preload(*args) @@ -34,7 +47,7 @@ def preload(*args) vms = nil expect { vms = preload_and_map(ems, :vms) }.to make_database_queries(:count => 1) - expect { expect(vms.size).to eq(2) }.to_not make_database_queries + expect { expect(vms.size).to eq(2) }.not_to make_database_queries end it "preloads from an association" do @@ -56,7 +69,7 @@ def preload_and_map(*args) FactoryBot.create_list(:vm, 2, :ext_management_system => ems) vms = nil - expect { vms = preload_and_scope(ems, :vms) }.to_not make_database_queries + expect { vms = preload_and_scope(ems, :vms) }.not_to make_database_queries expect { expect(vms.count).to eq(2) }.to make_database_queries(:count => 1) end @@ -65,7 +78,7 @@ def preload_and_map(*args) FactoryBot.create(:template, :ext_management_system => FactoryBot.create(:ems_infra)) vms = nil - expect { vms = preload_and_scope(ExtManagementSystem.all, :vms_and_templates) }.to_not make_database_queries + expect { vms = preload_and_scope(ExtManagementSystem.all, :vms_and_templates) }.not_to make_database_queries expect { expect(vms.count).to eq(3) }.to make_database_queries(:count => 1) end @@ -86,8 +99,8 @@ def preload_and_map(*args) hosts = nil vms = nil - expect { vms = preload_and_scope(ExtManagementSystem.all, :vms_and_templates) }.to_not make_database_queries - expect { hosts = preload_and_scope(vms, :host) }.to_not make_database_queries + expect { vms = preload_and_scope(ExtManagementSystem.all, :vms_and_templates) }.not_to make_database_queries + expect { hosts = preload_and_scope(vms, :host) }.not_to make_database_queries expect { expect(hosts.count).to eq(2) }.to make_database_queries(:count => 1) end @@ -103,8 +116,8 @@ def preload_and_map(*args) emses = nil vms = nil - expect { emses = preload_and_scope(Host.all, :ext_management_system) }.to_not make_database_queries - expect { vms = preload_and_scope(emses, :vms) }.to_not make_database_queries + expect { emses = preload_and_scope(Host.all, :ext_management_system) }.not_to make_database_queries + expect { vms = preload_and_scope(emses, :vms) }.not_to make_database_queries expect { expect(vms.count).to eq(3) }.to make_database_queries(:count => 1) end