cohort_analysis 0.4.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,222 +1,310 @@
1
1
  require 'helper'
2
2
 
3
- c = ActiveRecord::Base.connection
4
- c.create_table 'flights', :force => true do |t|
5
- t.string 'origin'
6
- t.string 'dest'
7
- t.string 'airline'
8
- t.string 'plane'
9
- end
10
-
11
3
  class Flight < ActiveRecord::Base
4
+ col :origin
5
+ col :dest
6
+ col :year, :type => :integer
7
+ col :airline
8
+ col :origin_city
9
+ col :dest_city
12
10
  end
11
+ Flight.auto_upgrade!
13
12
 
14
13
  FactoryGirl.define do
15
- factory :lax, :class => Flight do
16
- origin 'LAX'
17
- end
18
14
  factory :lax_sfo, :class => Flight do
19
15
  origin 'LAX'
20
16
  dest 'SFO'
21
17
  end
22
- factory :lax_sfo_co, :class => Flight do
18
+ factory :lax_ord, :class => Flight do
23
19
  origin 'LAX'
24
- dest 'SFO'
25
- airline 'Continental'
20
+ dest 'ORD'
26
21
  end
27
- factory :lax_sfo_a320, :class => Flight do
28
- origin 'LAX'
22
+ factory :ord_sfo, :class => Flight do
23
+ origin 'ORD'
29
24
  dest 'SFO'
30
- plane 'A320'
31
- end
32
- factory :lax_sfo_aa_a320, :class => Flight do
33
- origin 'LAX'
34
- dest 'SFO'
35
- airline 'American'
36
- plane 'A320'
37
25
  end
38
26
  end
39
27
 
40
- describe CohortAnalysis do
41
- before do
42
- Flight.delete_all
28
+ shared_examples_for 'an adapter the provides #cohort' do
29
+ def moot_condition
30
+ Arel.sql('9 = 9')
43
31
  end
44
32
 
45
- describe 'ActiveRecordBaseClassMethods' do
46
- describe :cohort do
47
- it "defaults to :minimum_size => 1" do
48
- FactoryGirl.create(:lax)
49
- Flight.cohort({:origin => 'LAX'}).count.must_equal 1
50
- Flight.cohort({:origin => 'LAX'}, :minimum_size => 2).count.must_equal 0
51
- end
33
+ describe :cohort do
34
+ it "finds the biggest set of records matching the characteristics" do
35
+ FactoryGirl.create(:lax_ord)
36
+ FactoryGirl.create(:lax_sfo)
37
+ assert_count 2, model.cohort(:origin => 'LAX')
38
+ assert_count 1, model.cohort(:dest => 'SFO')
39
+ assert_count 1, model.cohort(:origin => 'LAX', :dest => 'SFO')
40
+ assert_count 0, model.cohort(:dest => 'MSN')
41
+ end
52
42
 
53
- it "doesn't discard characteristics if it doesn't need to" do
54
- FactoryGirl.create(:lax)
55
- FactoryGirl.create(:lax_sfo)
56
- Flight.cohort(:origin => 'LAX', :dest => 'SFO').count.must_equal 1
57
- end
43
+ it "handles arrays of values" do
44
+ FactoryGirl.create(:lax_ord)
45
+ FactoryGirl.create(:lax_sfo)
46
+ assert_count 2, model.cohort(:dest => ['ORD','SFO'])
47
+ assert_count 2, model.cohort(:origin => ['LAX'])
48
+ assert_count 1, model.cohort(:dest => ['SFO'])
49
+ assert_count 1, model.cohort(:origin => ['LAX'], :dest => ['SFO'])
50
+ assert_count 0, model.cohort(:dest => ['MSN'])
51
+ assert_count 1, model.cohort(:dest => ['MSN','SFO'])
52
+ end
58
53
 
59
- it "discards characteristics until it can fulfil the minimum size" do
60
- FactoryGirl.create(:lax)
61
- FactoryGirl.create(:lax_sfo)
62
- drops_dest = Flight.cohort({:origin => 'LAX', :dest => 'SFO'}, :minimum_size => 2)
63
- drops_dest.count.must_equal 2
64
- drops_dest.one? { |flight| flight.dest != 'SFO' }.must_equal true
65
- end
54
+ it "matches everything if empty characteristics" do
55
+ FactoryGirl.create(:lax_ord)
56
+ FactoryGirl.create(:lax_sfo)
57
+ assert_count 2, model.cohort({})
58
+ end
59
+
60
+ it "discards characteristics to maximize size until the minimum size is met" do
61
+ a = FactoryGirl.create(:lax_ord)
62
+ b = FactoryGirl.create(:lax_sfo)
63
+ cohort = model.cohort({:origin => 'LAX', :dest => 'SFO'}, :minimum_size => 2)
64
+ assert_count 2, cohort
65
+ assert_members [a,b], cohort
66
+ end
67
+
68
+ it "returns an empty cohort (basically an impossible condition) unless the minimum size is set" do
69
+ FactoryGirl.create(:lax_ord)
70
+ cohort = model.cohort({:origin => 'LAX'}, :minimum_size => 2)
71
+ assert_count 0, cohort
72
+ end
73
+
74
+ it "discards characteristics in order until a minimum size is met" do
75
+ a = FactoryGirl.create(:lax_ord)
76
+ b = FactoryGirl.create(:lax_sfo)
77
+ cohort = model.cohort({:origin => 'LAX', :dest => 'MSN'}, :minimum_size => 2, :priority => [:origin, :dest])
78
+ assert_count 2, cohort
79
+ assert_members [a,b], cohort
80
+ end
81
+
82
+ it "returns an empty cohort if discarding characteristics in order has that effect" do
83
+ FactoryGirl.create(:lax_ord)
84
+ FactoryGirl.create(:lax_sfo)
85
+ cohort = model.cohort({:origin => 'LAX', :dest => 'MSN'}, :minimum_size => 2, :priority => [:dest, :origin])
86
+ assert_count 0, cohort
87
+ end
88
+
89
+ it "obeys conditions already added" do
90
+ FactoryGirl.create(:lax_ord, :year => 1900)
91
+ FactoryGirl.create(:lax_sfo, :year => 1900)
92
+ FactoryGirl.create(:lax_sfo, :year => 2009)
93
+ FactoryGirl.create(:ord_sfo, :year => 2009)
94
+ year_is_2009 = f_t[:year].eq(2009)
95
+
96
+ assert_count 1, model.where(year_is_2009).cohort(:origin => 'LAX').where(moot_condition)
97
+ assert_count 1, model.where(year_is_2009).cohort(:origin => 'LAX', :dest => 'MSN').where(moot_condition)
98
+
99
+ assert_count 1, model.where(year_is_2009).cohort({:origin => 'LAX', :dest => 'MSN'}, :priority => [:origin, :dest]).where(moot_condition)
100
+ assert_count 0, model.where(year_is_2009).cohort({:origin => 'LAX', :dest => 'MSN'}, :priority => [:dest, :origin]).where(moot_condition)
101
+
102
+ assert_count 0, model.where(year_is_2009).cohort({:origin => 'LAX'}, :minimum_size => 2).where(moot_condition)
103
+ assert_count 0, model.where(year_is_2009).cohort({:origin => 'LAX', :dest => 'MSN'}, :minimum_size => 2).where(moot_condition)
104
+
105
+
106
+ assert_count 1, model.where(year_is_2009).cohort(:origin => 'LAX').where(moot_condition)
107
+ assert_count 1, model.where(year_is_2009).cohort(:origin => 'LAX', :dest => 'SFO').where(moot_condition)
108
+
109
+ assert_count 1, model.where(year_is_2009).cohort({:origin => 'LAX', :dest => 'SFO'}, :priority => [:origin, :dest]).where(moot_condition)
110
+ assert_count 1, model.where(year_is_2009).cohort({:origin => 'LAX', :dest => 'SFO'}, :priority => [:dest, :origin]).where(moot_condition)
111
+
112
+ assert_count 0, model.where(year_is_2009).cohort({:origin => 'LAX'}, :minimum_size => 2).where(moot_condition)
113
+ assert_count 2, model.where(year_is_2009).cohort({:origin => 'LAX', :dest => 'SFO'}, :minimum_size => 2).where(moot_condition)
114
+
115
+
116
+ assert_count 1, model.where(year_is_2009).cohort(:origin => 'LAX').where(moot_condition)
117
+ assert_count 1, model.where(year_is_2009).cohort(:origin => 'LAX', :dest => 'ORD').where(moot_condition)
118
+
119
+ assert_count 1, model.where(year_is_2009).cohort({:origin => 'LAX', :dest => 'ORD'}, :priority => [:origin, :dest]).where(moot_condition)
120
+ assert_count 0, model.where(year_is_2009).cohort({:origin => 'LAX', :dest => 'ORD'}, :priority => [:dest, :origin]).where(moot_condition)
121
+
122
+ assert_count 0, model.where(year_is_2009).cohort({:origin => 'LAX'}, :minimum_size => 2).where(moot_condition)
123
+ assert_count 0, model.where(year_is_2009).cohort({:origin => 'LAX', :dest => 'ORD'}, :minimum_size => 2).where(moot_condition)
124
+
125
+
126
+ assert_count 1, model.where(year_is_2009).cohort(:origin => 'ORD').where(moot_condition)
127
+ assert_count 1, model.where(year_is_2009).cohort(:origin => 'ORD', :dest => 'MSN').where(moot_condition)
128
+
129
+ assert_count 1, model.where(year_is_2009).cohort({:origin => 'ORD', :dest => 'MSN'}, :priority => [:origin, :dest]).where(moot_condition)
130
+ assert_count 0, model.where(year_is_2009).cohort({:origin => 'ORD', :dest => 'MSN'}, :priority => [:dest, :origin]).where(moot_condition)
131
+
132
+ assert_count 0, model.where(year_is_2009).cohort({:origin => 'ORD'}, :minimum_size => 2).where(moot_condition)
133
+ assert_count 0, model.where(year_is_2009).cohort({:origin => 'ORD', :dest => 'MSN'}, :minimum_size => 2).where(moot_condition)
134
+ end
135
+
136
+ it "carries over into conditions added later" do
137
+ FactoryGirl.create(:lax_ord, :year => 1900)
138
+ FactoryGirl.create(:lax_sfo, :year => 1900)
139
+ FactoryGirl.create(:lax_sfo, :year => 2009)
140
+ FactoryGirl.create(:ord_sfo, :year => 2009)
141
+ year_is_2009 = f_t[:year].eq(2009)
142
+
143
+ assert_count 1, model.where(moot_condition).cohort(:origin => 'LAX').where(year_is_2009)
144
+ assert_count 1, model.where(moot_condition).cohort(:origin => 'LAX', :dest => 'MSN').where(year_is_2009)
66
145
 
67
- it "defaults to :strategy => :big" do
68
- FactoryGirl.create(:lax)
69
- Flight.cohort({:origin => 'LAX', :dest => 'SFO'}, :strategy => :big).count.must_equal Flight.cohort(:origin => 'LAX', :dest => 'SFO').count
70
- Flight.cohort({:dest => 'SFO', :origin => 'LAX'}, :strategy => :big).count.must_equal Flight.cohort(:dest => 'SFO', :origin => 'LAX').count
146
+ assert_count 1, model.where(moot_condition).cohort({:origin => 'LAX', :dest => 'MSN'}, :priority => [:origin, :dest]).where(year_is_2009)
147
+ assert_count 0, model.where(moot_condition).cohort({:origin => 'LAX', :dest => 'MSN'}, :priority => [:dest, :origin]).where(year_is_2009)
148
+
149
+ assert_count 0, model.where(moot_condition).cohort({:origin => 'LAX'}, :minimum_size => 2).where(year_is_2009)
150
+ assert_count 0, model.where(moot_condition).cohort({:origin => 'LAX', :dest => 'MSN'}, :minimum_size => 2).where(year_is_2009)
151
+
152
+
153
+ assert_count 1, model.where(moot_condition).cohort(:origin => 'LAX').where(year_is_2009)
154
+ assert_count 1, model.where(moot_condition).cohort(:origin => 'LAX', :dest => 'SFO').where(year_is_2009)
155
+
156
+ assert_count 1, model.where(moot_condition).cohort({:origin => 'LAX', :dest => 'SFO'}, :priority => [:origin, :dest]).where(year_is_2009)
157
+ assert_count 1, model.where(moot_condition).cohort({:origin => 'LAX', :dest => 'SFO'}, :priority => [:dest, :origin]).where(year_is_2009)
158
+
159
+ assert_count 0, model.where(moot_condition).cohort({:origin => 'LAX'}, :minimum_size => 2).where(year_is_2009)
160
+ assert_count 2, model.where(moot_condition).cohort({:origin => 'LAX', :dest => 'SFO'}, :minimum_size => 2).where(year_is_2009)
161
+
162
+
163
+ assert_count 1, model.where(moot_condition).cohort(:origin => 'LAX').where(year_is_2009)
164
+ assert_count 1, model.where(moot_condition).cohort(:origin => 'LAX', :dest => 'ORD').where(year_is_2009)
165
+
166
+ assert_count 1, model.where(moot_condition).cohort({:origin => 'LAX', :dest => 'ORD'}, :priority => [:origin, :dest]).where(year_is_2009)
167
+ assert_count 0, model.where(moot_condition).cohort({:origin => 'LAX', :dest => 'ORD'}, :priority => [:dest, :origin]).where(year_is_2009)
168
+
169
+ assert_count 0, model.where(moot_condition).cohort({:origin => 'LAX'}, :minimum_size => 2).where(year_is_2009)
170
+ assert_count 0, model.where(moot_condition).cohort({:origin => 'LAX', :dest => 'ORD'}, :minimum_size => 2).where(year_is_2009)
171
+
172
+
173
+ assert_count 1, model.where(moot_condition).cohort(:origin => 'ORD').where(year_is_2009)
174
+ assert_count 1, model.where(moot_condition).cohort(:origin => 'ORD', :dest => 'MSN').where(year_is_2009)
175
+
176
+ assert_count 1, model.where(moot_condition).cohort({:origin => 'ORD', :dest => 'MSN'}, :priority => [:origin, :dest]).where(year_is_2009)
177
+ assert_count 0, model.where(moot_condition).cohort({:origin => 'ORD', :dest => 'MSN'}, :priority => [:dest, :origin]).where(year_is_2009)
178
+
179
+ assert_count 0, model.where(moot_condition).cohort({:origin => 'ORD'}, :minimum_size => 2).where(year_is_2009)
180
+ assert_count 0, model.where(moot_condition).cohort({:origin => 'ORD', :dest => 'MSN'}, :minimum_size => 2).where(year_is_2009)
181
+ end
182
+
183
+ it "can get where sql" do
184
+ FactoryGirl.create(:lax_ord)
185
+ FactoryGirl.create(:lax_sfo)
186
+ model.cohort(:origin => 'LAX').where_sql.must_equal %{WHERE ("flights"."origin" = 'LAX')}
187
+ end
188
+
189
+ it "will resolve independently from other cohorts" do
190
+ FactoryGirl.create(:lax_ord)
191
+ FactoryGirl.create(:lax_sfo)
192
+ assert_count 0, model.cohort(:dest => 'SFO').cohort(:dest => 'ORD')
193
+ end
194
+
195
+ it "will resolve independently from other cohorts (complex example)" do
196
+ FactoryGirl.create(:lax_ord)
197
+ FactoryGirl.create(:lax_ord, :origin_city => 'Los Angeles', :airline => 'Delta')
198
+ FactoryGirl.create(:lax_sfo)
199
+ FactoryGirl.create(:lax_ord, :origin_city => 'Los Angeles', :airline => 'Delta', :year => 2000)
200
+ FactoryGirl.create(:lax_sfo, :year => 2000)
201
+ year_condition = f_t[:year].eq(2000)
202
+
203
+ # sanity check
204
+ assert_count 2, model.where(year_condition)
205
+ assert_count 2, model.cohort(:origin => 'LAX', :dest => 'SFO')
206
+ assert_count 2, model.cohort(:origin_city => 'Los Angeles', :airline => 'Delta')
207
+
208
+ assert_count 1, model.cohort(:origin => 'LAX', :dest => 'SFO').where(year_condition)
209
+ assert_count 1, model.where(year_condition).cohort(:origin => 'LAX', :dest => 'SFO')
210
+
211
+ assert_count 2, model.cohort({:origin => 'LAX', :dest => 'SFO'}, :minimum_size => 2).where(year_condition)
212
+ assert_count 2, model.where(year_condition).cohort({:origin => 'LAX', :dest => 'SFO'}, :minimum_size => 2)
213
+
214
+ assert_count 1, model.cohort(:origin_city => 'Los Angeles', :airline => 'Delta').where(year_condition)
215
+ assert_count 1, model.where(year_condition).cohort(:origin_city => 'Los Angeles', :airline => 'Delta')
216
+
217
+ assert_count 1, model.cohort(:origin_city => 'Los Angeles', :airline => 'Delta').where(year_condition)
218
+ assert_count 1, model.where(year_condition).cohort(:origin_city => 'Los Angeles', :airline => 'Delta')
219
+ #--
220
+
221
+ assert_count 0, model.cohort(:origin => 'LAX', :dest => 'SFO').cohort(:origin_city => 'Los Angeles', :airline => 'Delta')
222
+ assert_count 0, model.cohort(:origin_city => 'Los Angeles', :airline => 'Delta').cohort(:origin => 'LAX', :dest => 'SFO')
223
+ end
224
+
225
+ describe "when used with UNION" do
226
+ before do
227
+ @ord = FactoryGirl.create(:lax_ord)
228
+ @sfo = FactoryGirl.create(:lax_sfo)
71
229
  end
72
230
 
73
- it "offers :strategy => :strict" do
74
- FactoryGirl.create(:lax)
75
- if RUBY_VERSION >= '1.9'
76
- # native ordered hashes
77
- Flight.cohort({:origin => 'LAX', :dest => 'SFO'}, :strategy => :strict).count.must_equal 1
78
- Flight.cohort({:dest => 'SFO', :origin => 'LAX'}, :strategy => :strict).count.must_equal 0
79
- else
80
- # activesupport provides ActiveSupport::OrderedHash
81
- origin_important = ActiveSupport::OrderedHash.new
82
- origin_important[:origin] = 'LAX'
83
- origin_important[:dest] = 'SFO'
84
- dest_important = ActiveSupport::OrderedHash.new
85
- dest_important[:dest] = 'SFO'
86
- dest_important[:origin] = 'LAX'
87
- Flight.cohort(origin_important, :strategy => :strict).count.must_equal 1
88
- Flight.cohort(dest_important, :strategy => :strict).count.must_equal 0
89
-
90
- lambda {
91
- Flight.cohort({:origin => 'LAX', :dest => 'SFO'}, :strategy => :strict).count
92
- }.must_raise(ArgumentError, 'hash')
93
- end
231
+ # sanity check!
232
+ it "has tests that use unions properly" do
233
+ ord = model.where(f_t[:dest].eq('ORD')).project(Arel.star)
234
+ sfo = model.where(f_t[:dest].eq('SFO')).project(Arel.star)
235
+ Flight.find_by_sql("SELECT * FROM #{ord.union(sfo).to_sql}").must_equal [@ord, @sfo]
94
236
  end
237
+
238
+ it "builds successful cohorts" do
239
+ ord = model.cohort(:dest => 'ORD').project(Arel.star)
240
+ sfo = model.cohort(:dest => 'SFO').project(Arel.star)
241
+ Flight.find_by_sql("SELECT * FROM #{ord.union(sfo).to_sql}").must_equal [@ord, @sfo]
95
242
 
96
- it "lets you pick :priority of keys when using :strict strategy" do
97
- FactoryGirl.create(:lax)
98
- Flight.cohort({:origin => 'LAX', :dest => 'SFO'}, :strategy => :strict, :priority => [:origin, :dest]).count.must_equal 1
99
- Flight.cohort({:origin => 'LAX', :dest => 'SFO'}, :strategy => :strict, :priority => [:dest, :origin]).count.must_equal 0
100
- Flight.cohort({:dest => 'SFO', :origin => 'LAX'}, :strategy => :strict, :priority => [:origin, :dest]).count.must_equal 1
101
- Flight.cohort({:dest => 'SFO', :origin => 'LAX'}, :strategy => :strict, :priority => [:dest, :origin]).count.must_equal 0
243
+ msn = model.cohort(:origin => 'LAX', :dest => 'MSN').project(Arel.star)
244
+ lhr = model.cohort(:origin => 'LAX', :dest => 'LHR').project(Arel.star)
245
+ Flight.find_by_sql("SELECT * FROM #{msn.union(lhr).to_sql}").must_equal [@ord, @sfo]
102
246
  end
103
247
 
104
- it "lets you play with more than 1 or 2 characteristics" do
105
- ActiveRecord::Base.silence do
106
- # make some fixtures
107
- 1_000.times { FactoryGirl.create(:lax) }
108
- 100.times { FactoryGirl.create(:lax_sfo) }
109
- 10.times { FactoryGirl.create(:lax_sfo_co) }
110
- 3.times { FactoryGirl.create(:lax_sfo_a320) }
111
- 1.times { FactoryGirl.create(:lax_sfo_aa_a320) }
112
- end
113
- Flight.count.must_equal 1_114 # sanity check
114
-
115
- lax_sfo_aa_a320 = {:origin => 'LAX', :dest => 'SFO', :airline => 'American', :plane => 'A320'}
116
- # don't discard anything
117
- Flight.cohort(lax_sfo_aa_a320).count.must_equal 1
118
- # discard airline
119
- Flight.cohort(lax_sfo_aa_a320, :minimum_size => 2).count.must_equal 4
120
- # discard plane and airline
121
- Flight.cohort(lax_sfo_aa_a320, :minimum_size => 5).count.must_equal 114
122
- # discard plane and airline and dest
123
- Flight.cohort(lax_sfo_aa_a320, :minimum_size => 115).count.must_equal 1_114
124
-
125
- lax_sfo_a320 = {:origin => 'LAX', :dest => 'SFO', :plane => 'A320'}
126
- # don't discard anything
127
- Flight.cohort(lax_sfo_a320).count.must_equal 4
128
- # discard plane
129
- Flight.cohort(lax_sfo_a320, :minimum_size => 5).count.must_equal 114
130
- # discard plane and dest
131
- Flight.cohort(lax_sfo_a320, :minimum_size => 115).count.must_equal 1_114
132
-
133
- # off the rails here a bit
134
- woah_lax_co_a320 = {:origin => 'LAX', :airline => 'Continental', :plane => 'A320'}
135
- # discard plane
136
- Flight.cohort(woah_lax_co_a320).count.must_equal 10
137
- # discard plane and airline
138
- Flight.cohort(woah_lax_co_a320, :minimum_size => 11).count.must_equal 1_114
248
+ it "doesn't somehow create unions with false positives" do
249
+ msn = model.cohort(:dest => 'MSN').project(Arel.star)
250
+ lhr = model.cohort(:dest => 'LHR').project(Arel.star)
251
+ ActiveRecord::Base.connection.select_value("SELECT COUNT(*) FROM #{msn.union(lhr).to_sql}").must_equal 0
139
252
  end
140
253
 
141
- it "lets you play with multiple characteristics in :strategy => :strict" do
142
- ActiveRecord::Base.silence do
143
- # make some fixtures
144
- 1_000.times { FactoryGirl.create(:lax) }
145
- 100.times { FactoryGirl.create(:lax_sfo) }
146
- 10.times { FactoryGirl.create(:lax_sfo_co) }
147
- 3.times { FactoryGirl.create(:lax_sfo_a320) }
148
- 1.times { FactoryGirl.create(:lax_sfo_aa_a320) }
149
- end
150
-
151
- lax_sfo_aa_a320 = {:origin => 'LAX', :dest => 'SFO', :airline => 'American', :plane => 'A320'}
152
- priority = [:origin, :dest, :airline, :plane]
153
- # discard nothing
154
- Flight.cohort(lax_sfo_aa_a320, :strategy => :strict, :priority => priority).count.must_equal 1
155
- # (force) discard plane, then (force) discard airline
156
- Flight.cohort(lax_sfo_aa_a320, :strategy => :strict, :priority => priority, :minimum_size => 2).count.must_equal 114
157
- # (force) discard plane, then (force) discard airline, then (force) discard dest
158
- Flight.cohort(lax_sfo_aa_a320, :strategy => :strict, :priority => priority, :minimum_size => 115).count.must_equal 1_114
159
-
160
- priority = [:plane, :airline, :dest, :origin]
161
- # discard nothing
162
- Flight.cohort(lax_sfo_aa_a320, :strategy => :strict, :priority => priority).count.must_equal 1
163
- # (force) discard origin, then (force) discard dest, then (force) discard airline
164
- Flight.cohort(lax_sfo_aa_a320, :strategy => :strict, :priority => priority, :minimum_size => 2).count.must_equal 4
165
- # gives up!
166
- Flight.cohort(lax_sfo_aa_a320, :strategy => :strict, :priority => priority, :minimum_size => 5).count.must_equal 0
254
+ it "builds unions where only one side has rows" do
255
+ msn = model.cohort(:dest => 'MSN').project(Arel.star)
256
+ ord = model.cohort(:dest => 'ORD').project(Arel.star)
257
+ Flight.find_by_sql("SELECT * FROM #{msn.union(ord).to_sql}").must_equal [@ord]
167
258
  end
168
259
  end
260
+ end
261
+ end
169
262
 
170
- describe :cohort_constraint do
171
- it "can be used like other ARel constraints" do
172
- FactoryGirl.create(:lax)
173
- Flight.where(Flight.cohort_constraint(:origin => 'LAX')).count.must_equal 1
174
- Flight.where(Flight.cohort_constraint({:origin => 'LAX'}, :minimum_size => 2)).count.must_equal 0
175
- end
263
+ describe CohortAnalysis do
264
+ def assert_count(expected_count, relation)
265
+ relation = relation.clone
266
+ relation.projections = [Arel.sql('COUNT(*)')]
267
+ sql = relation.to_sql
268
+ ActiveRecord::Base.connection.select_value(sql).must_equal expected_count
269
+ end
176
270
 
177
- it "can be combined with other ARel constraints" do
178
- FactoryGirl.create(:lax)
179
- FactoryGirl.create(:lax_sfo)
180
- origin_lax_constraint = Flight.cohort_constraint(:origin => 'LAX')
181
- dest_sfo_constraint = Flight.arel_table[:dest].eq('SFO')
182
- Flight.where(dest_sfo_constraint.and(origin_lax_constraint)).count.must_equal 1
183
- Flight.where(dest_sfo_constraint.or(origin_lax_constraint)).count.must_equal 2
184
- Flight.where(origin_lax_constraint.and(dest_sfo_constraint)).count.must_equal 1
185
- Flight.where(origin_lax_constraint.or(dest_sfo_constraint)).count.must_equal 2
186
- end
271
+ def assert_members(expected_members, relation)
272
+ relation = relation.clone
273
+ table = relation.source.left
274
+ relation.projections = [Arel.star]
275
+ actual_members = Flight.find_by_sql relation.to_sql
276
+ actual_members.map(&:id).sort.must_equal expected_members.map(&:id).sort
277
+ end
187
278
 
188
- # Caution!
189
- it "is NOT smart enough to enforce minimum size when composed" do
190
- FactoryGirl.create(:lax)
191
- FactoryGirl.create(:lax_sfo)
192
- origin_lax_constraint = Flight.cohort_constraint({:origin => 'LAX'}, :minimum_size => 2)
193
- dest_sfo_constraint = Flight.arel_table[:dest].eq('SFO')
194
- Flight.where(dest_sfo_constraint.and(origin_lax_constraint)).count.must_equal 1 # see how minimum_size is ignored?
195
- Flight.where(origin_lax_constraint.and(dest_sfo_constraint)).count.must_equal 1 # it's because the cohort constraint resolves itself before allowing the ARel visitor to continue
196
- end
279
+ def f_t
280
+ Arel::Table.new(:flights)
281
+ end
282
+
283
+ describe 'ArelSelectManagerInstanceMethods' do
284
+ it_behaves_like 'an adapter the provides #cohort'
285
+ def model
286
+ Arel::SelectManager.new(ActiveRecord::Base, Arel::Table.new(:flights))
197
287
  end
198
288
  end
199
289
 
200
- describe 'ActiveRecordRelationInstanceMethods' do
201
- describe :cohort do
202
- it "is the proper way to compose when other ARel constraints are present" do
203
- FactoryGirl.create(:lax)
204
- FactoryGirl.create(:lax_sfo)
205
- Flight.where(:dest => 'SFO').cohort(:origin => 'LAX').count.must_equal 1
206
- Flight.where(:dest => 'SFO').cohort({:origin => 'LAX'}, :minimum_size => 2).count.must_equal 0
207
- end
290
+ describe 'ArelTableInstanceMethods' do
291
+ it_behaves_like 'an adapter the provides #cohort'
292
+ def model
293
+ Arel::Table.new(:flights, ActiveRecord::Base)
208
294
  end
209
- describe :cohort_constraint do
210
- it "can also be used (carefully) to compose with other ARel constraints" do
211
- FactoryGirl.create(:lax)
212
- FactoryGirl.create(:lax_sfo)
213
- dest_sfo_relation = Flight.where(:dest => 'SFO')
214
- origin_lax_constraint_from_dest_sfo_relation = dest_sfo_relation.cohort_constraint(:origin => 'LAX')
215
- Flight.where(origin_lax_constraint_from_dest_sfo_relation).count.must_equal 1
216
- dest_sfo_relation = Flight.where(:dest => 'SFO')
217
- origin_lax_constraint_from_dest_sfo_relation = dest_sfo_relation.cohort_constraint({:origin => 'LAX'}, :minimum_size => 2)
218
- Flight.where(origin_lax_constraint_from_dest_sfo_relation).count.must_equal 0
219
- end
295
+ end
296
+
297
+ describe 'ActiveRecordBaseClassMethods' do
298
+ it_behaves_like 'an adapter the provides #cohort'
299
+ def model
300
+ Flight
301
+ end
302
+ end
303
+
304
+ describe 'ActiveRecordRelationInstanceMethods' do
305
+ it_behaves_like 'an adapter the provides #cohort'
306
+ def model
307
+ Flight.scoped
220
308
  end
221
309
  end
222
310
  end