dm-aggregates 1.1.0.rc2 → 1.1.0.rc3
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/Gemfile +1 -1
- data/VERSION +1 -1
- data/dm-aggregates.gemspec +8 -6
- data/lib/dm-aggregates.rb +0 -6
- data/lib/dm-aggregates/aggregate_functions.rb +1 -232
- data/lib/dm-aggregates/collection.rb +0 -4
- data/lib/dm-aggregates/core_ext/symbol.rb +6 -8
- data/lib/dm-aggregates/functions.rb +232 -0
- data/lib/dm-aggregates/model.rb +0 -4
- data/lib/dm-aggregates/operators.rb +25 -0
- data/lib/dm-aggregates/repository.rb +5 -2
- data/spec/public/collection_spec.rb +3 -1
- data/spec/public/shared/aggregate_shared_spec.rb +11 -9
- data/spec/spec_helper.rb +2 -0
- metadata +6 -4
data/Gemfile
CHANGED
@@ -5,7 +5,7 @@ source 'http://rubygems.org'
|
|
5
5
|
SOURCE = ENV.fetch('SOURCE', :git).to_sym
|
6
6
|
REPO_POSTFIX = SOURCE == :path ? '' : '.git'
|
7
7
|
DATAMAPPER = SOURCE == :path ? Pathname(__FILE__).dirname.parent : 'http://github.com/datamapper'
|
8
|
-
DM_VERSION = '~> 1.1.0.
|
8
|
+
DM_VERSION = '~> 1.1.0.rc3'
|
9
9
|
DO_VERSION = '~> 0.10.2'
|
10
10
|
DM_DO_ADAPTERS = %w[ sqlite postgres mysql oracle sqlserver ]
|
11
11
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.1.0.
|
1
|
+
1.1.0.rc3
|
data/dm-aggregates.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{dm-aggregates}
|
8
|
-
s.version = "1.1.0.
|
8
|
+
s.version = "1.1.0.rc3"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Foy Savas"]
|
12
|
-
s.date = %q{2011-03-
|
12
|
+
s.date = %q{2011-03-10}
|
13
13
|
s.description = %q{DataMapper plugin providing support for aggregates on collections}
|
14
14
|
s.email = %q{foysavas [a] gmail [d] com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -28,7 +28,9 @@ Gem::Specification.new do |s|
|
|
28
28
|
"lib/dm-aggregates/aggregate_functions.rb",
|
29
29
|
"lib/dm-aggregates/collection.rb",
|
30
30
|
"lib/dm-aggregates/core_ext/symbol.rb",
|
31
|
+
"lib/dm-aggregates/functions.rb",
|
31
32
|
"lib/dm-aggregates/model.rb",
|
33
|
+
"lib/dm-aggregates/operators.rb",
|
32
34
|
"lib/dm-aggregates/query.rb",
|
33
35
|
"lib/dm-aggregates/repository.rb",
|
34
36
|
"spec/isolated/require_after_setup_spec.rb",
|
@@ -47,7 +49,7 @@ Gem::Specification.new do |s|
|
|
47
49
|
s.homepage = %q{http://github.com/datamapper/dm-aggregates}
|
48
50
|
s.require_paths = ["lib"]
|
49
51
|
s.rubyforge_project = %q{datamapper}
|
50
|
-
s.rubygems_version = %q{1.
|
52
|
+
s.rubygems_version = %q{1.6.2}
|
51
53
|
s.summary = %q{DataMapper plugin providing support for aggregates on collections}
|
52
54
|
s.test_files = [
|
53
55
|
"spec/isolated/require_after_setup_spec.rb",
|
@@ -63,18 +65,18 @@ Gem::Specification.new do |s|
|
|
63
65
|
s.specification_version = 3
|
64
66
|
|
65
67
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
66
|
-
s.add_runtime_dependency(%q<dm-core>, ["~> 1.1.0.
|
68
|
+
s.add_runtime_dependency(%q<dm-core>, ["~> 1.1.0.rc3"])
|
67
69
|
s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
|
68
70
|
s.add_development_dependency(%q<rake>, ["~> 0.8.7"])
|
69
71
|
s.add_development_dependency(%q<rspec>, ["~> 1.3.1"])
|
70
72
|
else
|
71
|
-
s.add_dependency(%q<dm-core>, ["~> 1.1.0.
|
73
|
+
s.add_dependency(%q<dm-core>, ["~> 1.1.0.rc3"])
|
72
74
|
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
73
75
|
s.add_dependency(%q<rake>, ["~> 0.8.7"])
|
74
76
|
s.add_dependency(%q<rspec>, ["~> 1.3.1"])
|
75
77
|
end
|
76
78
|
else
|
77
|
-
s.add_dependency(%q<dm-core>, ["~> 1.1.0.
|
79
|
+
s.add_dependency(%q<dm-core>, ["~> 1.1.0.rc3"])
|
78
80
|
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
79
81
|
s.add_dependency(%q<rake>, ["~> 0.8.7"])
|
80
82
|
s.add_dependency(%q<rspec>, ["~> 1.3.1"])
|
data/lib/dm-aggregates.rb
CHANGED
@@ -7,12 +7,6 @@ require 'dm-aggregates/model'
|
|
7
7
|
require 'dm-aggregates/query'
|
8
8
|
require 'dm-aggregates/repository'
|
9
9
|
|
10
|
-
begin
|
11
|
-
require 'active_support/core_ext/time/conversions'
|
12
|
-
rescue LoadError
|
13
|
-
require 'extlib/time'
|
14
|
-
end
|
15
|
-
|
16
10
|
module DataMapper
|
17
11
|
module Aggregates
|
18
12
|
def self.include_aggregate_api
|
@@ -1,232 +1 @@
|
|
1
|
-
|
2
|
-
module Aggregates
|
3
|
-
module Functions
|
4
|
-
include DataMapper::Assertions
|
5
|
-
|
6
|
-
# Count results (given the conditions)
|
7
|
-
#
|
8
|
-
# @example the count of all friends
|
9
|
-
# Friend.count
|
10
|
-
#
|
11
|
-
# @example the count of all friends older then 18
|
12
|
-
# Friend.count(:age.gt => 18)
|
13
|
-
#
|
14
|
-
# @example the count of all your female friends
|
15
|
-
# Friend.count(:conditions => [ 'gender = ?', 'female' ])
|
16
|
-
#
|
17
|
-
# @example the count of all friends with an address (NULL values are not included)
|
18
|
-
# Friend.count(:address)
|
19
|
-
#
|
20
|
-
# @example the count of all friends with an address that are older then 18
|
21
|
-
# Friend.count(:address, :age.gt => 18)
|
22
|
-
#
|
23
|
-
# @example the count of all your female friends with an address
|
24
|
-
# Friend.count(:address, :conditions => [ 'gender = ?', 'female' ])
|
25
|
-
#
|
26
|
-
# @param property [Symbol] of the property you with to count (optional)
|
27
|
-
# @param opts [Hash, Symbol] the conditions
|
28
|
-
#
|
29
|
-
# @return [Integer] return the count given the conditions
|
30
|
-
#
|
31
|
-
# @api public
|
32
|
-
def count(*args)
|
33
|
-
query = args.last.kind_of?(Hash) ? args.pop : {}
|
34
|
-
property_name = args.first
|
35
|
-
|
36
|
-
if property_name
|
37
|
-
assert_kind_of 'property', property_by_name(property_name), Property
|
38
|
-
end
|
39
|
-
|
40
|
-
aggregate(query.merge(:fields => [ property_name ? property_name.count : :all.count ])).to_i
|
41
|
-
end
|
42
|
-
|
43
|
-
# Get the lowest value of a property
|
44
|
-
#
|
45
|
-
# @example the age of the youngest friend
|
46
|
-
# Friend.min(:age)
|
47
|
-
#
|
48
|
-
# @example the age of the youngest female friend
|
49
|
-
# Friend.min(:age, :conditions => [ 'gender = ?', 'female' ])
|
50
|
-
#
|
51
|
-
# @param property [Symbol] the property you wish to get the lowest value of
|
52
|
-
# @param opts [Hash, Symbol] the conditions
|
53
|
-
#
|
54
|
-
# @return [Integer] return the lowest value of a property given the conditions
|
55
|
-
#
|
56
|
-
# @api public
|
57
|
-
def min(*args)
|
58
|
-
query = args.last.kind_of?(Hash) ? args.pop : {}
|
59
|
-
property_name = args.first
|
60
|
-
|
61
|
-
assert_property_type property_name, ::Integer, ::Float, ::BigDecimal, ::DateTime, ::Date, ::Time
|
62
|
-
|
63
|
-
aggregate(query.merge(:fields => [ property_name.min ]))
|
64
|
-
end
|
65
|
-
|
66
|
-
# Get the highest value of a property
|
67
|
-
#
|
68
|
-
# @example the age of the oldest friend
|
69
|
-
# Friend.max(:age)
|
70
|
-
#
|
71
|
-
# @example the age of the oldest female friend
|
72
|
-
# Friend.max(:age, :conditions => [ 'gender = ?', 'female' ])
|
73
|
-
#
|
74
|
-
# @param property [Symbol] the property you wish to get the highest value of
|
75
|
-
# @param opts [Hash, Symbol] the conditions
|
76
|
-
#
|
77
|
-
# @return [Integer] return the highest value of a property given the conditions
|
78
|
-
#
|
79
|
-
# @api public
|
80
|
-
def max(*args)
|
81
|
-
query = args.last.kind_of?(Hash) ? args.pop : {}
|
82
|
-
property_name = args.first
|
83
|
-
|
84
|
-
assert_property_type property_name, ::Integer, ::Float, ::BigDecimal, ::DateTime, ::Date, ::Time
|
85
|
-
|
86
|
-
aggregate(query.merge(:fields => [ property_name.max ]))
|
87
|
-
end
|
88
|
-
|
89
|
-
# Get the average value of a property
|
90
|
-
#
|
91
|
-
# @example the average age of all friends
|
92
|
-
# Friend.avg(:age)
|
93
|
-
#
|
94
|
-
# @example the average age of all female friends
|
95
|
-
# Friend.avg(:age, :conditions => [ 'gender = ?', 'female' ])
|
96
|
-
#
|
97
|
-
# @param property [Symbol] the property you wish to get the average value of
|
98
|
-
# @param opts [Hash, Symbol] the conditions
|
99
|
-
#
|
100
|
-
# @return [Integer] return the average value of a property given the conditions
|
101
|
-
#
|
102
|
-
# @api public
|
103
|
-
def avg(*args)
|
104
|
-
query = args.last.kind_of?(Hash) ? args.pop : {}
|
105
|
-
property_name = args.first
|
106
|
-
|
107
|
-
assert_property_type property_name, ::Integer, ::Float, ::BigDecimal
|
108
|
-
|
109
|
-
aggregate(query.merge(:fields => [ property_name.avg ]))
|
110
|
-
end
|
111
|
-
|
112
|
-
# Get the total value of a property
|
113
|
-
#
|
114
|
-
# @example the total age of all friends
|
115
|
-
# Friend.sum(:age)
|
116
|
-
#
|
117
|
-
# @example the total age of all female friends
|
118
|
-
# Friend.max(:age, :conditions => [ 'gender = ?', 'female' ])
|
119
|
-
#
|
120
|
-
# @param property [Symbol] the property you wish to get the total value of
|
121
|
-
# @param opts [Hash, Symbol] the conditions
|
122
|
-
#
|
123
|
-
# @return [Integer] return the total value of a property given the conditions
|
124
|
-
#
|
125
|
-
# @api public
|
126
|
-
def sum(*args)
|
127
|
-
query = args.last.kind_of?(::Hash) ? args.pop : {}
|
128
|
-
property_name = args.first
|
129
|
-
|
130
|
-
assert_property_type property_name, ::Integer, ::Float, ::BigDecimal
|
131
|
-
|
132
|
-
aggregate(query.merge(:fields => [ property_name.sum ]))
|
133
|
-
end
|
134
|
-
|
135
|
-
# Perform aggregate queries
|
136
|
-
#
|
137
|
-
# @example the count of friends
|
138
|
-
# Friend.aggregate(:all.count)
|
139
|
-
#
|
140
|
-
# @example the minimum age, the maximum age and the total age of friends
|
141
|
-
# Friend.aggregate(:age.min, :age.max, :age.sum)
|
142
|
-
#
|
143
|
-
# @example the average age, grouped by gender
|
144
|
-
# Friend.aggregate(:age.avg, :fields => [ :gender ])
|
145
|
-
#
|
146
|
-
# @param aggregates [Symbol, ...] operators to aggregate with
|
147
|
-
# @param query [Hash] the conditions
|
148
|
-
#
|
149
|
-
# @return [Array,Numeric,DateTime,Date,Time] the results of the
|
150
|
-
# aggregate query
|
151
|
-
#
|
152
|
-
# @api public
|
153
|
-
def aggregate(*args)
|
154
|
-
query = args.last.kind_of?(Hash) ? args.pop : {}
|
155
|
-
|
156
|
-
query[:fields] ||= []
|
157
|
-
query[:fields] |= args
|
158
|
-
query[:fields].map! { |f| normalize_field(f) }
|
159
|
-
|
160
|
-
raise ArgumentError, 'query[:fields] must not be empty' if query[:fields].empty?
|
161
|
-
|
162
|
-
unless query.key?(:order)
|
163
|
-
# the current collection/model is already sorted by attributes
|
164
|
-
# and since we are projecting away some of the attributes,
|
165
|
-
# and then performing aggregate functions on the remainder,
|
166
|
-
# we need to honor the existing order, as if it were already
|
167
|
-
# materialized, and we are looping over the rows in order.
|
168
|
-
|
169
|
-
directions = direction_map
|
170
|
-
|
171
|
-
query[:order] = []
|
172
|
-
|
173
|
-
# use the current query order for each property if available
|
174
|
-
query[:fields].each do |property|
|
175
|
-
next unless property.kind_of?(Property)
|
176
|
-
query[:order] << directions.fetch(property, property)
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
|
-
query = scoped_query(query)
|
181
|
-
|
182
|
-
if query.fields.any? { |p| p.kind_of?(Property) }
|
183
|
-
query.repository.aggregate(query.update(:unique => true))
|
184
|
-
else
|
185
|
-
query.repository.aggregate(query).first # only return one row
|
186
|
-
end
|
187
|
-
end
|
188
|
-
|
189
|
-
private
|
190
|
-
|
191
|
-
def assert_property_type(name, *types)
|
192
|
-
if name.nil?
|
193
|
-
raise ArgumentError, 'property name must not be nil'
|
194
|
-
end
|
195
|
-
|
196
|
-
property = property_by_name(name)
|
197
|
-
type = property.primitive
|
198
|
-
|
199
|
-
unless types.include?(type)
|
200
|
-
raise ArgumentError, "#{name} must be #{types * ' or '}, but was #{type}"
|
201
|
-
end
|
202
|
-
end
|
203
|
-
|
204
|
-
def normalize_field(field)
|
205
|
-
assert_kind_of 'field', field, DataMapper::Query::Operator, Symbol, Property
|
206
|
-
|
207
|
-
case field
|
208
|
-
when DataMapper::Query::Operator
|
209
|
-
if field.target == :all
|
210
|
-
field
|
211
|
-
else
|
212
|
-
field.class.new(property_by_name(field.target), field.operator)
|
213
|
-
end
|
214
|
-
|
215
|
-
when Symbol
|
216
|
-
property_by_name(field)
|
217
|
-
|
218
|
-
when Property
|
219
|
-
field
|
220
|
-
end
|
221
|
-
end
|
222
|
-
|
223
|
-
def direction_map
|
224
|
-
direction_map = {}
|
225
|
-
self.query.order.each do |direction|
|
226
|
-
direction_map[direction.target] = direction
|
227
|
-
end
|
228
|
-
direction_map
|
229
|
-
end
|
230
|
-
end
|
231
|
-
end
|
232
|
-
end
|
1
|
+
require 'dm-aggregates/functions'
|
@@ -1,9 +1,7 @@
|
|
1
|
+
require 'dm-aggregates/operators'
|
2
|
+
|
1
3
|
class Symbol
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
end
|
7
|
-
RUBY
|
8
|
-
end
|
9
|
-
end # class Symbol
|
4
|
+
|
5
|
+
include DataMapper::Aggregates::Operators
|
6
|
+
|
7
|
+
end
|
@@ -0,0 +1,232 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Aggregates
|
3
|
+
module Functions
|
4
|
+
include DataMapper::Assertions
|
5
|
+
|
6
|
+
# Count results (given the conditions)
|
7
|
+
#
|
8
|
+
# @example the count of all friends
|
9
|
+
# Friend.count
|
10
|
+
#
|
11
|
+
# @example the count of all friends older then 18
|
12
|
+
# Friend.count(:age.gt => 18)
|
13
|
+
#
|
14
|
+
# @example the count of all your female friends
|
15
|
+
# Friend.count(:conditions => [ 'gender = ?', 'female' ])
|
16
|
+
#
|
17
|
+
# @example the count of all friends with an address (NULL values are not included)
|
18
|
+
# Friend.count(:address)
|
19
|
+
#
|
20
|
+
# @example the count of all friends with an address that are older then 18
|
21
|
+
# Friend.count(:address, :age.gt => 18)
|
22
|
+
#
|
23
|
+
# @example the count of all your female friends with an address
|
24
|
+
# Friend.count(:address, :conditions => [ 'gender = ?', 'female' ])
|
25
|
+
#
|
26
|
+
# @param property [Symbol] of the property you with to count (optional)
|
27
|
+
# @param opts [Hash, Symbol] the conditions
|
28
|
+
#
|
29
|
+
# @return [Integer] return the count given the conditions
|
30
|
+
#
|
31
|
+
# @api public
|
32
|
+
def count(*args)
|
33
|
+
query = args.last.kind_of?(Hash) ? args.pop : {}
|
34
|
+
property_name = args.first
|
35
|
+
|
36
|
+
if property_name
|
37
|
+
assert_kind_of 'property', property_by_name(property_name), Property
|
38
|
+
end
|
39
|
+
|
40
|
+
aggregate(query.merge(:fields => [ property_name ? property_name.count : :all.count ])).to_i
|
41
|
+
end
|
42
|
+
|
43
|
+
# Get the lowest value of a property
|
44
|
+
#
|
45
|
+
# @example the age of the youngest friend
|
46
|
+
# Friend.min(:age)
|
47
|
+
#
|
48
|
+
# @example the age of the youngest female friend
|
49
|
+
# Friend.min(:age, :conditions => [ 'gender = ?', 'female' ])
|
50
|
+
#
|
51
|
+
# @param property [Symbol] the property you wish to get the lowest value of
|
52
|
+
# @param opts [Hash, Symbol] the conditions
|
53
|
+
#
|
54
|
+
# @return [Integer] return the lowest value of a property given the conditions
|
55
|
+
#
|
56
|
+
# @api public
|
57
|
+
def min(*args)
|
58
|
+
query = args.last.kind_of?(Hash) ? args.pop : {}
|
59
|
+
property_name = args.first
|
60
|
+
|
61
|
+
assert_property_type property_name, ::Integer, ::Float, ::BigDecimal, ::DateTime, ::Date, ::Time
|
62
|
+
|
63
|
+
aggregate(query.merge(:fields => [ property_name.min ]))
|
64
|
+
end
|
65
|
+
|
66
|
+
# Get the highest value of a property
|
67
|
+
#
|
68
|
+
# @example the age of the oldest friend
|
69
|
+
# Friend.max(:age)
|
70
|
+
#
|
71
|
+
# @example the age of the oldest female friend
|
72
|
+
# Friend.max(:age, :conditions => [ 'gender = ?', 'female' ])
|
73
|
+
#
|
74
|
+
# @param property [Symbol] the property you wish to get the highest value of
|
75
|
+
# @param opts [Hash, Symbol] the conditions
|
76
|
+
#
|
77
|
+
# @return [Integer] return the highest value of a property given the conditions
|
78
|
+
#
|
79
|
+
# @api public
|
80
|
+
def max(*args)
|
81
|
+
query = args.last.kind_of?(Hash) ? args.pop : {}
|
82
|
+
property_name = args.first
|
83
|
+
|
84
|
+
assert_property_type property_name, ::Integer, ::Float, ::BigDecimal, ::DateTime, ::Date, ::Time
|
85
|
+
|
86
|
+
aggregate(query.merge(:fields => [ property_name.max ]))
|
87
|
+
end
|
88
|
+
|
89
|
+
# Get the average value of a property
|
90
|
+
#
|
91
|
+
# @example the average age of all friends
|
92
|
+
# Friend.avg(:age)
|
93
|
+
#
|
94
|
+
# @example the average age of all female friends
|
95
|
+
# Friend.avg(:age, :conditions => [ 'gender = ?', 'female' ])
|
96
|
+
#
|
97
|
+
# @param property [Symbol] the property you wish to get the average value of
|
98
|
+
# @param opts [Hash, Symbol] the conditions
|
99
|
+
#
|
100
|
+
# @return [Integer] return the average value of a property given the conditions
|
101
|
+
#
|
102
|
+
# @api public
|
103
|
+
def avg(*args)
|
104
|
+
query = args.last.kind_of?(Hash) ? args.pop : {}
|
105
|
+
property_name = args.first
|
106
|
+
|
107
|
+
assert_property_type property_name, ::Integer, ::Float, ::BigDecimal
|
108
|
+
|
109
|
+
aggregate(query.merge(:fields => [ property_name.avg ]))
|
110
|
+
end
|
111
|
+
|
112
|
+
# Get the total value of a property
|
113
|
+
#
|
114
|
+
# @example the total age of all friends
|
115
|
+
# Friend.sum(:age)
|
116
|
+
#
|
117
|
+
# @example the total age of all female friends
|
118
|
+
# Friend.max(:age, :conditions => [ 'gender = ?', 'female' ])
|
119
|
+
#
|
120
|
+
# @param property [Symbol] the property you wish to get the total value of
|
121
|
+
# @param opts [Hash, Symbol] the conditions
|
122
|
+
#
|
123
|
+
# @return [Integer] return the total value of a property given the conditions
|
124
|
+
#
|
125
|
+
# @api public
|
126
|
+
def sum(*args)
|
127
|
+
query = args.last.kind_of?(::Hash) ? args.pop : {}
|
128
|
+
property_name = args.first
|
129
|
+
|
130
|
+
assert_property_type property_name, ::Integer, ::Float, ::BigDecimal
|
131
|
+
|
132
|
+
aggregate(query.merge(:fields => [ property_name.sum ]))
|
133
|
+
end
|
134
|
+
|
135
|
+
# Perform aggregate queries
|
136
|
+
#
|
137
|
+
# @example the count of friends
|
138
|
+
# Friend.aggregate(:all.count)
|
139
|
+
#
|
140
|
+
# @example the minimum age, the maximum age and the total age of friends
|
141
|
+
# Friend.aggregate(:age.min, :age.max, :age.sum)
|
142
|
+
#
|
143
|
+
# @example the average age, grouped by gender
|
144
|
+
# Friend.aggregate(:age.avg, :fields => [ :gender ])
|
145
|
+
#
|
146
|
+
# @param aggregates [Symbol, ...] operators to aggregate with
|
147
|
+
# @param query [Hash] the conditions
|
148
|
+
#
|
149
|
+
# @return [Array,Numeric,DateTime,Date,Time] the results of the
|
150
|
+
# aggregate query
|
151
|
+
#
|
152
|
+
# @api public
|
153
|
+
def aggregate(*args)
|
154
|
+
query = args.last.kind_of?(Hash) ? args.pop : {}
|
155
|
+
|
156
|
+
query[:fields] ||= []
|
157
|
+
query[:fields] |= args
|
158
|
+
query[:fields].map! { |f| normalize_field(f) }
|
159
|
+
|
160
|
+
raise ArgumentError, 'query[:fields] must not be empty' if query[:fields].empty?
|
161
|
+
|
162
|
+
unless query.key?(:order)
|
163
|
+
# the current collection/model is already sorted by attributes
|
164
|
+
# and since we are projecting away some of the attributes,
|
165
|
+
# and then performing aggregate functions on the remainder,
|
166
|
+
# we need to honor the existing order, as if it were already
|
167
|
+
# materialized, and we are looping over the rows in order.
|
168
|
+
|
169
|
+
directions = direction_map
|
170
|
+
|
171
|
+
query[:order] = []
|
172
|
+
|
173
|
+
# use the current query order for each property if available
|
174
|
+
query[:fields].each do |property|
|
175
|
+
next unless property.kind_of?(Property)
|
176
|
+
query[:order] << directions.fetch(property, property)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
query = scoped_query(query)
|
181
|
+
|
182
|
+
if query.fields.any? { |p| p.kind_of?(Property) }
|
183
|
+
query.repository.aggregate(query.update(:unique => true))
|
184
|
+
else
|
185
|
+
query.repository.aggregate(query).first # only return one row
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
private
|
190
|
+
|
191
|
+
def assert_property_type(name, *types)
|
192
|
+
if name.nil?
|
193
|
+
raise ArgumentError, 'property name must not be nil'
|
194
|
+
end
|
195
|
+
|
196
|
+
property = property_by_name(name)
|
197
|
+
type = property.primitive
|
198
|
+
|
199
|
+
unless types.include?(type)
|
200
|
+
raise ArgumentError, "#{name} must be #{types * ' or '}, but was #{type}"
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def normalize_field(field)
|
205
|
+
assert_kind_of 'field', field, DataMapper::Query::Operator, Symbol, Property
|
206
|
+
|
207
|
+
case field
|
208
|
+
when DataMapper::Query::Operator
|
209
|
+
if field.target == :all
|
210
|
+
field
|
211
|
+
else
|
212
|
+
field.class.new(property_by_name(field.target), field.operator)
|
213
|
+
end
|
214
|
+
|
215
|
+
when Symbol
|
216
|
+
property_by_name(field)
|
217
|
+
|
218
|
+
when Property
|
219
|
+
field
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def direction_map
|
224
|
+
direction_map = {}
|
225
|
+
self.query.order.each do |direction|
|
226
|
+
direction_map[direction.target] = direction
|
227
|
+
end
|
228
|
+
direction_map
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
data/lib/dm-aggregates/model.rb
CHANGED
@@ -0,0 +1,25 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Aggregates
|
3
|
+
module Operators
|
4
|
+
def count
|
5
|
+
DataMapper::Query::Operator.new(self, :count)
|
6
|
+
end
|
7
|
+
|
8
|
+
def min
|
9
|
+
DataMapper::Query::Operator.new(self, :min)
|
10
|
+
end
|
11
|
+
|
12
|
+
def max
|
13
|
+
DataMapper::Query::Operator.new(self, :max)
|
14
|
+
end
|
15
|
+
|
16
|
+
def avg
|
17
|
+
DataMapper::Query::Operator.new(self, :avg)
|
18
|
+
end
|
19
|
+
|
20
|
+
def sum
|
21
|
+
DataMapper::Query::Operator.new(self, :sum)
|
22
|
+
end
|
23
|
+
end # module Operators
|
24
|
+
end # module Aggregates
|
25
|
+
end # module DataMapper
|
@@ -118,7 +118,9 @@ describe DataMapper::Collection do
|
|
118
118
|
|
119
119
|
describe "#count" do
|
120
120
|
it 'should return 1' do
|
121
|
-
pending
|
121
|
+
pending 'TODO: make count apply to the limited collection. Currently limit applies after the count' do
|
122
|
+
@dragons.count.should == 1
|
123
|
+
end
|
122
124
|
end
|
123
125
|
end
|
124
126
|
|
@@ -10,9 +10,9 @@ shared_examples_for 'It Has Setup Resources' do
|
|
10
10
|
|
11
11
|
DataMapper.auto_migrate!
|
12
12
|
|
13
|
-
@
|
14
|
-
@
|
15
|
-
@
|
13
|
+
@birth_at = DateTime.now
|
14
|
+
@birth_on = Date.parse(@birth_at.to_s)
|
15
|
+
@birth_time = Time.parse(@birth_at.to_s)
|
16
16
|
|
17
17
|
@chuck = Knight.create(:name => 'Chuck')
|
18
18
|
@larry = Knight.create(:name => 'Larry')
|
@@ -112,9 +112,10 @@ shared_examples_for 'An Aggregatable Class' do
|
|
112
112
|
end
|
113
113
|
|
114
114
|
it 'should provide the lowest value of a DateTime property' do
|
115
|
-
|
116
|
-
|
117
|
-
|
115
|
+
pending_if 'TODO: returns incorrect value until DO handles TZs properly', @skip do
|
116
|
+
@dragons.min(:birth_at).should be_kind_of(DateTime)
|
117
|
+
@dragons.min(:birth_at).to_s.should == @birth_at.to_s
|
118
|
+
end
|
118
119
|
end
|
119
120
|
|
120
121
|
it 'should provide the lowest value of a Date property' do
|
@@ -158,9 +159,10 @@ shared_examples_for 'An Aggregatable Class' do
|
|
158
159
|
end
|
159
160
|
|
160
161
|
it 'should provide the highest value of a DateTime property' do
|
161
|
-
|
162
|
-
|
163
|
-
|
162
|
+
pending_if 'TODO: returns incorrect value until DO handles TZs properly', @skip do
|
163
|
+
@dragons.min(:birth_at).should be_kind_of(DateTime)
|
164
|
+
@dragons.min(:birth_at).to_s.should == @birth_at.to_s
|
165
|
+
end
|
164
166
|
end
|
165
167
|
|
166
168
|
it 'should provide the highest value of a Date property' do
|
data/spec/spec_helper.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'dm-core/spec/setup'
|
2
2
|
require 'dm-core/spec/lib/adapter_helpers'
|
3
|
+
require 'dm-core/spec/lib/pending_helpers'
|
3
4
|
|
4
5
|
require 'dm-aggregates'
|
5
6
|
require 'dm-migrations'
|
@@ -11,6 +12,7 @@ DataMapper::Spec.setup
|
|
11
12
|
Spec::Runner.configure do |config|
|
12
13
|
|
13
14
|
config.extend(DataMapper::Spec::Adapters::Helpers)
|
15
|
+
config.include(DataMapper::Spec::PendingHelpers)
|
14
16
|
|
15
17
|
config.before(:all) do
|
16
18
|
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: dm-aggregates
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease: 6
|
5
|
-
version: 1.1.0.
|
5
|
+
version: 1.1.0.rc3
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Foy Savas
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-03-
|
13
|
+
date: 2011-03-10 00:00:00 -08:00
|
14
14
|
default_executable:
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
@@ -20,7 +20,7 @@ dependencies:
|
|
20
20
|
requirements:
|
21
21
|
- - ~>
|
22
22
|
- !ruby/object:Gem::Version
|
23
|
-
version: 1.1.0.
|
23
|
+
version: 1.1.0.rc3
|
24
24
|
type: :runtime
|
25
25
|
prerelease: false
|
26
26
|
version_requirements: *id001
|
@@ -78,7 +78,9 @@ files:
|
|
78
78
|
- lib/dm-aggregates/aggregate_functions.rb
|
79
79
|
- lib/dm-aggregates/collection.rb
|
80
80
|
- lib/dm-aggregates/core_ext/symbol.rb
|
81
|
+
- lib/dm-aggregates/functions.rb
|
81
82
|
- lib/dm-aggregates/model.rb
|
83
|
+
- lib/dm-aggregates/operators.rb
|
82
84
|
- lib/dm-aggregates/query.rb
|
83
85
|
- lib/dm-aggregates/repository.rb
|
84
86
|
- spec/isolated/require_after_setup_spec.rb
|
@@ -117,7 +119,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
117
119
|
requirements: []
|
118
120
|
|
119
121
|
rubyforge_project: datamapper
|
120
|
-
rubygems_version: 1.
|
122
|
+
rubygems_version: 1.6.2
|
121
123
|
signing_key:
|
122
124
|
specification_version: 3
|
123
125
|
summary: DataMapper plugin providing support for aggregates on collections
|