activerecord-collections 0.0.2

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 34ee535f77402404d36c364d9a78f8ee1d4d0dc8
4
+ data.tar.gz: 45d79d06830e4c8a90350bf26a953d75fb4f5f9c
5
+ SHA512:
6
+ metadata.gz: 988fccfb23096549e3367a3e8518596d8ba5f362b9ebda32b32958390cfbac806b9c6879f9a294f9b053a2ab1a0a34aad824b99f19f5e3421ee4c8606c1fe4b4
7
+ data.tar.gz: 985765df3ed5f409d9b10a83546076a682422f2d9c9608442aed747b6af98ae32c6964e65adcc29c1426db16b64dd91ceae0c9e2bce178a538a5d933945fa114
@@ -0,0 +1,54 @@
1
+ module ActiveRecord
2
+ class Collection
3
+ include ActiveRecord::Collections::QueryChain
4
+ include ActiveRecord::Collections::Records
5
+ include ActiveRecord::Collections::Batching
6
+ include ActiveRecord::Collections::Delegation
7
+ include ActiveRecord::Collections::Serialization
8
+ attr_reader :model, :relation, :options
9
+
10
+ # dup relation and call none so that we don't end up inspecting it
11
+ # and loading it before we want it
12
+ def inspect
13
+ relation_backup = relation.dup
14
+ @records = @relation = relation.none
15
+ inspected = super
16
+ @records = @relation = relation_backup
17
+ inspected
18
+ end
19
+
20
+ protected
21
+
22
+ def initialize(model, *criteria)
23
+ @model = model
24
+ self.class.instance_eval do
25
+ model_plural = model.name.demodulize.pluralize.underscore
26
+ model_singular = model.name.demodulize.singularize.underscore
27
+ alias_method model_plural.to_sym, :records
28
+ alias_method "#{model_singular}_ids".to_sym, :record_ids
29
+ alias_method "on_#{model_plural}".to_sym, :on_records
30
+ end
31
+ @options = {} # defaults, not implemented yet
32
+ @options.merge!(criteria.extract_options!) if criteria.length > 1
33
+
34
+ if criteria.length == 1
35
+ criteria = criteria.first
36
+ if criteria.is_a?(ActiveRecord::Relation)
37
+ @relation = criteria
38
+ elsif criteria.is_a?(Hash) || criteria.is_a?(String) || criteria.is_a?(Array)
39
+ @relation = model.where(criteria).dup
40
+ end
41
+ else
42
+ @relation = model.where(criteria).dup
43
+ end
44
+ end
45
+
46
+ def initialize_copy(old)
47
+ @options = old.options.dup
48
+ @records = @relation = old.relation.dup
49
+ @total_records = old.total_records if !old.is_batch? && old.instance_variable_get(:@total_records).to_i > 0
50
+ page!(old.current_page).per!(old.per_page) if old.is_batch? || old.batched?(false)
51
+ is_batch! if old.is_batch?
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,245 @@
1
+ module ActiveRecord
2
+ module Collections
3
+ module Batching
4
+ def self.included(base)
5
+ base.send :extend, ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ def default_batch_size(size=nil)
10
+ @default_batch_size = size unless size.nil?
11
+ @default_batch_size ||= 2_000
12
+ end
13
+
14
+ def batching_threshold(threshold=nil)
15
+ @batching_threshold = threshold unless threshold.nil?
16
+ @batching_threshold ||= 10_000
17
+ end
18
+
19
+ def batch_by_default!
20
+ @batch_by_default = true
21
+ end
22
+
23
+ def batch_by_default?
24
+ @batch_by_default || false
25
+ end
26
+
27
+ def page(*num)
28
+ new.page(*num)
29
+ end
30
+ alias_method :batch, :page
31
+
32
+ def per(num)
33
+ new.per(num)
34
+ end
35
+ alias_method :batch_size, :per
36
+ end
37
+
38
+ def default_batch_size
39
+ self.class.default_batch_size
40
+ end
41
+
42
+ def batching_threshold
43
+ self.class.batching_threshold
44
+ end
45
+
46
+ def batch_by_default?
47
+ self.class.batch_by_default?
48
+ end
49
+
50
+ def should_batch?(check_if_batched=true)
51
+ return false if is_batch?
52
+ return false if check_if_batched && batched?
53
+ batch_by_default? ||
54
+ ( batching_threshold > 0 &&
55
+ total_records >= batching_threshold )
56
+ end
57
+
58
+ def is_batch!
59
+ @is_batch = true
60
+ self
61
+ end
62
+
63
+ def is_batch?
64
+ @is_batch || false
65
+ end
66
+ alias_method :batch?, :is_batch?
67
+
68
+ def as_batch
69
+ dup.is_batch!
70
+ end
71
+
72
+ def as_next_batch
73
+ next_page!.as_batch
74
+ end
75
+
76
+ def to_batches
77
+ total_count # init count once before duping
78
+ batched = dup.batch!
79
+ batches = [batched.first_batch!.as_batch]
80
+ while batched.next_batch? do
81
+ batches << batched.next_batch!.as_batch
82
+ end
83
+ batches
84
+ end
85
+
86
+ def as_batches(&block)
87
+ total_count # init count once before duping
88
+ batched = dup.batch!
89
+ batches = [batched.first_batch!.as_batch]
90
+ yield batches.first if block_given?
91
+ while batched.next_batch? do
92
+ b = batched.next_batch!.as_batch
93
+ yield b if block_given?
94
+ batches << b
95
+ end
96
+ batches
97
+ end
98
+ alias_method :in_batches, :as_batches
99
+
100
+ def page(*num)
101
+ dup.page!(*num)
102
+ end
103
+ alias_method :batch, :page
104
+
105
+ def page!(*num)
106
+ reset!(false, false)
107
+ @page = num[0] || 1
108
+ @per ||= default_batch_size
109
+ @relation = relation.page(@page).per(@per)
110
+ self
111
+ end
112
+ alias_method :batch!, :page!
113
+
114
+ def per(num=nil)
115
+ dup.per!(num)
116
+ end
117
+ alias_method :batch_size, :per
118
+
119
+ def per!(num)
120
+ reset!(false, false)
121
+ @page ||= 1
122
+ @per = num
123
+ @relation = relation.page(@page).per(@per)
124
+ self
125
+ end
126
+ alias_method :batch_size!, :per!
127
+
128
+ def paginated?(check_if_should=false)
129
+ return true if !(@page.nil? && @per.nil?)
130
+ if check_if_should && should_batch?(false)
131
+ batch!
132
+ true
133
+ else
134
+ false
135
+ end
136
+ end
137
+ alias_method :batched?, :paginated?
138
+
139
+ def current_page
140
+ @page || 1
141
+ end
142
+ alias_method :current_batch, :current_page
143
+
144
+ def per_page
145
+ @per || total_count
146
+ end
147
+ alias_method :per_batch, :per_page
148
+
149
+ def total_pages
150
+ return 1 if is_batch?
151
+ (total_count.to_f / per_page.to_f).ceil
152
+ end
153
+ alias_method :total_batches, :total_pages
154
+
155
+ def each_page(&block)
156
+ if total_pages <= 1
157
+ yield to_a if block_given?
158
+ return [to_a]
159
+ end
160
+
161
+ first_page!
162
+ paged = []
163
+ total_pages.times do
164
+ paged << to_a
165
+ yield to_a if block_given?
166
+ next_page!
167
+ end
168
+ first_page!
169
+ paged
170
+ end
171
+ alias_method :each_batch, :each_page
172
+
173
+ def page_map(&block)
174
+ if total_pages <= 1
175
+ return (block_given? ? yield(to_a) : to_a)
176
+ end
177
+
178
+ first_page!
179
+ paged = []
180
+ total_pages.times do
181
+ paged << (block_given? ? yield(to_a) : to_a)
182
+ next_page!
183
+ end
184
+ first_page!
185
+ paged
186
+ end
187
+ alias_method :batch_map, :page_map
188
+
189
+ def flat_page_map(&block)
190
+ page_map(&block).flatten
191
+ end
192
+ alias_method :flat_batch_map, :flat_page_map
193
+
194
+ def first_page
195
+ dup.first_page!
196
+ end
197
+ alias_method :first_batch, :first_page
198
+
199
+ def first_page!
200
+ page!(1)
201
+ end
202
+ alias_method :first_batch!, :first_page!
203
+
204
+ def next_page?
205
+ current_page < total_pages
206
+ end
207
+ alias_method :next_batch?, :next_page?
208
+
209
+ def next_page
210
+ dup.next_page!
211
+ end
212
+ alias_method :next_batch, :next_page
213
+
214
+ def next_page!
215
+ page!(current_page + 1) if next_page?
216
+ end
217
+ alias_method :next_batch!, :next_page!
218
+
219
+ def prev_page?
220
+ current_page > 1
221
+ end
222
+ alias_method :prev_batch?, :prev_page?
223
+
224
+ def prev_page
225
+ dup.prev_page!
226
+ end
227
+ alias_method :prev_batch, :prev_page
228
+
229
+ def prev_page!
230
+ page!(current_page - 1) if prev_page?
231
+ end
232
+ alias_method :prev_batch!, :prev_page!
233
+
234
+ def last_page
235
+ dup.last_page!
236
+ end
237
+ alias_method :last_batch, :last_page
238
+
239
+ def last_page!
240
+ page!(total_pages)
241
+ end
242
+ alias_method :last_batch!, :last_page!
243
+ end
244
+ end
245
+ end
@@ -0,0 +1,91 @@
1
+ module ActiveRecord
2
+ module Collections
3
+ module Delegation
4
+ def self.included(base)
5
+ base.send :extend, ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ def method_missing(meth, *args)
10
+ collection = new
11
+ return collection.send(meth, *args) if collection.respond_to?(meth)
12
+ super
13
+ end
14
+
15
+ def respond_to_missing?(meth, include_private=false)
16
+ new.respond_to?(meth, include_private) || super
17
+ end
18
+ end
19
+
20
+ def method_missing(meth, *args)
21
+ if relation.respond_to?(meth)
22
+ return call_on_relation(meth, *args)
23
+ end
24
+
25
+ if records_respond_to?(meth)
26
+ return call_on_records(meth, *args)
27
+ end
28
+
29
+ super
30
+ end
31
+
32
+ def respond_to_missing?(meth, include_private=false)
33
+ records_respond_to?(meth, include_private) ||
34
+ relation.respond_to?(meth, include_private) ||
35
+ super
36
+ end
37
+
38
+ def on_relation(&block)
39
+ collection = dup
40
+ collection.instance_eval do
41
+ def self.method_missing(meth, *args)
42
+ call_on_relation(meth, *args)
43
+ end
44
+ def self.respond_to_missing?(meth, include_private=false)
45
+ relation.respond_to?(meth, include_private)
46
+ end
47
+ end
48
+ return collection.instance_eval(&block) if block_given?
49
+ collection
50
+ end
51
+
52
+ def on_records(&block)
53
+ collection = dup
54
+ collection.instance_eval do
55
+ def self.method_missing(meth, *args)
56
+ call_on_records(meth, *args)
57
+ end
58
+ def self.respond_to_missing?(meth, include_private=false)
59
+ records_respond_to?(meth, include_private)
60
+ end
61
+ end
62
+ return collection.instance_eval(&block) if block_given?
63
+ collection
64
+ end
65
+
66
+ protected
67
+
68
+ def call_on_records(meth, *args)
69
+ return page_map do |batch|
70
+ if model.columns.map(&:name).include?(meth.to_s) && !batch.loaded?
71
+ batch.pluck(meth)
72
+ else
73
+ batch.map { |record| record.send(meth, *args) }
74
+ end
75
+ end
76
+ end
77
+
78
+ def records_respond_to?(meth, include_private=false)
79
+ model.public_instance_methods.include?(meth) ||
80
+ (include_private && model.private_instance_methods.include?(meth)) ||
81
+ (!records.nil? && records.loaded? && records.first.respond_to?(meth, include_private))
82
+ end
83
+
84
+ def call_on_relation(meth, *args)
85
+ reset!
86
+ @relation = relation.send(meth, *args)
87
+ self
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,164 @@
1
+ module ActiveRecord
2
+ module Collections
3
+ module QueryChain
4
+ def self.included(base)
5
+ base.send :extend, ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ def select(*args)
10
+ new.select(*args)
11
+ end
12
+
13
+ def distinct(bool=true)
14
+ new.distinct(bool)
15
+ end
16
+
17
+ def where(*args, &block)
18
+ new.where(*args, &block)
19
+ end
20
+
21
+ def order(*args, &block)
22
+ new.order(*args, &block)
23
+ end
24
+
25
+ def limit(*args, &block)
26
+ new.limit(*args, &block)
27
+ end
28
+
29
+ def joins(*args)
30
+ new.joins(*args)
31
+ end
32
+
33
+ def includes(*args)
34
+ new.includes(*args)
35
+ end
36
+
37
+ def references(*table_names)
38
+ new.references(*table_names)
39
+ end
40
+ end
41
+
42
+ def all
43
+ reset.limit!(nil)
44
+ end
45
+
46
+ def load
47
+ relation.load
48
+ records
49
+ end
50
+
51
+ def select(*args)
52
+ dup.select!(*args)
53
+ end
54
+
55
+ def select!(*args)
56
+ reset!
57
+ @relation = relation.select(*args)
58
+ self
59
+ end
60
+
61
+ def distinct(bool=true)
62
+ dup.distinct!(bool)
63
+ end
64
+
65
+ def distinct!(bool=true)
66
+ reset!
67
+ @relation = relation.distinct(bool)
68
+ self
69
+ end
70
+
71
+ def where(*args, &block)
72
+ dup.where!(*args, &block)
73
+ end
74
+
75
+ def where!(*args, &block)
76
+ reset!
77
+ relation.where!(*args, &block)
78
+ self
79
+ end
80
+
81
+ def not(*args, &block)
82
+ dup.not!(*args, &block)
83
+ end
84
+
85
+ def not!(*args, &block)
86
+ reset!
87
+ @relation = relation.where.not(*args, &block)
88
+ self
89
+ end
90
+
91
+ def or(*args, &block)
92
+ dup.or!(*args, &block)
93
+ end
94
+
95
+ def or!(*args, &block)
96
+ reset!
97
+ @relation = relation.or.where(*args, &block)
98
+ self
99
+ end
100
+
101
+ def order(*args, &block)
102
+ dup.order!(*args, &block)
103
+ end
104
+
105
+ def order!(*args, &block)
106
+ reset!(false)
107
+ relation.order!(*args, &block)
108
+ self
109
+ end
110
+
111
+ def limit(*args, &block)
112
+ dup.limit!(*args, &block)
113
+ end
114
+
115
+ def limit!(*args, &block)
116
+ reset!
117
+ relation.limit!(*args, &block)
118
+ self
119
+ end
120
+
121
+ def joins(*args)
122
+ dup.joins!(*args)
123
+ end
124
+
125
+ def joins!(*args)
126
+ reset!
127
+ relation.joins!(*args)
128
+ self
129
+ end
130
+
131
+ def includes(*args)
132
+ dup.includes!(*args)
133
+ end
134
+
135
+ def includes!(*args)
136
+ reset!
137
+ relation.includes!(*args)
138
+ self
139
+ end
140
+
141
+ def references(*table_names)
142
+ dup.references!(*table_names)
143
+ end
144
+
145
+ def references!(*table_names)
146
+ reset!
147
+ relation.references!(*table_names)
148
+ self
149
+ end
150
+
151
+ def reset(clear_total=true, clear_pages=true)
152
+ dup.reset!(clear_total, clear_pages)
153
+ end
154
+
155
+ def reset!(clear_total=true, clear_pages=true)
156
+ @records = @record_ids = @size = nil
157
+ @page = @per = nil if clear_pages
158
+ @total_records = nil if clear_total
159
+ relation.reset
160
+ self
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,65 @@
1
+ module ActiveRecord
2
+ module Collections
3
+ module Records
4
+ def records
5
+ @records ||= relation
6
+ end
7
+
8
+ def record_ids
9
+ @record_ids ||= records.loaded? ? records.map(&:id) : records.pluck(:id)
10
+ end
11
+
12
+ def pluck(col)
13
+ relation.pluck(col)
14
+ end
15
+
16
+ def to_ary
17
+ records.to_a
18
+ end
19
+ alias_method :to_a, :to_ary
20
+
21
+ def total_records
22
+ @total_records ||= relation.limit(nil).count
23
+ end
24
+
25
+ def total_count
26
+ #batch! if try(:should_batch?)
27
+ total_records
28
+ end
29
+ alias_method :total, :total_count
30
+ alias_method :count, :total_count
31
+
32
+ def size
33
+ @size ||= relation.size
34
+ end
35
+
36
+ def length
37
+ to_a.length
38
+ end
39
+
40
+ def each(&block)
41
+ batch! if try(:should_batch?)
42
+
43
+ if try(:batched?)
44
+ flat_batch_map.each { |record| block_given? ? yield(record) : record }
45
+ else
46
+ records.each { |record| block_given? ? yield(record) : record }
47
+ end
48
+ end
49
+
50
+ def map(&block)
51
+ batch! if try(:should_batch?)
52
+
53
+ if try(:batched?)
54
+ flat_batch_map.map { |record| block_given? ? yield(record) : record }
55
+ else
56
+ each.map { |record| block_given? ? yield(record) : record }
57
+ end
58
+ end
59
+
60
+ def flat_map(&block)
61
+ map(&block).flatten
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,61 @@
1
+ module ActiveRecord
2
+ module Collections
3
+ module Serialization
4
+ def self.included(base)
5
+ base.send :extend, ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ def from_json(json)
10
+ from_hash JSON.load(json)
11
+ end
12
+
13
+ def from_hash(hash)
14
+ hash.symbolize_keys!
15
+ collection = new
16
+ collection.select!(*hash[:select]) unless hash[:select].empty?
17
+ collection.distinct! if hash[:distinct] == true
18
+ collection.joins!(*hash[:joins]) unless hash[:joins].empty?
19
+ collection.references!(*hash[:references]) unless hash[:references].empty?
20
+ collection.includes!(*hash[:includes]) unless hash[:includes].empty?
21
+ collection.where!(*hash[:bind].map { |b| b[:value] }.unshift(hash[:where].join(" AND ").gsub(/\$\d/,'?'))) unless hash[:where].empty?
22
+ collection.order!(hash[:order]) unless hash[:order].empty?
23
+ collection.limit!(hash[:limit]) unless hash[:limit].empty?
24
+ collection.offset!(hash[:offset]) unless hash[:offset].empty?
25
+ collection
26
+ end
27
+ end
28
+
29
+ def to_sql
30
+ relation.to_sql
31
+ end
32
+
33
+ def to_hash(include_limit=false)
34
+ h = {
35
+ select: relation.select_values,
36
+ distinct: relation.distinct_value,
37
+ joins: relation.joins_values,
38
+ references: relation.references_values,
39
+ includes: relation.includes_values,
40
+ where: relation.where_values.map { |v| v.is_a?(String) ? v : v.to_sql },
41
+ order: relation.order_values.map { |v| v.is_a?(String) ? v : v.to_sql },
42
+ bind: relation.bind_values.map { |b| {name: b.first.name, value: b.last} }
43
+ }
44
+ if include_limit || try(:is_batch?)
45
+ h[:limit] = relation.limit_value
46
+ h[:offset] = relation.offset_value
47
+ end
48
+ h
49
+ end
50
+ alias_method :to_h, :to_hash
51
+
52
+ def to_json(options=nil)
53
+ to_hash.to_json
54
+ end
55
+
56
+ def to_param
57
+ to_json
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,5 @@
1
+ module ActiveRecord
2
+ module Collections
3
+ VERSION = '0.0.2'
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ require 'active_record/collections/batching'
2
+ require 'active_record/collections/delegation'
3
+ require 'active_record/collections/query_chain'
4
+ require 'active_record/collections/records'
5
+ require 'active_record/collections/serialization'
6
+ require 'active_record/collection'
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe ActiveRecord::Collection do
4
+ context 'querying' do
5
+ before(:each) { create(:retailer) }
6
+
7
+ it 'should return the same records as a standard relation' do
8
+ retailer = Retailer.all.to_a.sample
9
+ collection = StockedProducts.where(retailer_id: retailer.id)
10
+ relation = StockedProduct.where(retailer_id: retailer.id)
11
+ expect(collection.count).to eql(relation.count)
12
+ expect(collection.pluck(:id).sort).to eql(relation.pluck(:id).sort)
13
+ end
14
+ end
15
+
16
+ context 'batching' do
17
+ describe 'default_batch_size' do
18
+ it 'should default to 2000' do
19
+ expect(ActiveRecord::Collection.default_batch_size).to eql(2000)
20
+ end
21
+
22
+ it 'should be overridable by extending classes' do
23
+ expect(StockedProducts.default_batch_size).to eql(200)
24
+ end
25
+ end
26
+
27
+ describe 'batching_threshold' do
28
+ it 'should default to 10000' do
29
+ expect(ActiveRecord::Collection.batching_threshold).to eql(10000)
30
+ end
31
+
32
+ it 'should be overridable by extending classes' do
33
+ expect(StockedProducts.batching_threshold).to eql(500)
34
+ end
35
+ end
36
+ end
37
+ end
data/spec/db/schema.rb ADDED
@@ -0,0 +1,23 @@
1
+ ActiveRecord::Schema.define(:version => 0) do
2
+ create_table :products do |t|
3
+ t.string :brand
4
+ t.string :name
5
+ end
6
+
7
+ create_table :retailers do |t|
8
+ t.string :name
9
+ end
10
+
11
+ create_table :stocked_products do |t|
12
+ t.integer :product_id
13
+ t.integer :retailer_id
14
+ t.integer :store_location_id
15
+ t.integer :stocked
16
+ t.float :cost
17
+ end
18
+
19
+ create_table :store_locations do |t|
20
+ t.integer :retailer_id
21
+ t.string :address
22
+ end
23
+ end
@@ -0,0 +1,5 @@
1
+ FactoryGirl.define do
2
+ factory :product do
3
+ name { Faker::Commerce.product_name }
4
+ end
5
+ end
@@ -0,0 +1,13 @@
1
+ FactoryGirl.define do
2
+ factory :retailer do
3
+ name { Faker::Company.name }
4
+ after(:create) do |retailer, evaluator|
5
+ if Product.all.empty?
6
+ 20.times { create(:product) }
7
+ end
8
+ 5.times do
9
+ create(:store_location, retailer: retailer)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ FactoryGirl.define do
2
+ factory :stocked_product do
3
+ product
4
+ retailer
5
+ store_location
6
+ stocked { (0..50).to_a.sample }
7
+ cost { Faker::Commerce.price }
8
+ end
9
+ end
@@ -0,0 +1,18 @@
1
+ FactoryGirl.define do
2
+ factory :store_location do
3
+ retailer
4
+ address { [Faker::Address.street_address, Faker::Address.zip].join(" ") }
5
+ after(:create) do |location, evaluator|
6
+ products = Product.all
7
+ if products.empty?
8
+ 20.times do
9
+ create(:stocked_product, store_location: location, retailer: location.retailer)
10
+ end
11
+ else
12
+ products.each do |product|
13
+ create(:stocked_product, store_location: location, retailer: location.retailer, product: product)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,115 @@
1
+ require 'active_record'
2
+ require 'activerecord-collections'
3
+ require 'factory_girl'
4
+ require 'faker'
5
+ require 'rspec'
6
+
7
+ Dir[File.join(File.dirname(__FILE__), '..', "spec/support/**/*.rb")].each { |f| require f }
8
+ Dir[File.join(File.dirname(__FILE__), '..', "spec/factories/**/*.rb")].each { |f| require f }
9
+
10
+ RSpec.configure do |config|
11
+ config.before(:suite) do
12
+ ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
13
+ capture_stdout { load "db/schema.rb" }
14
+ load 'support/models.rb'
15
+ end
16
+
17
+ # Using Factory Girl instead of fixtures
18
+ config.include FactoryGirl::Syntax::Methods
19
+ # Lint your factories before running the suite to find any errors up front
20
+ config.before(:suite) do
21
+ ActiveRecord::Base.transaction do
22
+ begin
23
+ FactoryGirl.lint
24
+ rescue FactoryGirl::InvalidFactoryError => e
25
+ puts e.message
26
+ end
27
+ raise ActiveRecord::Rollback
28
+ end
29
+ end
30
+
31
+ # rspec-expectations config goes here. You can use an alternate
32
+ # assertion/expectation library such as wrong or the stdlib/minitest
33
+ # assertions if you prefer.
34
+ config.expect_with :rspec do |expectations|
35
+ # This option will default to `true` in RSpec 4. It makes the `description`
36
+ # and `failure_message` of custom matchers include text for helper methods
37
+ # defined using `chain`, e.g.:
38
+ # be_bigger_than(2).and_smaller_than(4).description
39
+ # # => "be bigger than 2 and smaller than 4"
40
+ # ...rather than:
41
+ # # => "be bigger than 2"
42
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
43
+ end
44
+
45
+ # rspec-mocks config goes here. You can use an alternate test double
46
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
47
+ config.mock_with :rspec do |mocks|
48
+ # Prevents you from mocking or stubbing a method that does not exist on
49
+ # a real object. This is generally recommended, and will default to
50
+ # `true` in RSpec 4.
51
+ mocks.verify_partial_doubles = true
52
+ end
53
+
54
+ # Many RSpec users commonly either run the entire suite or an individual
55
+ # file, and it's useful to allow more verbose output when running an
56
+ # individual spec file.
57
+ if config.files_to_run.one?
58
+ # Use the documentation formatter for detailed output,
59
+ # unless a formatter has already been configured
60
+ # (e.g. via a command-line flag).
61
+ config.default_formatter = 'doc'
62
+ end
63
+
64
+ # Limits the available syntax to the non-monkey patched syntax that is recommended.
65
+ # For more details, see:
66
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
67
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
68
+ # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
69
+ config.disable_monkey_patching!
70
+
71
+ # Run specs in random order to surface order dependencies. If you find an
72
+ # order dependency and want to debug it, you can fix the order by providing
73
+ # the seed, which is printed after each run.
74
+ # --seed 1234
75
+ config.order = :random
76
+
77
+ # Seed global randomization in this process using the `--seed` CLI option.
78
+ # Setting this allows you to use `--seed` to deterministically reproduce
79
+ # test failures related to randomization by passing the same `--seed` value
80
+ # as the one that triggered the failure.
81
+ Kernel.srand config.seed
82
+
83
+ # Print the 10 slowest examples and example groups at the
84
+ # end of the spec run, to help surface which specs are running
85
+ # particularly slow.
86
+ #config.profile_examples = 10
87
+
88
+ # These two settings work together to allow you to limit a spec run
89
+ # to individual examples or groups you care about by tagging them with
90
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
91
+ # get run.
92
+ #config.filter_run :focus
93
+ #config.run_all_when_everything_filtered = true
94
+ end
95
+
96
+ # TODO look into why I need to patch these to work with default behavior?
97
+ class FalseClass
98
+ def false?
99
+ true
100
+ end
101
+
102
+ def true?
103
+ false
104
+ end
105
+ end
106
+
107
+ class TrueClass
108
+ def false?
109
+ false
110
+ end
111
+
112
+ def true?
113
+ true
114
+ end
115
+ end
@@ -0,0 +1,12 @@
1
+ require 'stringio'
2
+
3
+ module Kernel
4
+ def capture_stdout
5
+ out = StringIO.new
6
+ $stdout = out
7
+ yield
8
+ return out
9
+ ensure
10
+ $stdout = STDOUT
11
+ end
12
+ end
@@ -0,0 +1,29 @@
1
+ class Retailer < ActiveRecord::Base
2
+ has_many :stocked_products
3
+ end
4
+
5
+ class StoreLocation < ActiveRecord::Base
6
+ belongs_to :retailer
7
+ has_many :stocked_products
8
+ end
9
+
10
+ class Product < ActiveRecord::Base
11
+ has_many :stocked_products
12
+ end
13
+
14
+ class StockedProduct < ActiveRecord::Base
15
+ belongs_to :product
16
+ belongs_to :store_location
17
+ belongs_to :retailer
18
+ end
19
+
20
+ class StockedProducts < ActiveRecord::Collection
21
+ default_batch_size 200
22
+ batching_threshold 500
23
+
24
+ protected
25
+
26
+ def initialize(*criteria)
27
+ super(StockedProduct, *criteria)
28
+ end
29
+ end
metadata ADDED
@@ -0,0 +1,156 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activerecord-collections
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Mark Rebec
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-01-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: sqlite3
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: factory_girl
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: faker
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Create collections of records, represented by ActiveRecord::Relation
98
+ query criteria which can be serialized and passed around without executing queries
99
+ or loading records.
100
+ email:
101
+ - mark@markrebec.com
102
+ executables: []
103
+ extensions: []
104
+ extra_rdoc_files: []
105
+ files:
106
+ - lib/active_record/collection.rb
107
+ - lib/active_record/collections/batching.rb
108
+ - lib/active_record/collections/delegation.rb
109
+ - lib/active_record/collections/query_chain.rb
110
+ - lib/active_record/collections/records.rb
111
+ - lib/active_record/collections/serialization.rb
112
+ - lib/active_record/collections/version.rb
113
+ - lib/activerecord-collections.rb
114
+ - spec/active_record/collection_spec.rb
115
+ - spec/db/schema.rb
116
+ - spec/factories/products.rb
117
+ - spec/factories/retailers.rb
118
+ - spec/factories/stocked_products.rb
119
+ - spec/factories/store_locations.rb
120
+ - spec/spec_helper.rb
121
+ - spec/support/capture_stdout.rb
122
+ - spec/support/models.rb
123
+ homepage: http://github.com/markrebec/activerecord-collections
124
+ licenses: []
125
+ metadata: {}
126
+ post_install_message:
127
+ rdoc_options: []
128
+ require_paths:
129
+ - lib
130
+ required_ruby_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ required_rubygems_version: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ requirements: []
141
+ rubyforge_project:
142
+ rubygems_version: 2.2.2
143
+ signing_key:
144
+ specification_version: 4
145
+ summary: Create collections of records, represented by ActiveRecord::Relation query
146
+ criteria.
147
+ test_files:
148
+ - spec/active_record/collection_spec.rb
149
+ - spec/db/schema.rb
150
+ - spec/factories/products.rb
151
+ - spec/factories/retailers.rb
152
+ - spec/factories/stocked_products.rb
153
+ - spec/factories/store_locations.rb
154
+ - spec/spec_helper.rb
155
+ - spec/support/capture_stdout.rb
156
+ - spec/support/models.rb