diff --git a/lib/policy_machine.rb b/lib/policy_machine.rb index 40147097..b3c57c33 100644 --- a/lib/policy_machine.rb +++ b/lib/policy_machine.rb @@ -194,6 +194,17 @@ def scoped_privileges(user_or_attribute, object_or_attribute, options = {}) end end + ## + # Search for and iterate over a collection in batches + def batch_find(type:, query: {}, config: {}, &blk) + if policy_machine_storage_adapter.respond_to?(:batch_find) + policy_machine_storage_adapter.batch_find(type, query, config, &blk) + else + batch_size = config.fetch(:batch_size, 1) + method(type.to_s.pluralize).call(query).each_slice(batch_size, &blk) + end + end + ## # Returns an array of all objects the given user (attribute) # has the given operation on. diff --git a/lib/policy_machine_storage_adapters/active_record.rb b/lib/policy_machine_storage_adapters/active_record.rb index 84ae4891..42f99d5c 100644 --- a/lib/policy_machine_storage_adapters/active_record.rb +++ b/lib/policy_machine_storage_adapters/active_record.rb @@ -345,6 +345,10 @@ def scoped_privileges(user_or_attribute, object_or_attribute, options = {}) end end + def batch_find(policy_object, query = {}, config = {}, &blk) + method("find_all_of_type_#{policy_object}").call(query).find_in_batches(config, &blk) + end + ## Optimized version of PolicyMachine#scoped_privileges # Returns all objects the user has the given operation on # TODO: Support multiple policy classes here diff --git a/spec/support/shared_examples_policy_machine.rb b/spec/support/shared_examples_policy_machine.rb index 651e44fc..77bbfa0c 100644 --- a/spec/support/shared_examples_policy_machine.rb +++ b/spec/support/shared_examples_policy_machine.rb @@ -793,4 +793,95 @@ end + + describe 'batch_find' do + + before do + @one_fish = policy_machine.create_object('one:fish') + @two_fish = policy_machine.create_object('two:fish') + @red_one = policy_machine.create_object('red:one') + @read = policy_machine.create_operation('read') + @write = policy_machine.create_operation('write') + @u1 = policy_machine.create_user('u1') + @ua = policy_machine.create_user_attribute('ua') + [@one_fish, @two_fish, @red_one].each do |object| + policy_machine.add_association(@ua, Set.new([@read]), object) + end + @oa = policy_machine.create_object_attribute('oa') + policy_machine.add_association(@ua, Set.new([@write]), @oa) + policy_machine.add_assignment(@u1, @ua) + policy_machine.add_assignment(@red_one, @oa) + end + + context 'when given a block' do + + it 'calls the block' do + expect do |spy| + policy_machine.batch_find(type: :object, query: { unique_identifier: 'one:fish' }, &spy) + end.to yield_control + end + + context 'and search terms' do + it 'returns the matching records' do + policy_machine.batch_find(type: :object, query: { unique_identifier: 'one:fish' }) do |batch| + expect(batch.size).to eq 1 + expect(batch.first.unique_identifier).to eq 'one:fish' + end + end + end + + context 'and config options' do + it 'returns the correct batch size' do + policy_machine.batch_find(type: :object, config: { batch_size: 1 }) do |batch| + expect(batch.size).to eq 1 + end + + policy_machine.batch_find(type: :object, config: { batch_size: 3 }) do |batch| + expect(batch.size).to eq 3 + end + end + end + end + + context 'when not given a block' do + + it 'returns an enumerator' do + result = policy_machine.batch_find(type: :object) + expect(result).to be_a Enumerator + end + + it 'the results are chainable and returns the relevant results' do + enum = policy_machine.batch_find(type: :object) + results = enum.flat_map do |batch| + batch.map { |pe| pe.unique_identifier } + end + expected = %w(one:fish two:fish red:one) + expect(results).to include(*expected) + end + + context 'but given search terms' do + it 'the results are chainable and returns the relevant results' do + enum = policy_machine.batch_find(type: :object, query: { unique_identifier: 'one:fish' }) + results = enum.flat_map do |batch| + batch.map { |pe| pe.unique_identifier } + end + expected = 'one:fish' + expect(results.first).to eq(expected) + end + end + + context 'but given config options' do + it 'resepects batch size configs while return all results' do + enum = policy_machine.batch_find(type: :object, config: { batch_size: 3}) + results = enum.flat_map do |batch| + expect(batch.size).to eq 3 + batch.map { |pe| pe.unique_identifier } + end + expected = %w(one:fish two:fish red:one) + expect(results).to include(*expected) + end + end + + end + end end