cohort_analysis 0.4.0 → 1.0.0

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.
@@ -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