weighted_average 1.1.0 → 2.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.
- data/.gitignore +3 -0
- data/.yardopts +2 -0
- data/CHANGELOG +9 -0
- data/{README.rdoc → README.markdown} +7 -7
- data/Rakefile +5 -0
- data/lib/weighted_average/active_record_base_class_methods.rb +17 -0
- data/lib/weighted_average/active_record_relation_instance_methods.rb +62 -0
- data/lib/weighted_average/arel_select_manager_instance_methods.rb +72 -0
- data/lib/weighted_average/arel_table_instance_methods.rb +17 -0
- data/lib/weighted_average/version.rb +1 -1
- data/lib/weighted_average.rb +14 -81
- data/test/helper.rb +3 -0
- data/test/test_weighted_average.rb +40 -30
- data/weighted_average.gemspec +2 -1
- metadata +40 -19
data/.gitignore
CHANGED
data/.yardopts
ADDED
data/CHANGELOG
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
|
1
|
+
# weighted_average
|
2
2
|
|
3
|
-
Do weighted averages in
|
3
|
+
Do weighted averages in ARel.
|
4
4
|
|
5
|
-
|
5
|
+
## Rationale
|
6
6
|
|
7
7
|
You have a bunch of flight records with passenger count and distance.
|
8
8
|
|
@@ -13,9 +13,9 @@ The average distance is <tt>(10_000 + 500) / 2 = 5250</tt>.
|
|
13
13
|
|
14
14
|
The average distance weighted by passenger count is <tt>(30_000 * 500 + 15 * 10_000) / (10_500) = 1442</tt>.
|
15
15
|
|
16
|
-
|
16
|
+
## Usage
|
17
17
|
|
18
|
-
Using <tt>FlightSegment</tt> from
|
18
|
+
Using <tt>FlightSegment</tt> from [Brighter Planet's earth library](http://rubygems.org/gems/earth):
|
19
19
|
|
20
20
|
>> FlightSegment.weighted_average(:distance, :weighted_by => :passengers)
|
21
21
|
=> 2436.1959
|
@@ -25,6 +25,6 @@ You can also see the SQL that is generated:
|
|
25
25
|
>> FlightSegment.weighted_average_relation(:distance, :weighted_by => :passengers).to_sql
|
26
26
|
=> "SELECT (SUM((`flight_segments`.`distance`) * `flight_segments`.`passengers`) / SUM(`flight_segments`.`passengers`)) AS weighted_average FROM `flight_segments` WHERE (`flight_segments`.`distance` IS NOT NULL)"
|
27
27
|
|
28
|
-
|
28
|
+
## Copyright
|
29
29
|
|
30
|
-
Copyright (c)
|
30
|
+
Copyright (c) 2012 Brighter Planet, Inc.
|
data/Rakefile
CHANGED
@@ -0,0 +1,17 @@
|
|
1
|
+
module WeightedAverage
|
2
|
+
module ActiveRecordBaseClassMethods
|
3
|
+
# @see WeightedAverage::ActiveRecordRelationInstanceMethods#weighted_average
|
4
|
+
#
|
5
|
+
# @return [Float,nil]
|
6
|
+
def weighted_average(*args)
|
7
|
+
scoped.weighted_average(*args)
|
8
|
+
end
|
9
|
+
|
10
|
+
# @see WeightedAverage::ActiveRecordRelationInstanceMethods#weighted_average_relation
|
11
|
+
#
|
12
|
+
# @return [Arel::SelectManager]
|
13
|
+
def weighted_average_relation(*args)
|
14
|
+
scoped.weighted_average_relation(*args)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module WeightedAverage
|
2
|
+
module ActiveRecordRelationInstanceMethods
|
3
|
+
# Get the weighted average of column(s).
|
4
|
+
#
|
5
|
+
# In addition to the options available on WeightedAverage::ArelSelectManagerInstanceMethods#weighted_average, this ActiveRecord-specific method understands associations.
|
6
|
+
#
|
7
|
+
# @param [Symbol,Array<Symbol>] data_column_names One or more column names whose average should be calculated. Added together before being multiplied by the weighting if more than one.
|
8
|
+
# @param [Hash] options
|
9
|
+
#
|
10
|
+
# @option options [Symbol] :weighted_by The name of an association to weight against OR a column name just like in the pure ARel version.
|
11
|
+
# @option options [Array{Symbol,Symbol}] :weighted_by The name of an association and a weighting column inside that association table to weight against. Not available in the pure ARel version.
|
12
|
+
# @option options [Symbol] :disaggregate_by Same as its meaning in the pure ARel version.
|
13
|
+
#
|
14
|
+
# @example Get the average m3 of all aircraft, weighted by a column named :weighting in flight segments table. But wait... there is no column called :weighting! So see the next example.
|
15
|
+
# Aircraft.weighted_average(:m3, :weighted_by => :segments)
|
16
|
+
#
|
17
|
+
# @example Get the average m3 of all aircraft, weighted by how many :passengers flew in a particular aircraft.
|
18
|
+
# Aircraft.weighted_average(:m3, :weighted_by => [:segments, :passengers])
|
19
|
+
#
|
20
|
+
# @see WeightedAverage::ArelSelectManagerInstanceMethods#weighted_average The pure ARel version of this method, which doesn't know about associations
|
21
|
+
#
|
22
|
+
# @return [Float,nil]
|
23
|
+
def weighted_average(data_column_names, options = {})
|
24
|
+
weighted_average = connection.select_value weighted_average_relation(data_column_names, options).to_sql
|
25
|
+
weighted_average.nil? ? nil : weighted_average.to_f
|
26
|
+
end
|
27
|
+
|
28
|
+
# Same as WeightedAverage::ArelSelectManagerInstanceMethods#weighted_average, except it can interpret associations.
|
29
|
+
#
|
30
|
+
# @see WeightedAverage::ArelSelectManagerInstanceMethods#weighted_average_relation The pure ARel version of this method.
|
31
|
+
#
|
32
|
+
# @return [Arel::SelectManager]
|
33
|
+
def weighted_average_relation(data_column_names, options = {})
|
34
|
+
if weighted_by_option = options[:weighted_by]
|
35
|
+
case weighted_by_option
|
36
|
+
when Array
|
37
|
+
# :weighted_by specifies a custom column on an association table (least common)
|
38
|
+
unless association = reflect_on_association(weighted_by_option.first)
|
39
|
+
raise ArgumentError, "#{name} does not have association #{weighted_by_option.first.inspect}"
|
40
|
+
end
|
41
|
+
weighted_by_column = association.klass.arel_table[weighted_by_option.last]
|
42
|
+
when Symbol, String
|
43
|
+
if association = reflect_on_association(weighted_by_option)
|
44
|
+
# :weighted_by specifies an association table with a column named "weighting"
|
45
|
+
weighted_by_column = association.klass.arel_table[DEFAULT_WEIGHTED_BY_COLUMN_NAME]
|
46
|
+
else
|
47
|
+
# :weighted_by specifies a custom column on the same table
|
48
|
+
weighted_by_column = arel_table[weighted_by_option]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
if association
|
52
|
+
joins(association.name).arel.weighted_average_relation data_column_names, options.merge(:weighted_by => weighted_by_column)
|
53
|
+
else
|
54
|
+
arel.weighted_average_relation data_column_names, options.merge(:weighted_by => weighted_by_column)
|
55
|
+
end
|
56
|
+
else
|
57
|
+
arel.weighted_average_relation data_column_names, options
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module WeightedAverage
|
2
|
+
module ArelSelectManagerInstanceMethods
|
3
|
+
# Calculate the weighted average of column(s).
|
4
|
+
#
|
5
|
+
# @param [Symbol,Array<Symbol>] data_column_names One or more column names whose average should be calculated. Added together before being multiplied by the weighting if more than one.
|
6
|
+
# @param [Hash] options
|
7
|
+
#
|
8
|
+
# @option options [Symbol] :weighted_by The name of the weighting column if it's not :weighting (the default)
|
9
|
+
# @option options [Symbol] :disaggregate_by The name of a column to disaggregate by. Usually not necessary.
|
10
|
+
#
|
11
|
+
# @see WeightedAverage::ActiveRecordRelationInstanceMethods The ActiveRecord-specific version of this method, which knows about associations.
|
12
|
+
#
|
13
|
+
# @example Weighted average of load factor in flight stage data
|
14
|
+
# Arel::Table.new(:flight_segments).weighted_average(:load_factor, :weighted_by => :passengers)
|
15
|
+
#
|
16
|
+
# @return [Float,nil]
|
17
|
+
def weighted_average(data_column_names, options = {})
|
18
|
+
weighted_average = @engine.connection.select_value(weighted_average_relation(data_column_names, options).to_sql)
|
19
|
+
weighted_average.nil? ? nil : weighted_average.to_f
|
20
|
+
end
|
21
|
+
|
22
|
+
# In case you want to get the relation and/or the SQL of the calculation query before actually runnnig it.
|
23
|
+
#
|
24
|
+
# @example Get the SQL
|
25
|
+
# Arel::Table.new(:flight_segments).weighted_average_relation(:load_factor, :weighted_by => :passengers).to_sql
|
26
|
+
#
|
27
|
+
# @return [Arel::SelectManager] A relation you can play around with.
|
28
|
+
def weighted_average_relation(data_column_names, options = {})
|
29
|
+
left = self.source.left
|
30
|
+
|
31
|
+
weighted_by_column = case options[:weighted_by]
|
32
|
+
when Arel::Attribute
|
33
|
+
options[:weighted_by]
|
34
|
+
when Symbol, String
|
35
|
+
left[options[:weighted_by]]
|
36
|
+
when NilClass
|
37
|
+
left[DEFAULT_WEIGHTED_BY_COLUMN_NAME]
|
38
|
+
else
|
39
|
+
raise ArgumentError, ":weighted_by => #{options[:weighted_by].inspect} must be a column on #{left.inspect}"
|
40
|
+
end
|
41
|
+
|
42
|
+
disaggregate_by_column = if options[:disaggregate_by]
|
43
|
+
left[options[:disaggregate_by]]
|
44
|
+
end
|
45
|
+
|
46
|
+
data_columns = ::Array.wrap(data_column_names).map do |data_column_name|
|
47
|
+
left[data_column_name]
|
48
|
+
end
|
49
|
+
|
50
|
+
if disaggregate_by_column
|
51
|
+
self.projections = [Arel::Nodes::Division.new(Arel::Nodes::Sum.new(weighted_by_column * (data_columns.inject(:+)) / disaggregate_by_column * 1.0), Arel::Nodes::Sum.new([weighted_by_column]))]
|
52
|
+
else
|
53
|
+
self.projections = [Arel::Nodes::Division.new(Arel::Nodes::Sum.new(weighted_by_column * (data_columns.inject(:+)) * 1.0), Arel::Nodes::Sum.new([weighted_by_column]))]
|
54
|
+
end
|
55
|
+
|
56
|
+
data_columns_not_eq_nil = data_columns.inject(nil) do |memo, data_column|
|
57
|
+
if memo
|
58
|
+
memo.and(data_column.not_eq(nil))
|
59
|
+
else
|
60
|
+
data_column.not_eq(nil)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
if disaggregate_by_column
|
65
|
+
where data_columns_not_eq_nil.and(weighted_by_column.gt(0)).and(disaggregate_by_column.gt(0))
|
66
|
+
else
|
67
|
+
where data_columns_not_eq_nil.and(weighted_by_column.gt(0))
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module WeightedAverage
|
2
|
+
module ArelTableInstanceMethods
|
3
|
+
# @see WeightedAverage::ArelSelectManagerInstanceMethods#weighted_average
|
4
|
+
#
|
5
|
+
# @return [Float,nil]
|
6
|
+
def weighted_average(*args)
|
7
|
+
from(self).weighted_average(*args)
|
8
|
+
end
|
9
|
+
|
10
|
+
# @see WeightedAverage::ArelSelectManagerInstanceMethods#weighted_average_relation
|
11
|
+
#
|
12
|
+
# @return [Arel::SelectManager]
|
13
|
+
def weighted_average_relation(*args)
|
14
|
+
from(self).weighted_average_relation(*args)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/weighted_average.rb
CHANGED
@@ -1,89 +1,22 @@
|
|
1
|
+
require 'arel'
|
1
2
|
require 'active_support/core_ext'
|
2
|
-
require 'active_record'
|
3
|
-
require "weighted_average/version"
|
4
3
|
|
5
4
|
module WeightedAverage
|
6
|
-
|
7
|
-
|
8
|
-
weighted_average = connection.select_value(weighted_average_relation(*args).to_sql, 'weighted_average')
|
9
|
-
weighted_average.nil? ? nil : weighted_average.to_f
|
10
|
-
end
|
11
|
-
|
12
|
-
# Returns the ARel relation for a weighted average query.
|
13
|
-
def weighted_average_relation(data_column_names, options = {})
|
14
|
-
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
|
15
|
-
raise ::ArgumentError, "No nil values in weighted_by, please" if ::Array.wrap(options[:weighted_by]).any?(&:nil?)
|
16
|
-
|
17
|
-
# :airline_aircraft_seat_class
|
18
|
-
association = if options[:weighted_by].present?
|
19
|
-
options[:weighted_by].is_a?(::Array) ? reflect_on_association(options[:weighted_by].first.to_sym) : reflect_on_association(options[:weighted_by].to_sym)
|
20
|
-
end
|
21
|
-
|
22
|
-
# AirlineAircraftSeatClass
|
23
|
-
association_class = association.klass if association
|
24
|
-
|
25
|
-
# `aircraft`
|
26
|
-
table_name = connection.quote_table_name table.name
|
27
|
-
|
28
|
-
# `airline_aircraft_seat_classes`
|
29
|
-
weighted_by_table_name = if association_class
|
30
|
-
association_class.quoted_table_name
|
31
|
-
else
|
32
|
-
table_name
|
33
|
-
end
|
34
|
-
|
35
|
-
# `airline_aircraft_seat_classes`.`weighting`
|
36
|
-
weighted_by_column_name = 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
|
-
weighted_by_column_name = [ weighted_by_table_name, connection.quote_column_name(weighted_by_column_name) ].join '.'
|
44
|
-
|
45
|
-
# `aircraft`.`passengers`
|
46
|
-
disaggregate_by_column_name = if options[:disaggregate_by]
|
47
|
-
[ table_name, connection.quote_column_name(options[:disaggregate_by]) ].join '.'
|
48
|
-
end
|
5
|
+
DEFAULT_WEIGHTED_BY_COLUMN_NAME = :weighting
|
6
|
+
end
|
49
7
|
|
50
|
-
|
51
|
-
|
52
|
-
[ table_name, connection.quote_column_name(data_column_name) ].join '.'
|
53
|
-
end
|
8
|
+
require 'weighted_average/arel_select_manager_instance_methods'
|
9
|
+
Arel::SelectManager.send :include, WeightedAverage::ArelSelectManagerInstanceMethods
|
54
10
|
|
55
|
-
|
56
|
-
|
57
|
-
relation = relation.where("#{data_column_name} IS NOT NULL")
|
58
|
-
end
|
11
|
+
require 'weighted_average/arel_table_instance_methods'
|
12
|
+
Arel::Table.send :include, WeightedAverage::ArelTableInstanceMethods
|
59
13
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
relation = relation.joins(association.name) if association_class
|
66
|
-
relation
|
67
|
-
end
|
68
|
-
end
|
14
|
+
if defined?(ActiveRecord)
|
15
|
+
require 'weighted_average/active_record_base_class_methods'
|
16
|
+
ActiveRecord::Base.extend WeightedAverage::ActiveRecordBaseClassMethods
|
17
|
+
proxy_class = defined?(ActiveRecord::Associations::CollectionProxy) ? ActiveRecord::Associations::CollectionProxy : ActiveRecord::Associations::AssociationCollection
|
18
|
+
proxy_class.extend WeightedAverage::ActiveRecordBaseClassMethods
|
69
19
|
|
70
|
-
|
71
|
-
|
72
|
-
scoped.weighted_average(*args)
|
73
|
-
end
|
74
|
-
|
75
|
-
def self.weighted_average_relation(*args) # :nodoc:
|
76
|
-
scoped.weighted_average_relation(*args)
|
77
|
-
end
|
78
|
-
end
|
79
|
-
::ActiveRecord::Base.class_eval do
|
80
|
-
def self.weighted_average(*args) # :nodoc:
|
81
|
-
scoped.weighted_average(*args)
|
82
|
-
end
|
83
|
-
|
84
|
-
def self.weighted_average_relation(*args) # :nodoc:
|
85
|
-
scoped.weighted_average_relation(*args)
|
86
|
-
end
|
20
|
+
require 'weighted_average/active_record_relation_instance_methods'
|
21
|
+
ActiveRecord::Relation.send :include, WeightedAverage::ActiveRecordRelationInstanceMethods
|
87
22
|
end
|
88
|
-
|
89
|
-
::ActiveRecord::Relation.send :include, ::WeightedAverage
|
data/test/helper.rb
CHANGED
@@ -48,14 +48,14 @@ describe WeightedAverage do
|
|
48
48
|
|
49
49
|
it "does default weighting" do
|
50
50
|
should_have_same_sql(
|
51
|
-
"SELECT
|
51
|
+
"SELECT SUM(airline_aircraft_seat_classes.weighting * airline_aircraft_seat_classes.seats * 1.0) / SUM(airline_aircraft_seat_classes.weighting) FROM airline_aircraft_seat_classes WHERE airline_aircraft_seat_classes.seats IS NOT NULL AND airline_aircraft_seat_classes.weighting > 0",
|
52
52
|
AirlineAircraftSeatClass.weighted_average_relation('seats')
|
53
53
|
)
|
54
54
|
end
|
55
55
|
|
56
56
|
it "does custom weighting" do
|
57
57
|
should_have_same_sql(
|
58
|
-
"SELECT
|
58
|
+
"SELECT SUM(segments.passengers * segments.distance * 1.0) / SUM(segments.passengers) FROM segments WHERE segments.distance IS NOT NULL AND segments.passengers > 0",
|
59
59
|
Segment.weighted_average_relation('distance', :weighted_by => 'passengers')
|
60
60
|
)
|
61
61
|
end
|
@@ -71,7 +71,7 @@ describe WeightedAverage do
|
|
71
71
|
|
72
72
|
it "adds multiple columns before averaging" do
|
73
73
|
should_have_same_sql(
|
74
|
-
"SELECT
|
74
|
+
"SELECT SUM(airline_aircraft_seat_classes.weighting * (airline_aircraft_seat_classes.seats + airline_aircraft_seat_classes.pitch) * 1.0) / SUM(airline_aircraft_seat_classes.weighting) FROM airline_aircraft_seat_classes WHERE airline_aircraft_seat_classes.seats IS NOT NULL AND airline_aircraft_seat_classes.pitch IS NOT NULL AND airline_aircraft_seat_classes.weighting > 0",
|
75
75
|
AirlineAircraftSeatClass.weighted_average_relation(['seats', 'pitch'])
|
76
76
|
)
|
77
77
|
end
|
@@ -80,10 +80,10 @@ describe WeightedAverage do
|
|
80
80
|
|
81
81
|
# a subquery used in Aircraft.update_all_seats
|
82
82
|
it "does default weighting with conditions" do
|
83
|
-
conditions =
|
83
|
+
conditions = "aircraft_id = #{rand(1e11)}"
|
84
84
|
|
85
85
|
should_have_same_sql(
|
86
|
-
"SELECT
|
86
|
+
"SELECT SUM(airline_aircraft_seat_classes.weighting * airline_aircraft_seat_classes.seats * 1.0) / SUM(airline_aircraft_seat_classes.weighting) FROM airline_aircraft_seat_classes WHERE (#{conditions}) AND airline_aircraft_seat_classes.seats IS NOT NULL AND airline_aircraft_seat_classes.weighting > 0",
|
87
87
|
AirlineAircraftSeatClass.where(conditions).weighted_average_relation('seats')
|
88
88
|
)
|
89
89
|
end
|
@@ -92,7 +92,7 @@ describe WeightedAverage do
|
|
92
92
|
it "does custom weighting with conditions" do
|
93
93
|
conditions = '456 = 456'
|
94
94
|
should_have_same_sql(
|
95
|
-
"SELECT
|
95
|
+
"SELECT SUM(segments.passengers * segments.load_factor * 1.0) / SUM(segments.passengers) FROM segments WHERE (456 = 456) AND segments.load_factor IS NOT NULL AND segments.passengers > 0",
|
96
96
|
Segment.where(conditions).weighted_average_relation('load_factor', :weighted_by => 'passengers')
|
97
97
|
)
|
98
98
|
end
|
@@ -102,7 +102,7 @@ describe WeightedAverage do
|
|
102
102
|
# fake! we would never calc seats this way
|
103
103
|
it "does foreign default weighting" do
|
104
104
|
should_have_same_sql(
|
105
|
-
"SELECT
|
105
|
+
"SELECT SUM(airline_aircraft_seat_classes.weighting * aircraft.seats * 1.0) / SUM(airline_aircraft_seat_classes.weighting) FROM aircraft INNER JOIN airline_aircraft_seat_classes ON airline_aircraft_seat_classes.aircraft_id = aircraft.id WHERE aircraft.seats IS NOT NULL AND airline_aircraft_seat_classes.weighting > 0",
|
106
106
|
Aircraft.weighted_average_relation('seats', :weighted_by => :airline_aircraft_seat_classes)
|
107
107
|
)
|
108
108
|
end
|
@@ -111,14 +111,14 @@ describe WeightedAverage do
|
|
111
111
|
# a subquery used in Aircraft.update_all_m3s
|
112
112
|
it "does foreign custom weighting" do
|
113
113
|
should_have_same_sql(
|
114
|
-
"SELECT
|
114
|
+
"SELECT SUM(segments.passengers * aircraft.m3 * 1.0) / SUM(segments.passengers) FROM aircraft INNER JOIN segments ON segments.bts_aircraft_type = aircraft.bts_aircraft_type WHERE aircraft.m3 IS NOT NULL AND segments.passengers > 0",
|
115
115
|
Aircraft.weighted_average_relation(:m3, :weighted_by => [:segments, :passengers])
|
116
116
|
)
|
117
117
|
end
|
118
118
|
|
119
119
|
it "does foreign custom weighting with custom join keys" do
|
120
120
|
should_have_same_sql(
|
121
|
-
"SELECT
|
121
|
+
"SELECT SUM(segments.passengers * aircraft_deux.m3 * 1.0) / SUM(segments.passengers) 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 AND segments.passengers > 0",
|
122
122
|
AircraftDeux.weighted_average_relation(:m3, :weighted_by => [:segments, :passengers])
|
123
123
|
)
|
124
124
|
end
|
@@ -128,7 +128,7 @@ describe WeightedAverage do
|
|
128
128
|
it "does default weighting, scoped" do
|
129
129
|
conditions = '456 = 456'
|
130
130
|
should_have_same_sql(
|
131
|
-
"SELECT
|
131
|
+
"SELECT SUM(airline_aircraft_seat_classes.weighting * airline_aircraft_seat_classes.seats * 1.0) / SUM(airline_aircraft_seat_classes.weighting) FROM airline_aircraft_seat_classes WHERE (#{conditions}) AND airline_aircraft_seat_classes.seats IS NOT NULL AND airline_aircraft_seat_classes.weighting > 0",
|
132
132
|
AirlineAircraftSeatClass.scoped(:conditions => conditions).weighted_average_relation(:seats)
|
133
133
|
)
|
134
134
|
end
|
@@ -136,17 +136,17 @@ describe WeightedAverage do
|
|
136
136
|
it "does custom weighting, scoped" do
|
137
137
|
conditions = '999 = 999'
|
138
138
|
should_have_same_sql(
|
139
|
-
"SELECT
|
139
|
+
"SELECT SUM(segments.passengers * segments.load_factor * 1.0) / SUM(segments.passengers) FROM segments WHERE (#{conditions}) AND segments.load_factor IS NOT NULL AND segments.passengers > 0",
|
140
140
|
Segment.scoped(:conditions => conditions).weighted_average_relation(:load_factor, :weighted_by => :passengers)
|
141
141
|
)
|
142
142
|
end
|
143
143
|
|
144
|
-
# scoped foreign weightings
|
144
|
+
# # scoped foreign weightings
|
145
145
|
|
146
146
|
it "does foreign default weighting, scoped" do
|
147
147
|
conditions = '454 != 999'
|
148
148
|
should_have_same_sql(
|
149
|
-
"SELECT
|
149
|
+
"SELECT SUM(airline_aircraft_seat_classes.weighting * aircraft.seats * 1.0) / SUM(airline_aircraft_seat_classes.weighting) FROM aircraft INNER JOIN airline_aircraft_seat_classes ON airline_aircraft_seat_classes.aircraft_id = aircraft.id WHERE (#{conditions}) AND aircraft.seats IS NOT NULL AND airline_aircraft_seat_classes.weighting > 0",
|
150
150
|
Aircraft.scoped(:conditions => conditions).weighted_average_relation(:seats, :weighted_by => :airline_aircraft_seat_classes)
|
151
151
|
)
|
152
152
|
end
|
@@ -154,21 +154,21 @@ describe WeightedAverage do
|
|
154
154
|
it "does foreign custom weighting, scoped" do
|
155
155
|
conditions = 'aircraft.m3 > 1'
|
156
156
|
should_have_same_sql(
|
157
|
-
"SELECT
|
157
|
+
"SELECT SUM(segments.passengers * aircraft.m3 * 1.0) / SUM(segments.passengers) FROM aircraft INNER JOIN segments ON segments.bts_aircraft_type = aircraft.bts_aircraft_type WHERE (#{conditions}) AND aircraft.m3 IS NOT NULL AND segments.passengers > 0",
|
158
158
|
Aircraft.scoped(:conditions => conditions).weighted_average_relation(:m3, :weighted_by => [:segments, :passengers])
|
159
159
|
)
|
160
160
|
end
|
161
161
|
|
162
|
-
# disaggregation
|
162
|
+
# # disaggregation
|
163
163
|
|
164
164
|
it "does custom weighting with disaggregation" do
|
165
165
|
should_have_same_sql(
|
166
|
-
"SELECT
|
166
|
+
"SELECT SUM(segments.passengers * segments.load_factor / segments.departures_performed * 1.0) / SUM(segments.passengers) FROM segments WHERE segments.load_factor IS NOT NULL AND segments.passengers > 0 AND segments.departures_performed > 0",
|
167
167
|
Segment.weighted_average_relation(:load_factor, :weighted_by => :passengers, :disaggregate_by => :departures_performed)
|
168
168
|
)
|
169
169
|
end
|
170
170
|
|
171
|
-
# more complicated stuff
|
171
|
+
# # more complicated stuff
|
172
172
|
|
173
173
|
it "construct weightings across has_many through associations (that can be used for updating all)" do
|
174
174
|
aircraft_class = AircraftClass.arel_table
|
@@ -176,31 +176,41 @@ describe WeightedAverage do
|
|
176
176
|
segment = Segment.arel_table
|
177
177
|
|
178
178
|
should_have_same_sql(
|
179
|
-
"SELECT
|
179
|
+
"SELECT SUM(segments.passengers * segments.seats * 1.0) / SUM(segments.passengers) 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 segments.passengers > 0 AND aircraft.aircraft_class_id = aircraft_classes.id",
|
180
180
|
Segment.joins(:aircraft => :aircraft_class).weighted_average_relation(:seats, :weighted_by => :passengers).where(aircraft[:aircraft_class_id].eq(aircraft_class[:id]))
|
181
181
|
)
|
182
182
|
end
|
183
183
|
|
184
184
|
|
185
|
-
# cohorts (requires the
|
185
|
+
# # cohorts (requires the cohort_analysis gem)
|
186
186
|
|
187
187
|
it "does custom weighting, with a cohort" do
|
188
188
|
should_have_same_sql(
|
189
|
-
"SELECT
|
189
|
+
"SELECT SUM(segments.passengers * segments.load_factor * 1.0) / SUM(segments.passengers) FROM segments WHERE (segments.payload = 5) AND segments.load_factor IS NOT NULL AND segments.passengers > 0",
|
190
190
|
Segment.cohort(:payload => 5).weighted_average_relation(:load_factor, :weighted_by => :passengers)
|
191
191
|
)
|
192
192
|
end
|
193
193
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
194
|
+
describe "on Arel::Table" do
|
195
|
+
it "works on plain tables" do
|
196
|
+
c = Segment.connection
|
197
|
+
table_name = "plain_arel_table_#{rand(1e11).to_s}"
|
198
|
+
c.execute %{
|
199
|
+
CREATE TEMPORARY TABLE #{table_name} AS SELECT * FROM #{Segment.quoted_table_name} LIMIT 1
|
200
|
+
}
|
201
|
+
table = Arel::Table.new(table_name)
|
202
|
+
should_have_same_sql(
|
203
|
+
"SELECT SUM(#{table_name}.passengers * #{table_name}.distance * 1.0) / SUM(#{table_name}.passengers) FROM #{table_name} WHERE #{table_name}.distance IS NOT NULL AND #{table_name}.passengers > 0",
|
204
|
+
table.weighted_average_relation('distance', :weighted_by => 'passengers')
|
205
|
+
)
|
206
|
+
end
|
207
|
+
|
208
|
+
it "takes complex args" do
|
209
|
+
should_have_same_sql(
|
210
|
+
"SELECT SUM(airline_aircraft_seat_classes.weighting * (airline_aircraft_seat_classes.seats + airline_aircraft_seat_classes.pitch) * 1.0) / SUM(airline_aircraft_seat_classes.weighting) FROM airline_aircraft_seat_classes WHERE airline_aircraft_seat_classes.seats IS NOT NULL AND airline_aircraft_seat_classes.pitch IS NOT NULL AND airline_aircraft_seat_classes.weighting > 0",
|
211
|
+
AirlineAircraftSeatClass.arel_table.weighted_average_relation(['seats', 'pitch'])
|
212
|
+
)
|
213
|
+
end
|
204
214
|
end
|
205
215
|
|
206
216
|
private
|
data/weighted_average.gemspec
CHANGED
@@ -17,7 +17,6 @@ Gem::Specification.new do |s|
|
|
17
17
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
18
|
s.require_paths = ["lib"]
|
19
19
|
|
20
|
-
s.add_runtime_dependency 'activerecord', '~>3'
|
21
20
|
s.add_runtime_dependency 'activesupport', '~>3'
|
22
21
|
s.add_runtime_dependency 'arel', '>= 2'
|
23
22
|
|
@@ -26,6 +25,8 @@ Gem::Specification.new do |s|
|
|
26
25
|
s.add_development_dependency 'rake'
|
27
26
|
s.add_development_dependency 'mysql'
|
28
27
|
s.add_development_dependency 'pg'
|
28
|
+
s.add_development_dependency 'activerecord', '~>3'
|
29
|
+
s.add_development_dependency 'yard'
|
29
30
|
end
|
30
31
|
|
31
32
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: weighted_average
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -12,24 +12,8 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: bin
|
14
14
|
cert_chain: []
|
15
|
-
date: 2012-05-
|
15
|
+
date: 2012-05-29 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
|
-
- !ruby/object:Gem::Dependency
|
18
|
-
name: activerecord
|
19
|
-
requirement: !ruby/object:Gem::Requirement
|
20
|
-
none: false
|
21
|
-
requirements:
|
22
|
-
- - ~>
|
23
|
-
- !ruby/object:Gem::Version
|
24
|
-
version: '3'
|
25
|
-
type: :runtime
|
26
|
-
prerelease: false
|
27
|
-
version_requirements: !ruby/object:Gem::Requirement
|
28
|
-
none: false
|
29
|
-
requirements:
|
30
|
-
- - ~>
|
31
|
-
- !ruby/object:Gem::Version
|
32
|
-
version: '3'
|
33
17
|
- !ruby/object:Gem::Dependency
|
34
18
|
name: activesupport
|
35
19
|
requirement: !ruby/object:Gem::Requirement
|
@@ -142,6 +126,38 @@ dependencies:
|
|
142
126
|
- - ! '>='
|
143
127
|
- !ruby/object:Gem::Version
|
144
128
|
version: '0'
|
129
|
+
- !ruby/object:Gem::Dependency
|
130
|
+
name: activerecord
|
131
|
+
requirement: !ruby/object:Gem::Requirement
|
132
|
+
none: false
|
133
|
+
requirements:
|
134
|
+
- - ~>
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: '3'
|
137
|
+
type: :development
|
138
|
+
prerelease: false
|
139
|
+
version_requirements: !ruby/object:Gem::Requirement
|
140
|
+
none: false
|
141
|
+
requirements:
|
142
|
+
- - ~>
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '3'
|
145
|
+
- !ruby/object:Gem::Dependency
|
146
|
+
name: yard
|
147
|
+
requirement: !ruby/object:Gem::Requirement
|
148
|
+
none: false
|
149
|
+
requirements:
|
150
|
+
- - ! '>='
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
type: :development
|
154
|
+
prerelease: false
|
155
|
+
version_requirements: !ruby/object:Gem::Requirement
|
156
|
+
none: false
|
157
|
+
requirements:
|
158
|
+
- - ! '>='
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
version: '0'
|
145
161
|
description: Perform weighted averages, even across associations. Rails 3 only because
|
146
162
|
it uses ARel.
|
147
163
|
email:
|
@@ -151,12 +167,17 @@ extensions: []
|
|
151
167
|
extra_rdoc_files: []
|
152
168
|
files:
|
153
169
|
- .gitignore
|
170
|
+
- .yardopts
|
154
171
|
- CHANGELOG
|
155
172
|
- Gemfile
|
156
173
|
- LICENSE
|
157
|
-
- README.
|
174
|
+
- README.markdown
|
158
175
|
- Rakefile
|
159
176
|
- lib/weighted_average.rb
|
177
|
+
- lib/weighted_average/active_record_base_class_methods.rb
|
178
|
+
- lib/weighted_average/active_record_relation_instance_methods.rb
|
179
|
+
- lib/weighted_average/arel_select_manager_instance_methods.rb
|
180
|
+
- lib/weighted_average/arel_table_instance_methods.rb
|
160
181
|
- lib/weighted_average/version.rb
|
161
182
|
- test/helper.rb
|
162
183
|
- test/test_weighted_average.rb
|