diff --git a/Gemfile b/Gemfile index d89959c..b605137 100644 --- a/Gemfile +++ b/Gemfile @@ -7,6 +7,7 @@ gem 'sqlite3', '~> 1.7.3' gem 'activerecord', '~> 5.2' gem 'esse-rspec', '~> 0.0.6' gem 'elasticsearch', '~> 7.17', '>= 7.17.10' +gem 'esse-redis-storage', '~> 0.0.1' # Specify your gem's dependencies in esse-active_record.gemspec gemspec diff --git a/Gemfile.lock b/Gemfile.lock index cc50584..09a5e9e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -27,6 +27,7 @@ GEM base64 (0.2.0) coderay (1.1.3) concurrent-ruby (1.2.3) + connection_pool (2.4.1) crack (0.4.5) rexml diff-lcs (1.5.0) @@ -42,6 +43,9 @@ GEM esse (0.2.6) multi_json thor (>= 0.19) + esse-redis-storage (0.0.1) + esse (>= 0.2.4) + redis (>= 4.0.0) esse-rspec (0.0.6) esse (>= 0.2.4) rspec (>= 3) @@ -71,6 +75,10 @@ GEM racc (1.7.3) rainbow (3.1.1) rake (13.1.0) + redis (5.2.0) + redis-client (>= 0.22.0) + redis-client (0.22.2) + connection_pool regexp_parser (2.9.0) rexml (3.2.6) rspec (3.12.0) @@ -144,6 +152,7 @@ DEPENDENCIES elasticsearch (~> 7.17, >= 7.17.10) esse (~> 0.2.4) esse-active_record! + esse-redis-storage (~> 0.0.1) esse-rspec (~> 0.0.6) pry rake diff --git a/lib/esse/active_record/collection.rb b/lib/esse/active_record/collection.rb index 6a7cb8f..14aa5ae 100644 --- a/lib/esse/active_record/collection.rb +++ b/lib/esse/active_record/collection.rb @@ -83,6 +83,12 @@ def each end end + def ids_in_batches + dataset.select(:id).except(:includes, :preload).find_in_batches(**batch_options) do |rows| + yield(rows.map(&:id)) + end + end + def dataset(**kwargs) query = self.class.base_scope&.call || raise(NotImplementedError, "No scope defined for #{self.class}") query = query.except(:order, :limit, :offset) diff --git a/spec/esse/active_record/collection_spec.rb b/spec/esse/active_record/collection_spec.rb index 37aba75..0e97453 100644 --- a/spec/esse/active_record/collection_spec.rb +++ b/spec/esse/active_record/collection_spec.rb @@ -97,6 +97,55 @@ end end + describe '#ids_in_batches' do + it 'raises NotImplementedError when scope is not defined on the collection class' do + expect { + collection = described_class.new + collection.ids_in_batches + }.to raise_error(NotImplementedError) + end + + it 'returns an Enumerator with a relation instance' do + collection = Class.new(described_class) + collection.base_scope = -> { Animal.all } + expect { |b| collection.new.ids_in_batches(&b) }.not_to yield_control + end + + context 'with start and batch_size' do + let(:collection_class) do + klass = Class.new(described_class) + klass.base_scope = -> { Dog } + klass + end + + let!(:dogs) do + Array.new(3) { |i| Dog.create!(name: "Dog #{i.next}") } + end + + after do + Dog.destroy_all + end + + it 'stream entity ids in batches according to the :batch_size option' do + instance = collection_class.new(batch_size: 1) + + expect { |b| instance.ids_in_batches(&b) }.to yield_successive_args(*dogs.map { |doc| [doc.id] }) + end + + it 'stream entity ids in batches according to the :batch_size option and :start option' do + instance = collection_class.new(batch_size: 1, start: dogs[1].id) + + expect { |b| instance.ids_in_batches(&b) }.to yield_successive_args(*dogs[1..2].map { |doc| [doc.id] }) + end + + it 'stream entity ids in batches according to the :batch_size option and :finish option' do + instance = collection_class.new(batch_size: 1, finish: dogs[1].id) + + expect { |b| instance.ids_in_batches(&b) }.to yield_successive_args(*dogs[0..1].map { |doc| [doc.id] }) + end + end + end + describe '#dataset' do it 'returns an ActiveRecord::Relation' do collection = Class.new(described_class)