activerecord-collections 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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