Skip to content

Commit

Permalink
Add support for scopes to MiqPreloader#preload
Browse files Browse the repository at this point in the history
We want to use preloaded data to populate a record's associations
Rails currently accepts a scope for preloading, but it runs the scope for every input record

We are using this to load VimPerformanceState values for a single record
We also want the list of records after it is done.

Before
======

```ruby
ems = ExtManagementSystem.take(5).to_a
vms = Vm.where(:ems => ems.map(&:id)).load

MiqPreloader.preload(ems, :vms, vms)      # => 5 queries
MiqPreloader.preload(ems, :vms, vms.to_a) # error
```

After
=====

```ruby
ems = ExtManagementSystem.take(5).to_a
vms = Vm.where(:ems => ems.map(&:id)).load

MiqPreloader.preload(ems, :vms, vms)      # => 5 queries
MiqPreloader.preload(ems, :vms, vms.to_a) # error
```
  • Loading branch information
kbrock committed Jul 5, 2023
1 parent f8c89ee commit 477a5ca
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 0 deletions.
18 changes: 18 additions & 0 deletions lib/extensions/ar_preloader.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module ActiveRecordPreloadScopes
# based upon active record 6.1
def records_for(ids)
# use our logic if passing in [ActiveRecord::Base] or passing in a loaded Relation/scope
unless (preload_scope.kind_of?(Array) && preload_scope.first.kind_of?(ActiveRecord::Base)) ||
preload_scope.try(:loaded?)
return super
end

preload_scope.each do |record|
owner = owners_by_key[convert_key(record[association_key_name])].first
association = owner.association(reflection.name)
association.set_inverse_instance(record)
end
end
end

ActiveRecord::Associations::Preloader::Association.prepend(ActiveRecordPreloadScopes)
39 changes: 39 additions & 0 deletions spec/lib/miq_preloader_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,45 @@
expect { preload(emses, :vms, vms) }.to make_database_queries(:count => 1)
end

it "preloads with a loaded relation (records is a relation)" do
ems = FactoryBot.create(:ems_infra)
FactoryBot.create_list(:vm, 2, :ext_management_system => ems)

emses = ExtManagementSystem.all.load
vms = Vm.where(:ems_id => emses.select(:id)).load

expect { preload(emses, :vms, vms) }.not_to make_database_queries
expect { preload(emses, :vms, vms) }.not_to make_database_queries
expect { expect(emses.first.vms.size).to eq(2) }.not_to make_database_queries
expect { expect(vms.first.ext_management_system).to eq(ems) }.not_to make_database_queries
end

it "preloads with an array (records is a relation)" do
ems = FactoryBot.create(:ems_infra)
FactoryBot.create_list(:vm, 2, :ext_management_system => ems)

emses = ExtManagementSystem.all.load
vms = Vm.where(:ems_id => emses.select(:id)).to_a

expect { preload(emses, :vms, vms) }.not_to make_database_queries
expect { preload(emses, :vms, vms) }.not_to make_database_queries
expect { expect(emses.first.vms.size).to eq(2) }.not_to make_database_queries
expect { expect(vms.first.ext_management_system).to eq(ems) }.not_to make_database_queries
end

it "preloads with an array (records is an array)" do
ems = FactoryBot.create(:ems_infra)
FactoryBot.create_list(:vm, 2, :ext_management_system => ems)

emses = ExtManagementSystem.all.load.to_a
vms = Vm.where(:ems_id => emses.map(&:id)).to_a

expect { preload(emses, :vms, vms) }.not_to make_database_queries
expect { preload(emses, :vms, vms) }.not_to make_database_queries
expect { expect(emses.first.vms.size).to eq(2) }.not_to make_database_queries
expect { expect(vms.first.ext_management_system).to eq(ems) }.not_to make_database_queries
end

def preload(*args)
MiqPreloader.preload(*args)
end
Expand Down

0 comments on commit 477a5ca

Please sign in to comment.