ar-ondemand 1.1.3 → 1.1.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NWUwZjI4MzczNTc2NTkxNGEyMDRmNjBlZjNjMWU4YmE5Zjk3MGZiZg==
4
+ OTQ1OGY0YzBjZGU4MjkzZTUxMzEzZTg1YzU2NjBiNzc2YmU1ZGQ5Yg==
5
5
  data.tar.gz: !binary |-
6
- MjhiYTMxYTgxZGI3MGFmOGQxODc2MTcwZDI5OGQ4MDgwOWM0Y2JhOQ==
6
+ MjM1YmU4MmQ3YjA5NDhkOWNmYTI5NTkwMTRmMzhlMmYwODRlMTZlZg==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- Y2E4Zjk0MTQzNjliM2RjOWZiZWUwZTA1ZGI1NTUyMTk5YTcwY2Y2ZTFiMzZi
10
- OWZlZGYzYjE2NTA3MjIzN2UyOWM2MDhhNGY0NzFiZGU5NTViYzhiYWMyYWM5
11
- NzFkNTg5NTc0ODE0ZGNmNmE2Nzk0NmRmZjBkOTJjODkyNjQ4NWU=
9
+ ZTRiODA1ZTM1ZDNiMjI2MzY3MjJhYjA3NmFjODVlZDQwZjE5M2NhZjA0NzBh
10
+ MzhmNTgwMGJkNWMzMTc2YTM1YWZhMjFjYzdmODNiMWMwYzExODE3YjVmODE3
11
+ NjlkOTQwZGExODZkMDFjODMyMWFkMjE3ODczYTI0MTY5NmJjMmU=
12
12
  data.tar.gz: !binary |-
13
- ZTU4MTg4NWY0MzU3NWY2NzE3ZTQ0YjM0NWNlMDZiMjM1ODg5NWMxNDk4NTc0
14
- ZTI1NGFhOTAwMWE4NDdhMTRhOGM4NjNlZWQ4NDI4MjNhMzc1ZmNlYTYzNmZl
15
- ZWFhY2I5ZDE3MzkxMTI4MzcyNjAzOTY4ODY3NGI2MDEyZDZmNGQ=
13
+ NGQ4NmY3MzUyNmI4NTZlY2JmODZmM2VkZmMyOThkMDVmNjJjM2MwZDBlNjY3
14
+ MDdiOWI2YjFlOGY3ZTg0MDg2ZDk0MzQzNzBlYWQwMWU5NWY0NDM2MTgyZGU1
15
+ NGQ1MDcyZjY1ZjMyYTE3OTBjZTVmOGU5ZTg1MzIzZTliYjA5MDQ=
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ar-ondemand (1.1.3)
4
+ ar-ondemand (1.1.4)
5
5
  activerecord (>= 3.2)
6
6
  activesupport (>= 3.2)
7
7
 
@@ -2,58 +2,62 @@ require 'active_support/concern'
2
2
 
3
3
  module ActiveRecord
4
4
  module OnDemand
5
- module DeleteAllByPkExtension
5
+ module DeleteAllByPk
6
6
  extend ::ActiveSupport::Concern
7
7
 
8
- # Use this instead of delete_all to perform a delete using the PK of the table, which prevents a complete table scan that locks it
9
- # Based on find_in_batches function
10
- def delete_all_by_pk(options = {})
11
- relation = self
8
+ module ClassMethods
9
+ # Use this instead of delete_all to perform a delete using the PK of the table, which prevents a complete table scan that locks it
10
+ # Based on find_in_batches function
11
+ def delete_all_by_pk(options = {})
12
+ relation = self
13
+ relation = self.scoped unless respond_to? :arel
12
14
 
13
- unless arel.orders.blank? && arel.taken.blank?
14
- ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
15
- end
15
+ unless relation.arel.orders.blank? && relation.arel.taken.blank?
16
+ ::ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
17
+ end
16
18
 
17
- if (finder_options = options.except(:start, :batch_size)).present?
18
- raise "You can't specify an order, it's forced to be #{batch_order_delete_all_by_pk}" if options[:order].present?
19
- raise "You can't specify a limit, it's forced to be the batch_size" if options[:limit].present?
19
+ if (finder_options = options.except(:start, :batch_size)).present?
20
+ raise "You can't specify an order, it's forced to be #{batch_order_delete_all_by_pk}" if options[:order].present?
21
+ raise "You can't specify a limit, it's forced to be the batch_size" if options[:limit].present?
20
22
 
21
- relation = apply_finder_options(finder_options)
22
- end
23
+ relation = apply_finder_options(finder_options)
24
+ end
23
25
 
24
- start = options.delete(:start)
25
- batch_size = options.delete(:batch_size)
26
+ start = options.delete(:start)
27
+ batch_size = options.delete(:batch_size)
26
28
 
27
- relation = relation.reorder(batch_order_delete_all_by_pk).limit(batch_size) if batch_size
28
- records = query_delete_all_by_pk(start ? relation.where(table[primary_key].gteq(start)) : relation)
29
- deleted = 0
29
+ relation = relation.reorder(batch_order_delete_all_by_pk).limit(batch_size) if batch_size
30
+ records = query_delete_all_by_pk(start ? relation.where(relation.table[primary_key].gteq(start)) : relation)
31
+ deleted = 0
30
32
 
31
- while records.any?
32
- records_size = records.size
33
- primary_key_offset = records.last
33
+ while records.any?
34
+ records_size = records.size
35
+ primary_key_offset = records.last
34
36
 
35
- deleted += self.unscoped.where(id: records).delete_all
37
+ deleted += self.unscoped.where(id: records).delete_all
36
38
 
37
- break if batch_size.nil? || records_size < batch_size
39
+ break if batch_size.nil? || records_size < batch_size
38
40
 
39
- records = query_delete_all_by_pk relation.where(table[primary_key].gt(primary_key_offset))
40
- end
41
+ records = query_delete_all_by_pk relation.where(relation.table[primary_key].gt(primary_key_offset))
42
+ end
41
43
 
42
- deleted
43
- end
44
+ deleted
45
+ end
44
46
 
45
- private
47
+ private
46
48
 
47
- def query_delete_all_by_pk(ar)
48
- results = ::ActiveRecord::Base.connection.exec_query ar.select([table[primary_key]]).to_sql
49
- results.rows.flatten
50
- end
49
+ def query_delete_all_by_pk(ar)
50
+ results = ::ActiveRecord::Base.connection.exec_query ar.select([ar.table[primary_key]]).to_sql
51
+ results.rows.flatten
52
+ end
51
53
 
52
- def batch_order_delete_all_by_pk
53
- "#{quoted_table_name}.#{quoted_primary_key} ASC"
54
+ def batch_order_delete_all_by_pk
55
+ "#{quoted_table_name}.#{quoted_primary_key} ASC"
56
+ end
54
57
  end
55
58
  end
56
59
  end
57
60
  end
58
61
 
59
- ::ActiveRecord::Relation.send(:include, ::ActiveRecord::OnDemand::DeleteAllByPkExtension)
62
+ ::ActiveRecord::Base.send :include, ::ActiveRecord::OnDemand::DeleteAllByPk
63
+ ::ActiveRecord::Relation.send :include, ::ActiveRecord::OnDemand::DeleteAllByPk::ClassMethods
@@ -4,67 +4,75 @@ require 'ar-ondemand/record'
4
4
 
5
5
  module ActiveRecord
6
6
  module OnDemand
7
- module ForReadingExtension
7
+ module Reading
8
8
  extend ::ActiveSupport::Concern
9
9
 
10
- # Ripped from the find_in_batches function, but customized to return an ::ActiveRecord::OnDemand::ResultSet
11
- def for_reading(options = {})
12
- if options.empty?
13
- res = query_for_reading(self)
14
- if block_given?
15
- yield res
16
- return
17
- end
18
- return res
10
+ module ClassMethods
11
+ def raw_results
12
+ query_for_reading self, readonly: true, raw: true
19
13
  end
20
14
 
21
- relation = self
15
+ # Ripped from the find_in_batches function, but customized to return an ::ActiveRecord::OnDemand::ResultSet
16
+ def for_reading(options = {})
17
+ if options.empty?
18
+ res = query_for_reading self, readonly: true
19
+ if block_given?
20
+ yield res
21
+ return
22
+ end
23
+ return res
24
+ end
22
25
 
23
- unless arel.orders.blank? && arel.taken.blank?
24
- ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
25
- end
26
+ relation = self
26
27
 
27
- if (finder_options = options.except(:start, :batch_size)).present?
28
- raise "You can't specify an order, it's forced to be #{batch_order_for_reading}" if options[:order].present?
29
- raise "You can't specify a limit, it's forced to be the batch_size" if options[:limit].present?
28
+ unless arel.orders.blank? && arel.taken.blank?
29
+ ::ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
30
+ end
30
31
 
31
- relation = apply_finder_options(finder_options)
32
- end
32
+ if (finder_options = options.except(:start, :batch_size)).present?
33
+ raise "You can't specify an order, it's forced to be #{batch_order_for_reading}" if options[:order].present?
34
+ raise "You can't specify a limit, it's forced to be the batch_size" if options[:limit].present?
33
35
 
34
- start = options.delete(:start)
35
- batch_size = options.delete(:batch_size) || 1000
36
+ relation = apply_finder_options(finder_options)
37
+ end
36
38
 
37
- relation = relation.reorder(batch_order_for_reading).limit(batch_size)
38
- records = query_for_reading(start ? relation.where(table[primary_key].gteq(start)) : relation)
39
+ start = options.delete(:start)
40
+ batch_size = options.delete(:batch_size) || 1000
39
41
 
40
- while records.any?
41
- records_size = records.size
42
- primary_key_offset = records.last.id
42
+ relation = relation.reorder(batch_order_for_reading).limit(batch_size)
43
+ records = query_for_reading(start ? relation.where(table[primary_key].gteq(start)) : relation, readonly: true)
43
44
 
44
- yield records
45
+ while records.any?
46
+ records_size = records.size
47
+ primary_key_offset = records.last.id
45
48
 
46
- break if records_size < batch_size
49
+ yield records
47
50
 
48
- if primary_key_offset
49
- records = query_for_reading relation.where(table[primary_key].gt(primary_key_offset))
50
- else
51
- raise "Primary key not included in the custom select clause"
51
+ break if records_size < batch_size
52
+
53
+ if primary_key_offset
54
+ records = query_for_reading relation.where(table[primary_key].gt(primary_key_offset)), readonly: true
55
+ else
56
+ raise 'Primary key not included in the custom select clause'
57
+ end
52
58
  end
53
59
  end
54
- end
55
60
 
56
- private
61
+ private
57
62
 
58
- def query_for_reading(ar)
59
- results = ::ActiveRecord::Base.connection.exec_query ar.to_sql
60
- ::ActiveRecord::OnDemand::ResultSet.new self.arel.engine, results, readonly: true
61
- end
63
+ def query_for_reading(ar, options = {})
64
+ ar = ar.scoped unless ar.respond_to?(:to_sql)
65
+ results = ::ActiveRecord::Base.connection.exec_query ar.to_sql
66
+ ::ActiveRecord::OnDemand::ResultSet.new ar.arel.engine, results, options
67
+ end
62
68
 
63
- def batch_order_for_reading
64
- "#{quoted_table_name}.#{quoted_primary_key} ASC"
69
+ def batch_order_for_reading
70
+ "#{quoted_table_name}.#{quoted_primary_key} ASC"
71
+ end
65
72
  end
66
73
  end
67
74
  end
68
75
  end
69
76
 
70
- ::ActiveRecord::Relation.send(:include, ::ActiveRecord::OnDemand::ForReadingExtension)
77
+ ::ActiveRecord::Base.send :include, ::ActiveRecord::OnDemand::Reading
78
+ ::ActiveRecord::Relation.send :include, ::ActiveRecord::OnDemand::Reading::ClassMethods
@@ -0,0 +1,26 @@
1
+ require 'active_support/concern'
2
+ require 'ar-ondemand/for_reading'
3
+
4
+ module ActiveRecord
5
+ module OnDemand
6
+ module Streaming
7
+ extend ::ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+ def for_streaming(options = {})
11
+ options[:batch_size] ||= 50_000
12
+ fr = options.delete(:for_reading)
13
+ s = self.respond_to?(:scoped) ? self.scoped : self
14
+ ::Enumerator.new do |n|
15
+ s.send(fr ? :for_reading : :find_in_batches, options) do |b|
16
+ b.each { |r| n << r }
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ ::ActiveRecord::Base.send :include, ::ActiveRecord::OnDemand::Streaming
26
+ ::ActiveRecord::Relation.send :include, ::ActiveRecord::OnDemand::Streaming::ClassMethods
@@ -3,11 +3,14 @@ module ActiveRecord
3
3
  class ResultSet
4
4
  include ::Enumerable
5
5
 
6
+ CACHED_READONLY_CLASSES ||= {}
7
+
6
8
  def initialize(model, results, options = {})
7
9
  @model = model
8
10
  @results = results
9
11
  @column_types = Hash[@model.columns.map { |x| [x.name, x] }]
10
12
  @col_indexes = HashWithIndifferentAccess[@results.columns.each_with_index.map { |x, i| [x,i] }]
13
+ @raw = options.delete :raw
11
14
  @readonly = options.delete :readonly
12
15
  @readonly_klass = @readonly ? create_readonly_class : nil
13
16
  end
@@ -39,6 +42,7 @@ module ActiveRecord
39
42
 
40
43
  def result_to_record(row)
41
44
  return nil if row.nil?
45
+ return row if @raw
42
46
  if @readonly
43
47
  convert_to_struct @readonly_klass, row
44
48
  else
@@ -48,7 +52,8 @@ module ActiveRecord
48
52
 
49
53
  def create_readonly_class
50
54
  attrs = @col_indexes.keys.map(&:to_sym)
51
- ::Struct.new *attrs
55
+ return CACHED_READONLY_CLASSES[attrs] if CACHED_READONLY_CLASSES[attrs]
56
+ CACHED_READONLY_CLASSES[attrs] = ::Struct.new *attrs
52
57
  end
53
58
 
54
59
  def convert_to_hash(rec)
@@ -1,3 +1,3 @@
1
1
  module ArOnDemand
2
- VERSION = '1.1.3'
2
+ VERSION = '1.1.4'
3
3
  end
data/lib/ar-ondemand.rb CHANGED
@@ -1,3 +1,4 @@
1
1
  require_dependency 'ar-ondemand/on_demand'
2
2
  require_dependency 'ar-ondemand/for_reading'
3
+ require_dependency 'ar-ondemand/for_streaming'
3
4
  require_dependency 'ar-ondemand/delete_all_by_pk'
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+ require 'ar-ondemand'
3
+
4
+ describe 'DeleteAllByPk' do
5
+ context 'Testing' do
6
+ context 'ClassMethod' do
7
+ before(:each) do
8
+ create(:audit_record)
9
+ end
10
+
11
+ it 'should delete record' do
12
+ AuditRecord.delete_all_by_pk.should eq(1)
13
+ end
14
+ end
15
+
16
+ context 'Relation' do
17
+ before(:each) do
18
+ create(:audit_record)
19
+ end
20
+
21
+ it 'should delete record' do
22
+ AuditRecord.where('id > 0').delete_all_by_pk.should eq(1)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -19,7 +19,7 @@ describe 'ForReading' do
19
19
 
20
20
  context 'Iterating' do
21
21
  before(:each) do
22
- (1..100).each do
22
+ (1..25).each do
23
23
  create(:audit_record)
24
24
  end
25
25
  end
@@ -29,7 +29,15 @@ describe 'ForReading' do
29
29
  AuditRecord.where(customer_id: 1).for_reading.each do |r|
30
30
  total += 1
31
31
  end
32
- total.should eq(100)
32
+ total.should eq(25)
33
+ end
34
+
35
+ it 'should support iterating' do
36
+ total = 0
37
+ AuditRecord.for_reading.each do |r|
38
+ total += 1
39
+ end
40
+ total.should eq(25)
33
41
  end
34
42
 
35
43
  it 'should produce same results as regular iterating' do
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+ require 'ar-ondemand'
3
+
4
+ describe 'ForStreaming' do
5
+ context 'Testing' do
6
+ context 'Iterating' do
7
+ before(:each) do
8
+ (1..25).each do
9
+ create(:audit_record)
10
+ end
11
+ end
12
+
13
+ context 'AR' do
14
+ it 'ClassMethod should support streaming' do
15
+ total = 0
16
+ AuditRecord.for_streaming.each do |r|
17
+ #r.is_a?(AuditRecord).should eq(true)
18
+ expect(r).to be_an_instance_of(AuditRecord)
19
+ total += 1
20
+ end
21
+ total.should eq(25)
22
+ end
23
+
24
+ it 'Relation should support streaming' do
25
+ total = 0
26
+ AuditRecord.where(customer_id: 1).for_streaming.each do |r|
27
+ expect(r).to be_an_instance_of(AuditRecord)
28
+ total += 1
29
+ end
30
+ total.should eq(25)
31
+ end
32
+
33
+ it 'should support select' do
34
+ total = 0
35
+ AuditRecord.select([:customer_id]).where(customer_id: 1).for_streaming.each do |r|
36
+ expect(r).to be_an_instance_of(AuditRecord)
37
+ expect(r.customer_id).to eq(1)
38
+ expect { r.model_type_id }.to raise_error(::ActiveModel::MissingAttributeError)
39
+ total += 1
40
+ end
41
+ total.should eq(25)
42
+ end
43
+ end
44
+
45
+ context 'For Reading' do
46
+ it 'ClassMethod should support streaming' do
47
+ total = 0
48
+ AuditRecord.for_streaming(for_reading: true).each do |r|
49
+ expect(r).not_to be_an_instance_of(AuditRecord)
50
+ r.customer_id.should eq(1)
51
+ total += 1
52
+ end
53
+ total.should eq(25)
54
+ end
55
+
56
+ it 'Relation should support streaming' do
57
+ total = 0
58
+ AuditRecord.where(customer_id: 1).for_streaming(for_reading: true).each do |r|
59
+ expect(r).not_to be_an_instance_of(AuditRecord)
60
+ r.customer_id.should eq(1)
61
+ total += 1
62
+ end
63
+ total.should eq(25)
64
+ end
65
+ end
66
+
67
+ end
68
+ end
69
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ar-ondemand
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.3
4
+ version: 1.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steve Frank
@@ -55,6 +55,7 @@ files:
55
55
  - lib/ar-ondemand.rb
56
56
  - lib/ar-ondemand/delete_all_by_pk.rb
57
57
  - lib/ar-ondemand/for_reading.rb
58
+ - lib/ar-ondemand/for_streaming.rb
58
59
  - lib/ar-ondemand/on_demand.rb
59
60
  - lib/ar-ondemand/record.rb
60
61
  - lib/ar-ondemand/result.rb
@@ -64,7 +65,9 @@ files:
64
65
  - spec/db/schema.rb
65
66
  - spec/db/seeds.rb
66
67
  - spec/factories/audit_record.rb
68
+ - spec/lib/delete_all_by_pk_spec.rb
67
69
  - spec/lib/for_reading_spec.rb
70
+ - spec/lib/for_streaming_spec.rb
68
71
  - spec/spec_helper.rb
69
72
  - spec/support/active_record.rb
70
73
  homepage: https://github.com/CloudHealth/ar-ondemand