weighted_average 0.0.3 → 0.0.4
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.
- data/VERSION +1 -1
- data/lib/weighted_average.rb +63 -65
- data/test/helper.rb +10 -14
- data/test/test_weighted_average.rb +20 -6
- metadata +3 -3
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.4
|
data/lib/weighted_average.rb
CHANGED
@@ -1,76 +1,74 @@
|
|
1
|
-
require 'active_support'
|
2
1
|
require 'active_record'
|
2
|
+
require 'active_support'
|
3
|
+
require 'active_support/version'
|
4
|
+
%w{
|
5
|
+
active_support/core_ext/module
|
6
|
+
}.each do |active_support_3_requirement|
|
7
|
+
require active_support_3_requirement
|
8
|
+
end if ActiveSupport::VERSION::MAJOR == 3
|
3
9
|
|
4
|
-
module
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
end
|
10
|
-
|
11
|
-
# Returns the ARel relation
|
12
|
-
def weighted_average_relation(column_names, options = {})
|
13
|
-
raise ArgumentError, "Only use array form if the weighting column in the foreign table is not called 'weighting'" if options[:weighted_by].is_a?(Array) and options[:weighted_by].length != 2
|
14
|
-
raise ArgumentError, "No nil values in weighted_by, please" if Array.wrap(options[:weighted_by]).any?(&:nil?)
|
15
|
-
|
16
|
-
# aircraft['seats'] or (aircraft['seats'] + aircraft['payload'])
|
17
|
-
columns = Array.wrap(column_names).map { |column_name| arel_table[column_name.to_s] }
|
18
|
-
|
19
|
-
# :airline_aircraft_seat_class
|
20
|
-
association = if options[:weighted_by].present?
|
21
|
-
options[:weighted_by].is_a?(Array) ? reflect_on_association(options[:weighted_by].first.to_sym) : reflect_on_association(options[:weighted_by].to_sym)
|
22
|
-
end
|
23
|
-
|
24
|
-
# AirlineAircraftSeatClass
|
25
|
-
association_class = association.klass if association
|
26
|
-
|
27
|
-
# AirlineAircraftSeatClass.arel_table
|
28
|
-
foreign_arel_table = association_class.arel_table if association_class
|
10
|
+
module WeightedAverage
|
11
|
+
# Returns a number
|
12
|
+
def weighted_average(*args)
|
13
|
+
connection.select_value(weighted_average_relation(*args).to_sql, 'weighted_average').to_f
|
14
|
+
end
|
29
15
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
# 'weighting'
|
38
|
-
weighted_by_column = if association_class and options[:weighted_by].is_a?(Array)
|
39
|
-
options[:weighted_by].last.to_s
|
40
|
-
elsif !association_class and (options[:weighted_by].is_a?(String) or options[:weighted_by].is_a?(Symbol))
|
41
|
-
options[:weighted_by].to_s
|
42
|
-
else
|
43
|
-
'weighting'
|
44
|
-
end
|
45
|
-
|
46
|
-
# [foreign_]arel_table['weighting']
|
47
|
-
weighted_by = if foreign_arel_table
|
48
|
-
foreign_arel_table[weighted_by_column]
|
49
|
-
else
|
50
|
-
arel_table[weighted_by_column]
|
51
|
-
end
|
16
|
+
# Returns the ARel relation
|
17
|
+
def weighted_average_relation(column_names, options = {})
|
18
|
+
raise ArgumentError, "Only use array form if the weighting column in the foreign table is not called 'weighting'" if options[:weighted_by].is_a?(Array) and options[:weighted_by].length != 2
|
19
|
+
raise ArgumentError, "No nil values in weighted_by, please" if Array.wrap(options[:weighted_by]).any?(&:nil?)
|
20
|
+
|
21
|
+
# aircraft['seats'] or (aircraft['seats'] + aircraft['payload'])
|
22
|
+
columns = Array.wrap(column_names).map { |column_name| arel_table[column_name.to_s] }
|
52
23
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
24
|
+
# :airline_aircraft_seat_class
|
25
|
+
association = if options[:weighted_by].present?
|
26
|
+
options[:weighted_by].is_a?(Array) ? reflect_on_association(options[:weighted_by].first.to_sym) : reflect_on_association(options[:weighted_by].to_sym)
|
27
|
+
end
|
28
|
+
|
29
|
+
# AirlineAircraftSeatClass
|
30
|
+
association_class = association.klass if association
|
31
|
+
|
32
|
+
# AirlineAircraftSeatClass.arel_table
|
33
|
+
foreign_arel_table = association_class.arel_table if association_class
|
34
|
+
|
35
|
+
# 'weighting'
|
36
|
+
weighted_by_column = if association_class and options[:weighted_by].is_a?(Array)
|
37
|
+
options[:weighted_by].last.to_s
|
38
|
+
elsif !association_class and (options[:weighted_by].is_a?(String) or options[:weighted_by].is_a?(Symbol))
|
39
|
+
options[:weighted_by].to_s
|
40
|
+
else
|
41
|
+
'weighting'
|
42
|
+
end
|
43
|
+
|
44
|
+
# [foreign_]arel_table['weighting']
|
45
|
+
weighted_by = if foreign_arel_table
|
46
|
+
foreign_arel_table[weighted_by_column]
|
47
|
+
else
|
48
|
+
arel_table[weighted_by_column]
|
49
|
+
end
|
57
50
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
end
|
51
|
+
disaggregate_by = if options[:disaggregate_by].present?
|
52
|
+
raise ArgumentError, "Disaggregating by a foreign table isn't supported right now" if options[:disaggregate_by].is_a?(Array)
|
53
|
+
arel_table[options[:disaggregate_by].to_s]
|
54
|
+
end
|
63
55
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
end
|
68
|
-
relation = relation.outer_join(foreign_arel_table).on(join_on) if foreign_arel_table
|
69
|
-
relation
|
56
|
+
relation = select("(SUM((#{columns.map { |column| column.to_sql }.join(' + ')}) #{"/ #{disaggregate_by.to_sql} " if disaggregate_by}* #{weighted_by.to_sql}) / SUM(#{weighted_by.to_sql})) AS weighted_average")
|
57
|
+
columns.each do |column|
|
58
|
+
relation = relation.where("#{column.to_sql} IS NOT NULL")
|
70
59
|
end
|
60
|
+
# FIXME this will break on through relationships, where it has to be :aircraft => :aircraft_class
|
61
|
+
relation = relation.joins(association.name) if association_class
|
62
|
+
relation
|
71
63
|
end
|
72
64
|
end
|
73
65
|
|
66
|
+
ActiveRecord::Associations::AssociationCollection.class_eval do
|
67
|
+
delegate :weighted_average, :weighted_average_relation, :to => :scoped
|
68
|
+
end
|
74
69
|
ActiveRecord::Base.class_eval do
|
75
|
-
|
76
|
-
|
70
|
+
class << self
|
71
|
+
delegate :weighted_average, :weighted_average_relation, :to => :scoped
|
72
|
+
end
|
73
|
+
end
|
74
|
+
ActiveRecord::Relation.send :include, WeightedAverage
|
data/test/helper.rb
CHANGED
@@ -13,6 +13,7 @@ class Test::Unit::TestCase
|
|
13
13
|
end
|
14
14
|
|
15
15
|
$logger = Logger.new STDOUT #'test/test.log'
|
16
|
+
$logger.level = Logger::INFO
|
16
17
|
|
17
18
|
ActiveSupport::Notifications.subscribe do |*args|
|
18
19
|
event = ActiveSupport::Notifications::Event.new(*args)
|
@@ -209,6 +210,8 @@ class Segment < ActiveRecord::Base
|
|
209
210
|
validates_presence_of :row_hash
|
210
211
|
extend CohortScope
|
211
212
|
self.minimum_cohort_size = 1
|
213
|
+
belongs_to :aircraft, :foreign_key => 'bts_aircraft_type', :primary_key => 'bts_aircraft_type'
|
214
|
+
has_one :aircraft_class, :through => :aircraft
|
212
215
|
end
|
213
216
|
|
214
217
|
(1..10).each do |i|
|
@@ -223,30 +226,23 @@ end
|
|
223
226
|
# Segment.create! :payload => 6, :row_hash => 'dsiauhiluashdliufhalsidfhuailsd'
|
224
227
|
|
225
228
|
class AirlineAircraftSeatClass < ActiveRecord::Base
|
226
|
-
|
227
|
-
|
228
|
-
# belongs_to :airline, :class_name => 'Airline', :foreign_key => 'airline_id'
|
229
|
-
belongs_to :aircraft, :class_name => 'Aircraft', :foreign_key => 'aircraft_id'
|
230
|
-
# belongs_to :seat_class, :class_name => 'SeatClass', :foreign_key => 'seat_class_id'
|
231
|
-
has_one :aircraft_class, :class_name => 'AircraftClass', :through => :aircraft
|
229
|
+
belongs_to :aircraft
|
230
|
+
has_one :aircraft_class, :through => :aircraft
|
232
231
|
end
|
233
232
|
|
234
233
|
|
235
234
|
class Aircraft < ActiveRecord::Base
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
# has_many :seat_classes, :class_name => 'AircraftSeatClass'
|
240
|
-
has_many :segments, :class_name => "Segment"
|
241
|
-
has_many :airline_aircraft_seat_classes, :class_name => 'AirlineAircraftSeatClass'
|
235
|
+
has_many :segments, :foreign_key => 'bts_aircraft_type', :primary_key => 'bts_aircraft_type'
|
236
|
+
belongs_to :aircraft_class
|
237
|
+
has_many :airline_aircraft_seat_classes
|
242
238
|
end
|
243
239
|
|
244
240
|
class AircraftClass < ActiveRecord::Base
|
245
|
-
has_many :aircraft
|
241
|
+
has_many :aircraft
|
246
242
|
has_many :airline_aircraft_seat_classes, :through => :aircraft
|
247
243
|
end
|
248
244
|
|
249
245
|
class AircraftDeux < ActiveRecord::Base
|
250
246
|
set_primary_key 'icao_code'
|
251
|
-
has_many :segments, :
|
247
|
+
has_many :segments, :primary_key => 'my_bts_aircraft_type_code', :foreign_key => 'bts_aircraft_type'
|
252
248
|
end
|
@@ -95,23 +95,23 @@ class TestWeightedAverage < Test::Unit::TestCase
|
|
95
95
|
# fake! we would never calc seats this way
|
96
96
|
should "do foreign default weighting" do
|
97
97
|
should_have_same_sql(
|
98
|
-
"SELECT (SUM((`aircraft`.`seats`) * `airline_aircraft_seat_classes`.`weighting`) / SUM(`airline_aircraft_seat_classes`.`weighting`)) AS weighted_average
|
98
|
+
"SELECT (SUM((`aircraft`.`seats`) * `airline_aircraft_seat_classes`.`weighting`) / SUM(`airline_aircraft_seat_classes`.`weighting`)) AS weighted_average FROM `aircraft` INNER JOIN `airline_aircraft_seat_classes` ON `airline_aircraft_seat_classes`.`aircraft_id` = `aircraft`.`id` WHERE (`aircraft`.`seats` IS NOT NULL)",
|
99
99
|
Aircraft.weighted_average_relation('seats', :weighted_by => :airline_aircraft_seat_classes)
|
100
100
|
)
|
101
101
|
end
|
102
|
-
|
102
|
+
|
103
103
|
# Aircraft#m3 fallback value
|
104
104
|
# a subquery used in Aircraft.update_all_m3s
|
105
105
|
should "do foreign custom weighting" do
|
106
106
|
should_have_same_sql(
|
107
|
-
"SELECT (SUM((`aircraft`.`m3`) * `segments`.`passengers`) / SUM(`segments`.`passengers`)) AS weighted_average
|
107
|
+
"SELECT (SUM((`aircraft`.`m3`) * `segments`.`passengers`) / SUM(`segments`.`passengers`)) AS weighted_average FROM `aircraft` INNER JOIN `segments` ON `segments`.`bts_aircraft_type` = `aircraft`.`bts_aircraft_type` WHERE (`aircraft`.`m3` IS NOT NULL)",
|
108
108
|
Aircraft.weighted_average_relation(:m3, :weighted_by => [:segments, :passengers])
|
109
109
|
)
|
110
110
|
end
|
111
111
|
|
112
112
|
should "do foreign custom weighting with custom join keys" do
|
113
113
|
should_have_same_sql(
|
114
|
-
"SELECT (SUM((`aircraft_deux`.`m3`) * `segments`.`passengers`) / SUM(`segments`.`passengers`)) AS weighted_average
|
114
|
+
"SELECT (SUM((`aircraft_deux`.`m3`) * `segments`.`passengers`) / SUM(`segments`.`passengers`)) AS weighted_average FROM `aircraft_deux` INNER JOIN `segments` ON `segments`.`bts_aircraft_type` = `aircraft_deux`.`my_bts_aircraft_type_code` WHERE (`aircraft_deux`.`m3` IS NOT NULL)",
|
115
115
|
AircraftDeux.weighted_average_relation(:m3, :weighted_by => [:segments, :passengers])
|
116
116
|
)
|
117
117
|
end
|
@@ -139,7 +139,7 @@ class TestWeightedAverage < Test::Unit::TestCase
|
|
139
139
|
should "do foreign default weighting, scoped" do
|
140
140
|
conditions = '454 != 999'
|
141
141
|
should_have_same_sql(
|
142
|
-
"SELECT (SUM((`aircraft`.`seats`) * `airline_aircraft_seat_classes`.`weighting`) / SUM(`airline_aircraft_seat_classes`.`weighting`)) AS weighted_average
|
142
|
+
"SELECT (SUM((`aircraft`.`seats`) * `airline_aircraft_seat_classes`.`weighting`) / SUM(`airline_aircraft_seat_classes`.`weighting`)) AS weighted_average FROM `aircraft` INNER JOIN `airline_aircraft_seat_classes` ON `airline_aircraft_seat_classes`.`aircraft_id` = `aircraft`.`id` WHERE (454 != 999) AND (`aircraft`.`seats` IS NOT NULL)",
|
143
143
|
Aircraft.scoped(:conditions => conditions).weighted_average_relation(:seats, :weighted_by => :airline_aircraft_seat_classes)
|
144
144
|
)
|
145
145
|
end
|
@@ -147,7 +147,7 @@ class TestWeightedAverage < Test::Unit::TestCase
|
|
147
147
|
should "do foreign custom weighting, scoped" do
|
148
148
|
conditions = '`aircraft`.`m3` > 1'
|
149
149
|
should_have_same_sql(
|
150
|
-
"SELECT (SUM((`aircraft`.`m3`) * `segments`.`passengers`) / SUM(`segments`.`passengers`)) AS weighted_average
|
150
|
+
"SELECT (SUM((`aircraft`.`m3`) * `segments`.`passengers`) / SUM(`segments`.`passengers`)) AS weighted_average FROM `aircraft` INNER JOIN `segments` ON `segments`.`bts_aircraft_type` = `aircraft`.`bts_aircraft_type` WHERE (`aircraft`.`m3` > 1) AND (`aircraft`.`m3` IS NOT NULL)",
|
151
151
|
Aircraft.scoped(:conditions => conditions).weighted_average_relation(:m3, :weighted_by => [:segments, :passengers])
|
152
152
|
)
|
153
153
|
end
|
@@ -160,6 +160,20 @@ class TestWeightedAverage < Test::Unit::TestCase
|
|
160
160
|
Segment.weighted_average_relation(:load_factor, :weighted_by => :passengers, :disaggregate_by => :departures_performed)
|
161
161
|
)
|
162
162
|
end
|
163
|
+
|
164
|
+
# more complicated stuff
|
165
|
+
|
166
|
+
should "construct weightings across has_many through associations (that can be used for updating all)" do
|
167
|
+
aircraft_class = AircraftClass.arel_table
|
168
|
+
aircraft = Aircraft.arel_table
|
169
|
+
segment = Segment.arel_table
|
170
|
+
|
171
|
+
should_have_same_sql(
|
172
|
+
"SELECT (SUM((`segments`.`seats`) * `segments`.`passengers`) / SUM(`segments`.`passengers`)) AS weighted_average FROM `segments` INNER JOIN `aircraft` ON `aircraft`.`bts_aircraft_type` = `segments`.`bts_aircraft_type` INNER JOIN `aircraft_classes` ON `aircraft_classes`.`id` = `aircraft`.`aircraft_class_id` WHERE (`segments`.`seats` IS NOT NULL) AND (`aircraft`.`aircraft_class_id` = `aircraft_classes`.`id`)",
|
173
|
+
Segment.joins(:aircraft => :aircraft_class).weighted_average_relation(:seats, :weighted_by => :passengers).where(aircraft[:aircraft_class_id].eq(aircraft_class[:id]))
|
174
|
+
)
|
175
|
+
end
|
176
|
+
|
163
177
|
|
164
178
|
# cohorts (requires the cohort_scope gem)
|
165
179
|
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
8
|
+
- 4
|
9
|
+
version: 0.0.4
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Seamus Abshere
|
@@ -17,7 +17,7 @@ autorequire:
|
|
17
17
|
bindir: bin
|
18
18
|
cert_chain: []
|
19
19
|
|
20
|
-
date: 2010-05-
|
20
|
+
date: 2010-05-18 00:00:00 -04:00
|
21
21
|
default_executable:
|
22
22
|
dependencies:
|
23
23
|
- !ruby/object:Gem::Dependency
|