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.
- data/.yardopts +2 -0
- data/CHANGELOG +14 -2
- data/Gemfile +0 -9
- data/README.markdown +0 -64
- data/cohort_analysis.gemspec +20 -2
- data/lib/cohort_analysis/active_record_base_class_methods.rb +0 -4
- data/lib/cohort_analysis/active_record_relation_instance_methods.rb +34 -5
- data/lib/cohort_analysis/arel_select_manager_instance_methods.rb +8 -0
- data/lib/cohort_analysis/arel_table_instance_methods.rb +7 -0
- data/lib/cohort_analysis/strategy/big.rb +4 -5
- data/lib/cohort_analysis/strategy/strict.rb +8 -6
- data/lib/cohort_analysis/strategy.rb +98 -11
- data/lib/cohort_analysis/version.rb +1 -1
- data/lib/cohort_analysis.rb +15 -15
- data/test/helper.rb +54 -9
- data/test/test_cohort_analysis.rb +269 -181
- metadata +169 -65
- data/.document +0 -5
@@ -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 :
|
18
|
+
factory :lax_ord, :class => Flight do
|
23
19
|
origin 'LAX'
|
24
|
-
dest '
|
25
|
-
airline 'Continental'
|
20
|
+
dest 'ORD'
|
26
21
|
end
|
27
|
-
factory :
|
28
|
-
origin '
|
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
|
-
|
41
|
-
|
42
|
-
|
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
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|
-
|
97
|
-
|
98
|
-
Flight.
|
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 "
|
105
|
-
|
106
|
-
|
107
|
-
|
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 "
|
142
|
-
|
143
|
-
|
144
|
-
|
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
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
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
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
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
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
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 '
|
201
|
-
|
202
|
-
|
203
|
-
|
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
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
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
|