ar-ondemand 1.1.5 → 1.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NDhlMjQ2ZTkzNjRkMTc0MmJiMGM3Y2VkYjQ1NDcwZDVlYTA4ZGQzNQ==
4
+ ZmZhMmNkZmQzM2E0M2RlOGVhY2NkZDJmMjE1OTBlNDQ2Zjg2Zjk4Mw==
5
5
  data.tar.gz: !binary |-
6
- Mzk5ZDY1OTdmZjg5YzEzZmI2YTNkNDNhMGI0MTliYzQ2Nzc0ZjU1Mw==
6
+ ZDFmNzAzZjRmN2E5ZTQ1NzM1NTdkNDEzZTA2YThlNGE5Yjc5OTlhMQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- OTRlYjk5NzhhZjU4MmVkOGViMGFjOTkyYWY2MjQ2ZGUxMmE3ZTFlMzE1NWZm
10
- YTQ1ZGFhZWI0NDZlYmUwMDUzYjA0OWIwMGJjMzM1OWRkMTcwNDliODIwZWVl
11
- NzZhN2QzMDc1MWMwY2UxMWI0ZTNhNThjMTFhODBkZjk4MTVjZWI=
9
+ ZDA3MDQzNTg2MDYyMjViNWYwYTdjNzVjOWJlOWYzZGM5Njc5NTlmYmYzNWMz
10
+ MzVlYzkwY2EyYzI2ZTJiNmE2ZTRkMzZjNzNjY2VhYjFkNDkwMzUxNDU1YTkz
11
+ YzRmNjBiMWJmMzg3YmM0YTUwNTk2OGE1NTQzZWQ2YThiYjRhYmI=
12
12
  data.tar.gz: !binary |-
13
- ZDRmOTExOTU0Y2E0ZmI0YjQ2YmUxZGVkOGVhMWU5YTlmNDU4N2FkM2JlYTVj
14
- N2Q0MWU1ZDI2NzViNjY0YTU2ZTU5ZTE2NzAyMmEyNGMzZjE5NTZiNjAyNDZh
15
- NmI1YjFkZWJiZDQ2YTdkNDY2OGQyMDNmOGQwMjYyYjk1YmI0ZjY=
13
+ YTA3MTE5ZDE4ZGM1ZGIxNzA0NzJmZjZiNmM3ZmE1Y2NlNjJjZjg3NTQxYWI0
14
+ OGRjOGZhNWM1M2Q5NTE4ZWRlZGMzYzM0MWM4YmI0OTRiYjk4MDY1NTEzMzdh
15
+ ZWNlZjcxYmZiZjljY2JmMGMzYmE4ZmJkN2NmMTc1NmIyMzlkZTA=
data/Gemfile CHANGED
@@ -11,7 +11,5 @@ group :test, :development do
11
11
 
12
12
  gem 'rspec'
13
13
  gem 'factory_girl'
14
- gem 'shoulda'
15
- gem 'shoulda-matchers'
16
14
  gem 'pry'
17
15
  end
data/Gemfile.lock CHANGED
@@ -1,54 +1,53 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ar-ondemand (1.1.5)
5
- activerecord (>= 3.2)
6
- activesupport (>= 3.2)
4
+ ar-ondemand (1.1.6)
5
+ activerecord (~> 3.2)
6
+ activesupport (~> 3.2)
7
7
 
8
8
  GEM
9
9
  remote: http://rubygems.org/
10
10
  specs:
11
- activemodel (3.2.21)
12
- activesupport (= 3.2.21)
11
+ activemodel (3.2.22.5)
12
+ activesupport (= 3.2.22.5)
13
13
  builder (~> 3.0.0)
14
- activerecord (3.2.21)
15
- activemodel (= 3.2.21)
16
- activesupport (= 3.2.21)
14
+ activerecord (3.2.22.5)
15
+ activemodel (= 3.2.22.5)
16
+ activesupport (= 3.2.22.5)
17
17
  arel (~> 3.0.2)
18
18
  tzinfo (~> 0.3.29)
19
- activesupport (3.2.21)
19
+ activesupport (3.2.22.5)
20
20
  i18n (~> 0.6, >= 0.6.4)
21
21
  multi_json (~> 1.0)
22
22
  arel (3.0.3)
23
23
  builder (3.0.4)
24
- coderay (1.1.0)
24
+ coderay (1.1.1)
25
25
  diff-lcs (1.2.5)
26
- factory_girl (4.5.0)
26
+ factory_girl (4.7.0)
27
27
  activesupport (>= 3.0.0)
28
- i18n (0.6.11)
28
+ i18n (0.7.0)
29
29
  method_source (0.8.2)
30
- multi_json (1.10.1)
31
- pry (0.10.1)
30
+ multi_json (1.12.1)
31
+ pry (0.10.4)
32
32
  coderay (~> 1.1.0)
33
33
  method_source (~> 0.8.1)
34
34
  slop (~> 3.4)
35
- rspec (2.99.0)
36
- rspec-core (~> 2.99.0)
37
- rspec-expectations (~> 2.99.0)
38
- rspec-mocks (~> 2.99.0)
39
- rspec-core (2.99.2)
40
- rspec-expectations (2.99.2)
41
- diff-lcs (>= 1.1.3, < 2.0)
42
- rspec-mocks (2.99.2)
43
- shoulda (3.5.0)
44
- shoulda-context (~> 1.0, >= 1.0.1)
45
- shoulda-matchers (>= 1.4.1, < 3.0)
46
- shoulda-context (1.2.1)
47
- shoulda-matchers (2.7.0)
48
- activesupport (>= 3.0.0)
35
+ rspec (3.5.0)
36
+ rspec-core (~> 3.5.0)
37
+ rspec-expectations (~> 3.5.0)
38
+ rspec-mocks (~> 3.5.0)
39
+ rspec-core (3.5.4)
40
+ rspec-support (~> 3.5.0)
41
+ rspec-expectations (3.5.0)
42
+ diff-lcs (>= 1.2.0, < 2.0)
43
+ rspec-support (~> 3.5.0)
44
+ rspec-mocks (3.5.0)
45
+ diff-lcs (>= 1.2.0, < 2.0)
46
+ rspec-support (~> 3.5.0)
47
+ rspec-support (3.5.0)
49
48
  slop (3.6.0)
50
- sqlite3 (1.3.10)
51
- tzinfo (0.3.42)
49
+ sqlite3 (1.3.12)
50
+ tzinfo (0.3.52)
52
51
 
53
52
  PLATFORMS
54
53
  ruby
@@ -58,6 +57,4 @@ DEPENDENCIES
58
57
  factory_girl
59
58
  pry
60
59
  rspec
61
- shoulda
62
- shoulda-matchers
63
60
  sqlite3
data/README.md CHANGED
@@ -7,7 +7,7 @@ The `ar-ondemand` gem adds functionality to ActiveRecord to help deal with AR's
7
7
 
8
8
  # Getting Started
9
9
 
10
- ```
10
+ ```rb
11
11
  require 'ar-ondemand'
12
12
  ```
13
13
 
@@ -34,7 +34,7 @@ ActiveRecord model so that all the real functionality you'd expect, such as vali
34
34
 
35
35
  ### Usage
36
36
 
37
- ```
37
+ ```rb
38
38
  assets = Widget.on_demand :identifier, {customer_id: 1, account_id: 42}
39
39
  source.each do |dso|
40
40
  w = assets[dso[:identifier]]
@@ -57,13 +57,13 @@ method makes it easy to get access to ActiveRecord-like functionality at 100th t
57
57
 
58
58
  Let's say you have some Widget's. Instead of:
59
59
 
60
- ```
60
+ ```rb
61
61
  Widget.where(customer_id: 1).each { |r| ... }
62
62
  ```
63
63
 
64
64
  use `for_reading` and get a significant speed boost, and use far less memory:
65
65
 
66
- ```
66
+ ```rb
67
67
  Widget.where(customer_id: 1).for_reading.each { |r| ... }
68
68
  ```
69
69
 
@@ -71,24 +71,24 @@ The big limitation is that you can't use `.includes` so you have to be writing a
71
71
 
72
72
  ### Batch Results
73
73
 
74
- ```
74
+ ```rb
75
75
  Widget.where(customer_id: 1).for_reading(batch_size: 50000).each { |b| b.each { |r| } }
76
76
  ```
77
77
 
78
- ### raw_results
78
+ ## raw_results
79
79
 
80
80
  This is just a nice little helper to get the raw database results, which you'd get by calling `ActiveRecord::Base.connection.select_all`
81
81
  but for some reason you can't call that on a model.
82
82
 
83
- ```
83
+ ```rb
84
84
  ActiveRecord::Base.connection.select_all "select * from widgets"
85
85
  ActiveRecord::Base.connection.select_all "select * from widgets where customer_id = 1"
86
86
  ActiveRecord::Base.connection.select_all "select * from widgets where customer_id = 1 limit 100000"
87
87
  ```
88
88
 
89
- use:
89
+ ### Usage
90
90
 
91
- ```
91
+ ```rb
92
92
  Widget.raw_results
93
93
  Widget.where(customer_id: 1).raw_results
94
94
  Widget.where(customer_id: 1).limit(100_000).raw_results
@@ -103,7 +103,7 @@ change your code to use batching.
103
103
 
104
104
  ### Usage
105
105
 
106
- ```
106
+ ```rb
107
107
  def run
108
108
  get_objects.each do |r|
109
109
  do_something r
@@ -117,7 +117,7 @@ end
117
117
 
118
118
  Additional usage:
119
119
 
120
- ```
120
+ ```rb
121
121
  Widget.where(customer_id: 1).for_streaming(batch_size: 100_000).each { |r| }
122
122
  Widget.where(customer_id: 1).for_streaming(for_reading: true).each { |r| }
123
123
  Widget.where(customer_id: 1).for_streaming(for_reading: true, batch_size: 1_000_000).each { |r| }
@@ -132,13 +132,13 @@ the primary keys as the `where` condition. This function does that all for you.
132
132
 
133
133
  ### Usage
134
134
 
135
- ```
135
+ ```rb
136
136
  Widget.delete_all_by_pk
137
137
  Widget.where(Widget[:customer_id].eq(1).and(Widget[:usage].gt(42))).delete_all_by_pk
138
138
  ```
139
139
 
140
140
  If you know you could be deleting millions, then we recommend batching the deletes:
141
141
 
142
- ```
142
+ ```rb
143
143
  Widget.where(Widget[:customer_id].eq(1).and(Widget[:usage].gt(42))).delete_all_by_pk(batch_size: 250_000)
144
144
  ```
data/ar-ondemand.gemspec CHANGED
@@ -20,6 +20,6 @@ Gem::Specification.new do |s|
20
20
 
21
21
  s.require_paths = %w(lib)
22
22
 
23
- s.add_dependency 'activesupport', '>= 3.2'
24
- s.add_dependency 'activerecord', '>= 3.2'
25
- end
23
+ s.add_dependency 'activesupport', '~> 3.2'
24
+ s.add_dependency 'activerecord', '~> 3.2'
25
+ end
data/lib/ar-ondemand.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require_dependency 'ar-ondemand/on_demand'
2
2
  require_dependency 'ar-ondemand/for_reading'
3
3
  require_dependency 'ar-ondemand/for_streaming'
4
- require_dependency 'ar-ondemand/delete_all_by_pk'
4
+ require_dependency 'ar-ondemand/for_enumeration_reading'
5
+ require_dependency 'ar-ondemand/delete_all_by_pk'
@@ -0,0 +1,38 @@
1
+ module ActiveRecord
2
+ module OnDemand
3
+ class FastEnumeration
4
+
5
+ def initialize(model, result_set)
6
+ @result_set = result_set
7
+
8
+ result_set.columns.each_with_index do |name, index|
9
+ self.define_singleton_method(name) {
10
+ raise "Not accessible outside of enumeration" if @row.nil?
11
+ @row[index]
12
+ }
13
+ end
14
+ end
15
+
16
+ def each
17
+ @result_set.rows.each do |r|
18
+ @row = r
19
+ yield(self)
20
+ nil
21
+ end
22
+ ensure
23
+ @row = nil
24
+ end
25
+
26
+ def inject(inj)
27
+ @result_set.rows.each do |r|
28
+ @row = r
29
+ inj = yield(inj, self)
30
+ end
31
+ inj
32
+ ensure
33
+ @row = nil
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,27 @@
1
+ require 'active_support/concern'
2
+ require 'ar-ondemand/fast_enumeration'
3
+
4
+ module ActiveRecord
5
+ module OnDemand
6
+ module EnumerationReading
7
+ extend ::ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+ def for_enumeration_reading
11
+ query_for_enumeration_reading self
12
+ end
13
+
14
+ private
15
+ def query_for_enumeration_reading(ar)
16
+ ar = ar.scoped unless ar.respond_to?(:to_sql)
17
+ ::ActiveRecord::OnDemand::FastEnumeration.new(ar.arel.engine, ::ActiveRecord::Base.connection.exec_query(ar.to_sql))
18
+ end
19
+
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ ::ActiveRecord::Base.send :include, ::ActiveRecord::OnDemand::EnumerationReading
26
+ ::ActiveRecord::Relation.send :include, ::ActiveRecord::OnDemand::EnumerationReading::ClassMethods
27
+
@@ -19,7 +19,6 @@ module ActiveRecord
19
19
  @key_index = @col_indexes[@key_column]
20
20
  raise "Unknown index #{key_column}" if @key_index.nil?
21
21
 
22
- @find_by_method = "find_or_initialize_by_#{(@defaults.keys + [@key_column]).join('_and_')}"
23
22
  @has_any_assets = !ids.empty? || @model.unscoped.where(@defaults).exists?
24
23
  @new_params = @defaults.dup
25
24
  @new_params[@key_column.to_sym] = nil
@@ -28,7 +27,7 @@ module ActiveRecord
28
27
  h[row[@key_index]] = row
29
28
  h
30
29
  end
31
-
30
+
32
31
  self
33
32
  end
34
33
 
@@ -37,8 +36,9 @@ module ActiveRecord
37
36
  rec = @results_to_hash[key]
38
37
  if rec.nil?
39
38
  rec = if @has_any_assets
40
- args = @defaults.values + [key]
41
- @model.unscoped.send(@find_by_method, *args)
39
+ @model.unscoped
40
+ .where(@defaults.merge(@key_column => key))
41
+ .first_or_initialize
42
42
  else
43
43
  @new_params[@key_column.to_sym] = key
44
44
  @model.new @new_params
@@ -1,3 +1,3 @@
1
1
  module ArOnDemand
2
- VERSION = '1.1.5'
2
+ VERSION = '1.1.6'
3
3
  end
@@ -9,7 +9,7 @@ describe 'DeleteAllByPk' do
9
9
  end
10
10
 
11
11
  it 'should delete record' do
12
- AuditRecord.delete_all_by_pk.should eq(1)
12
+ expect(AuditRecord.delete_all_by_pk).to be 1
13
13
  end
14
14
  end
15
15
 
@@ -19,8 +19,8 @@ describe 'DeleteAllByPk' do
19
19
  end
20
20
 
21
21
  it 'should delete record' do
22
- AuditRecord.where('id > 0').delete_all_by_pk.should eq(1)
22
+ expect(AuditRecord.where('id > 0').delete_all_by_pk).to be 1
23
23
  end
24
24
  end
25
25
  end
26
- end
26
+ end
@@ -0,0 +1,74 @@
1
+ require 'spec_helper'
2
+ require 'ar-ondemand'
3
+
4
+ describe 'ForEnumerationReading' do
5
+ context 'Testing' do
6
+ context 'Ensure Persistance Works' do
7
+ before(:each) do
8
+ create(:audit_record)
9
+ end
10
+
11
+ it 'should do persist' do
12
+ expect(AuditRecord.all.length).to be 1
13
+ end
14
+
15
+ it 'should do foo' do
16
+ expect(AuditRecord.first.action).to eq 'create'
17
+ end
18
+ end
19
+
20
+ context 'Iterating' do
21
+ before(:each) do
22
+ (1..25).each do
23
+ create(:audit_record)
24
+ end
25
+ end
26
+
27
+ it 'should support iterating' do
28
+ total = 0
29
+ AuditRecord.where(customer_id: 1).for_enumeration_reading.each do |r|
30
+ total += 1
31
+ end
32
+ expect(total).to be 25
33
+ end
34
+
35
+ it 'should support inject iterating' do
36
+ inj = AuditRecord.where(customer_id: 1).for_enumeration_reading.inject([]) do |i,r|
37
+ i << r.id
38
+ end
39
+ expect(inj.size).to be 25
40
+ end
41
+
42
+ it 'should produce same results as regular iterating' do
43
+ records_a = Set.new
44
+ AuditRecord.where(customer_id: 1).for_enumeration_reading.each do |r|
45
+ records_a.add r.id
46
+ end
47
+
48
+ records_b = Set.new
49
+ AuditRecord.where(customer_id: 1).each do |r|
50
+ records_b.add r.id
51
+ end
52
+
53
+ expect(records_a).to eq records_b
54
+ end
55
+
56
+ it 'should not allow access outside each enumeration' do
57
+ obj = nil
58
+ AuditRecord.where(customer_id: 1).for_enumeration_reading.each do |r|
59
+ obj = r
60
+ end
61
+ expect{obj.id}.to raise_error(RuntimeError)
62
+ end
63
+
64
+ it 'should not allow access outside each inject enumeration' do
65
+ cache = AuditRecord.where(customer_id: 1).for_enumeration_reading.inject({}) do |h,r|
66
+ h[r.id] = r
67
+ h
68
+ end
69
+ expect{cache.first[1].id}.to raise_error(RuntimeError)
70
+ end
71
+
72
+ end
73
+ end
74
+ end
@@ -9,11 +9,11 @@ describe 'ForReading' do
9
9
  end
10
10
 
11
11
  it 'should do persist' do
12
- AuditRecord.all.length.should eq(1)
12
+ expect(AuditRecord.all.length).to be 1
13
13
  end
14
14
 
15
15
  it 'should do foo' do
16
- AuditRecord.first.action.should eq('create')
16
+ expect(AuditRecord.first.action).to eq 'create'
17
17
  end
18
18
  end
19
19
 
@@ -29,7 +29,7 @@ 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(25)
32
+ expect(total).to be 25
33
33
  end
34
34
 
35
35
  it 'should support iterating' do
@@ -37,7 +37,7 @@ describe 'ForReading' do
37
37
  AuditRecord.for_reading.each do |r|
38
38
  total += 1
39
39
  end
40
- total.should eq(25)
40
+ expect(total).to be 25
41
41
  end
42
42
 
43
43
  it 'should produce same results as regular iterating' do
@@ -51,9 +51,9 @@ describe 'ForReading' do
51
51
  records_b.add r.id
52
52
  end
53
53
 
54
- records_a.should eq(records_b)
54
+ expect(records_a).to eq records_b
55
55
  end
56
56
 
57
57
  end
58
58
  end
59
- end
59
+ end
@@ -14,11 +14,10 @@ describe 'ForStreaming' do
14
14
  it 'ClassMethod should support streaming' do
15
15
  total = 0
16
16
  AuditRecord.for_streaming.each do |r|
17
- #r.is_a?(AuditRecord).should eq(true)
18
17
  expect(r).to be_an_instance_of(AuditRecord)
19
18
  total += 1
20
19
  end
21
- total.should eq(25)
20
+ expect(total).to be 25
22
21
  end
23
22
 
24
23
  it 'Relation should support streaming' do
@@ -27,7 +26,7 @@ describe 'ForStreaming' do
27
26
  expect(r).to be_an_instance_of(AuditRecord)
28
27
  total += 1
29
28
  end
30
- total.should eq(25)
29
+ expect(total).to be 25
31
30
  end
32
31
 
33
32
  it 'should support select' do
@@ -38,7 +37,7 @@ describe 'ForStreaming' do
38
37
  expect { r.model_type_id }.to raise_error(::ActiveModel::MissingAttributeError)
39
38
  total += 1
40
39
  end
41
- total.should eq(25)
40
+ expect(total).to be 25
42
41
  end
43
42
  end
44
43
 
@@ -47,23 +46,23 @@ describe 'ForStreaming' do
47
46
  total = 0
48
47
  AuditRecord.for_streaming(for_reading: true).each do |r|
49
48
  expect(r).not_to be_an_instance_of(AuditRecord)
50
- r.customer_id.should eq(1)
49
+ expect(r.customer_id).to be 1
51
50
  total += 1
52
51
  end
53
- total.should eq(25)
52
+ expect(total).to be 25
54
53
  end
55
54
 
56
55
  it 'Relation should support streaming' do
57
56
  total = 0
58
57
  AuditRecord.where(customer_id: 1).for_streaming(for_reading: true).each do |r|
59
58
  expect(r).not_to be_an_instance_of(AuditRecord)
60
- r.customer_id.should eq(1)
59
+ expect(r.customer_id).to be 1
61
60
  total += 1
62
61
  end
63
- total.should eq(25)
62
+ expect(total).to be 25
64
63
  end
65
64
  end
66
65
 
67
66
  end
68
67
  end
69
- end
68
+ end
@@ -5,9 +5,6 @@ ActiveRecord::Migration.suppress_messages do
5
5
  load 'spec/db/seeds.rb'
6
6
  end
7
7
 
8
- require 'shoulda'
9
- require 'shoulda/matchers/integrations/rspec'
10
-
11
8
  RSpec.configure do |config|
12
9
  config.around do |example|
13
10
  ActiveRecord::Base.transaction do
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.5
4
+ version: 1.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steve Frank
@@ -14,28 +14,28 @@ dependencies:
14
14
  name: activesupport
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ! '>='
17
+ - - ~>
18
18
  - !ruby/object:Gem::Version
19
19
  version: '3.2'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ! '>='
24
+ - - ~>
25
25
  - !ruby/object:Gem::Version
26
26
  version: '3.2'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activerecord
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ! '>='
31
+ - - ~>
32
32
  - !ruby/object:Gem::Version
33
33
  version: '3.2'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ! '>='
38
+ - - ~>
39
39
  - !ruby/object:Gem::Version
40
40
  version: '3.2'
41
41
  description: Fast access to database results without the memory overhead of ActiveRecord
@@ -57,6 +57,8 @@ files:
57
57
  - ar-ondemand.gemspec
58
58
  - lib/ar-ondemand.rb
59
59
  - lib/ar-ondemand/delete_all_by_pk.rb
60
+ - lib/ar-ondemand/fast_enumeration.rb
61
+ - lib/ar-ondemand/for_enumeration_reading.rb
60
62
  - lib/ar-ondemand/for_reading.rb
61
63
  - lib/ar-ondemand/for_streaming.rb
62
64
  - lib/ar-ondemand/on_demand.rb
@@ -71,6 +73,7 @@ files:
71
73
  - spec/factories/audit_record.rb
72
74
  - spec/factories/widget.rb
73
75
  - spec/lib/delete_all_by_pk_spec.rb
76
+ - spec/lib/for_enumeration_reading_spec.rb
74
77
  - spec/lib/for_reading_spec.rb
75
78
  - spec/lib/for_streaming_spec.rb
76
79
  - spec/lib/on_demand_spec.rb