dm-aggregates 0.9.2 → 0.9.3
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/History.txt +1 -0
- data/Manifest.txt +17 -0
- data/{README → README.txt} +0 -0
- data/Rakefile +21 -28
- data/lib/dm-aggregates.rb +10 -7
- data/lib/dm-aggregates/adapters/data_objects_adapter.rb +46 -31
- data/lib/dm-aggregates/aggregate_functions.rb +200 -0
- data/lib/dm-aggregates/collection.rb +3 -10
- data/lib/dm-aggregates/model.rb +3 -10
- data/lib/dm-aggregates/repository.rb +2 -18
- data/lib/dm-aggregates/support/symbol.rb +21 -0
- data/lib/dm-aggregates/version.rb +7 -0
- data/spec/integration/aggregates_spec.rb +91 -12
- data/spec/spec_helper.rb +1 -1
- metadata +33 -16
- data/lib/dm-aggregates/functions.rb +0 -118
data/History.txt
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
|
data/Manifest.txt
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
History.txt
|
2
|
+
LICENSE
|
3
|
+
Manifest.txt
|
4
|
+
README.txt
|
5
|
+
Rakefile
|
6
|
+
TODO
|
7
|
+
lib/dm-aggregates.rb
|
8
|
+
lib/dm-aggregates/adapters/data_objects_adapter.rb
|
9
|
+
lib/dm-aggregates/aggregate_functions.rb
|
10
|
+
lib/dm-aggregates/collection.rb
|
11
|
+
lib/dm-aggregates/model.rb
|
12
|
+
lib/dm-aggregates/repository.rb
|
13
|
+
lib/dm-aggregates/support/symbol.rb
|
14
|
+
lib/dm-aggregates/version.rb
|
15
|
+
spec/integration/aggregates_spec.rb
|
16
|
+
spec/spec.opts
|
17
|
+
spec/spec_helper.rb
|
data/{README → README.txt}
RENAMED
File without changes
|
data/Rakefile
CHANGED
@@ -1,51 +1,44 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'spec'
|
3
|
-
require 'rake/clean'
|
4
|
-
require 'rake/gempackagetask'
|
5
3
|
require 'spec/rake/spectask'
|
6
4
|
require 'pathname'
|
7
5
|
|
8
|
-
|
6
|
+
ROOT = Pathname(__FILE__).dirname.expand_path
|
7
|
+
require ROOT + 'lib/dm-aggregates/version'
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
s.add_dependency('dm-core', "=#{s.version}")
|
24
|
-
end
|
9
|
+
AUTHOR = "Foy Savas"
|
10
|
+
EMAIL = "foysavas@gmail.com"
|
11
|
+
GEM_NAME = "dm-aggregates"
|
12
|
+
GEM_VERSION = DataMapper::More::Aggregates::VERSION
|
13
|
+
GEM_DEPENDENCIES = [["dm-core", GEM_VERSION]]
|
14
|
+
GEM_CLEAN = ["log", "pkg"]
|
15
|
+
GEM_EXTRAS = { :has_rdoc => true, :extra_rdoc_files => %w[ README.txt LICENSE TODO ] }
|
16
|
+
|
17
|
+
PROJECT_NAME = "datamapper"
|
18
|
+
PROJECT_URL = "http://github.com/sam/dm-more/tree/master/dm-aggregates"
|
19
|
+
PROJECT_DESCRIPTION = PROJECT_SUMMARY = "DataMapper plugin providing support for aggregates, functions on collections and datasets"
|
20
|
+
|
21
|
+
require ROOT.parent + 'tasks/hoe'
|
25
22
|
|
26
23
|
task :default => [ :spec ]
|
27
24
|
|
28
25
|
WIN32 = (RUBY_PLATFORM =~ /win32|mingw|cygwin/) rescue nil
|
29
26
|
SUDO = WIN32 ? '' : ('sudo' unless ENV['SUDOLESS'])
|
30
27
|
|
31
|
-
|
32
|
-
pkg.gem_spec = spec
|
33
|
-
end
|
34
|
-
|
35
|
-
desc "Install #{spec.name} #{spec.version} (default ruby)"
|
28
|
+
desc "Install #{GEM_NAME} #{GEM_VERSION} (default ruby)"
|
36
29
|
task :install => [ :package ] do
|
37
|
-
sh "#{SUDO} gem install --local pkg/#{
|
30
|
+
sh "#{SUDO} gem install --local pkg/#{GEM_NAME}-#{GEM_VERSION} --no-update-sources", :verbose => false
|
38
31
|
end
|
39
32
|
|
40
|
-
desc "Uninstall #{
|
33
|
+
desc "Uninstall #{GEM_NAME} #{GEM_VERSION} (default ruby)"
|
41
34
|
task :uninstall => [ :clobber ] do
|
42
|
-
sh "#{SUDO} gem uninstall #{
|
35
|
+
sh "#{SUDO} gem uninstall #{GEM_NAME} -v#{GEM_VERSION} -I -x", :verbose => false
|
43
36
|
end
|
44
37
|
|
45
38
|
namespace :jruby do
|
46
|
-
desc "Install #{
|
39
|
+
desc "Install #{GEM_NAME} #{GEM_VERSION} with JRuby"
|
47
40
|
task :install => [ :package ] do
|
48
|
-
sh %{#{SUDO} jruby -S gem install --local pkg/#{
|
41
|
+
sh %{#{SUDO} jruby -S gem install --local pkg/#{GEM_NAME}-#{GEM_VERSION} --no-update-sources}, :verbose => false
|
49
42
|
end
|
50
43
|
end
|
51
44
|
|
data/lib/dm-aggregates.rb
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
|
3
|
-
|
3
|
+
dir = Pathname(__FILE__).dirname.expand_path + 'dm-aggregates'
|
4
|
+
|
5
|
+
require dir + 'version'
|
6
|
+
gem 'dm-core', DataMapper::More::Aggregates::VERSION
|
4
7
|
require 'dm-core'
|
5
8
|
|
6
|
-
dir = Pathname(__FILE__).dirname.expand_path / 'dm-aggregates'
|
7
9
|
|
8
|
-
require dir
|
9
|
-
require dir
|
10
|
-
require dir
|
11
|
-
require dir
|
12
|
-
require dir
|
10
|
+
require dir + 'aggregate_functions'
|
11
|
+
require dir + 'model'
|
12
|
+
require dir + 'repository'
|
13
|
+
require dir + 'collection'
|
14
|
+
require dir + 'adapters' + 'data_objects_adapter'
|
15
|
+
require dir + 'support' + 'symbol'
|
@@ -1,53 +1,68 @@
|
|
1
1
|
module DataMapper
|
2
2
|
module Adapters
|
3
3
|
class DataObjectsAdapter
|
4
|
-
def
|
5
|
-
|
4
|
+
def aggregate(query)
|
5
|
+
with_reader(read_statement(query), query.bind_values) do |reader|
|
6
|
+
results = []
|
7
|
+
|
8
|
+
while(reader.next!) do
|
9
|
+
row = query.fields.zip(reader.values).map do |field,value|
|
10
|
+
if field.respond_to?(:operator)
|
11
|
+
send(field.operator, field.target, value)
|
12
|
+
else
|
13
|
+
field.typecast(value)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
results << (query.fields.size > 1 ? row : row[0])
|
18
|
+
end
|
19
|
+
|
20
|
+
results
|
21
|
+
end
|
6
22
|
end
|
7
23
|
|
8
|
-
|
9
|
-
|
10
|
-
|
24
|
+
private
|
25
|
+
|
26
|
+
def count(property, value)
|
27
|
+
value.to_i
|
28
|
+
end
|
29
|
+
|
30
|
+
def min(property, value)
|
31
|
+
property.typecast(value)
|
11
32
|
end
|
12
33
|
|
13
|
-
def max(property,
|
14
|
-
|
15
|
-
property.typecast(max)
|
34
|
+
def max(property, value)
|
35
|
+
property.typecast(value)
|
16
36
|
end
|
17
37
|
|
18
|
-
def avg(property,
|
19
|
-
|
20
|
-
property.type == Integer ? avg.to_f : property.typecast(avg)
|
38
|
+
def avg(property, value)
|
39
|
+
property.type == Integer ? value.to_f : property.typecast(value)
|
21
40
|
end
|
22
41
|
|
23
|
-
def sum(property,
|
24
|
-
|
25
|
-
property.typecast(sum)
|
42
|
+
def sum(property, value)
|
43
|
+
property.typecast(value)
|
26
44
|
end
|
27
45
|
|
28
46
|
module SQL
|
29
47
|
private
|
30
48
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
rescue => e
|
45
|
-
DataMapper.logger.error("QUERY INVALID: #{query.inspect} (#{e})")
|
46
|
-
raise e
|
49
|
+
alias original_property_to_column_name property_to_column_name
|
50
|
+
|
51
|
+
def property_to_column_name(repository, property, qualify)
|
52
|
+
case property
|
53
|
+
when Query::Operator
|
54
|
+
aggregate_field_statement(repository, property.operator, property.target, qualify)
|
55
|
+
when Property
|
56
|
+
original_property_to_column_name(repository, property, qualify)
|
57
|
+
when Query::Path
|
58
|
+
original_property_to_column_name(repository, property, qualify)
|
59
|
+
else
|
60
|
+
raise ArgumentError, "+property+ must be a DataMapper::Query::Operator or a DataMapper::Property, but was a #{property.class} (#{property.inspect})"
|
61
|
+
end
|
47
62
|
end
|
48
63
|
|
49
64
|
def aggregate_field_statement(repository, aggregate_function, property, qualify)
|
50
|
-
column_name
|
65
|
+
column_name = if aggregate_function == :count && property == :all
|
51
66
|
'*'
|
52
67
|
else
|
53
68
|
property_to_column_name(repository, property, qualify)
|
@@ -0,0 +1,200 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module AggregateFunctions
|
3
|
+
# Count results (given the conditions)
|
4
|
+
#
|
5
|
+
# @example the count of all friends
|
6
|
+
# Friend.count
|
7
|
+
#
|
8
|
+
# @example the count of all friends older then 18
|
9
|
+
# Friend.count(:age.gt => 18)
|
10
|
+
#
|
11
|
+
# @example the count of all your female friends
|
12
|
+
# Friend.count(:conditions => [ 'gender = ?', 'female' ])
|
13
|
+
#
|
14
|
+
# @example the count of all friends with an address (NULL values are not included)
|
15
|
+
# Friend.count(:address)
|
16
|
+
#
|
17
|
+
# @example the count of all friends with an address that are older then 18
|
18
|
+
# Friend.count(:address, :age.gt => 18)
|
19
|
+
#
|
20
|
+
# @example the count of all your female friends with an address
|
21
|
+
# Friend.count(:address, :conditions => [ 'gender = ?', 'female' ])
|
22
|
+
#
|
23
|
+
# @param property [Symbol] of the property you with to count (optional)
|
24
|
+
# @param opts [Hash, Symbol] the conditions
|
25
|
+
#
|
26
|
+
# @return [Integer] return the count given the conditions
|
27
|
+
#
|
28
|
+
# @api public
|
29
|
+
def count(*args)
|
30
|
+
query = args.last.kind_of?(Hash) ? args.pop : {}
|
31
|
+
property_name = args.first
|
32
|
+
|
33
|
+
if property_name
|
34
|
+
assert_kind_of 'property', property_by_name(property_name), Property
|
35
|
+
end
|
36
|
+
|
37
|
+
aggregate(query.merge(:fields => [ property_name ? property_name.count : :all.count ]))
|
38
|
+
end
|
39
|
+
|
40
|
+
# Get the lowest value of a property
|
41
|
+
#
|
42
|
+
# @example the age of the youngest friend
|
43
|
+
# Friend.min(:age)
|
44
|
+
#
|
45
|
+
# @example the age of the youngest female friend
|
46
|
+
# Friend.min(:age, :conditions => [ 'gender = ?', 'female' ])
|
47
|
+
#
|
48
|
+
# @param property [Symbol] the property you wish to get the lowest value of
|
49
|
+
# @param opts [Hash, Symbol] the conditions
|
50
|
+
#
|
51
|
+
# @return [Integer] return the lowest value of a property given the conditions
|
52
|
+
#
|
53
|
+
# @api public
|
54
|
+
def min(*args)
|
55
|
+
query = args.last.kind_of?(Hash) ? args.pop : {}
|
56
|
+
property_name = args.first
|
57
|
+
|
58
|
+
assert_property_type property_name, Integer, Float, BigDecimal, DateTime, Date, Time
|
59
|
+
|
60
|
+
aggregate(query.merge(:fields => [ property_name.min ]))
|
61
|
+
end
|
62
|
+
|
63
|
+
# Get the highest value of a property
|
64
|
+
#
|
65
|
+
# @example the age of the oldest friend
|
66
|
+
# Friend.max(:age)
|
67
|
+
#
|
68
|
+
# @example the age of the oldest female friend
|
69
|
+
# Friend.max(:age, :conditions => [ 'gender = ?', 'female' ])
|
70
|
+
#
|
71
|
+
# @param property [Symbol] the property you wish to get the highest value of
|
72
|
+
# @param opts [Hash, Symbol] the conditions
|
73
|
+
#
|
74
|
+
# @return [Integer] return the highest value of a property given the conditions
|
75
|
+
#
|
76
|
+
# @api public
|
77
|
+
def max(*args)
|
78
|
+
query = args.last.kind_of?(Hash) ? args.pop : {}
|
79
|
+
property_name = args.first
|
80
|
+
|
81
|
+
assert_property_type property_name, Integer, Float, BigDecimal, DateTime, Date, Time
|
82
|
+
|
83
|
+
aggregate(query.merge(:fields => [ property_name.max ]))
|
84
|
+
end
|
85
|
+
|
86
|
+
# Get the average value of a property
|
87
|
+
#
|
88
|
+
# @example the average age of all friends
|
89
|
+
# Friend.avg(:age)
|
90
|
+
#
|
91
|
+
# @example the average age of all female friends
|
92
|
+
# Friend.avg(:age, :conditions => [ 'gender = ?', 'female' ])
|
93
|
+
#
|
94
|
+
# @param property [Symbol] the property you wish to get the average value of
|
95
|
+
# @param opts [Hash, Symbol] the conditions
|
96
|
+
#
|
97
|
+
# @return [Integer] return the average value of a property given the conditions
|
98
|
+
#
|
99
|
+
# @api public
|
100
|
+
def avg(*args)
|
101
|
+
query = args.last.kind_of?(Hash) ? args.pop : {}
|
102
|
+
property_name = args.first
|
103
|
+
|
104
|
+
assert_property_type property_name, Integer, Float, BigDecimal
|
105
|
+
|
106
|
+
aggregate(query.merge(:fields => [ property_name.avg ]))
|
107
|
+
end
|
108
|
+
|
109
|
+
# Get the total value of a property
|
110
|
+
#
|
111
|
+
# @example the total age of all friends
|
112
|
+
# Friend.sum(:age)
|
113
|
+
#
|
114
|
+
# @example the total age of all female friends
|
115
|
+
# Friend.max(:age, :conditions => [ 'gender = ?', 'female' ])
|
116
|
+
#
|
117
|
+
# @param property [Symbol] the property you wish to get the total value of
|
118
|
+
# @param opts [Hash, Symbol] the conditions
|
119
|
+
#
|
120
|
+
# @return [Integer] return the total value of a property given the conditions
|
121
|
+
#
|
122
|
+
# @api public
|
123
|
+
def sum(*args)
|
124
|
+
query = args.last.kind_of?(Hash) ? args.pop : {}
|
125
|
+
property_name = args.first
|
126
|
+
|
127
|
+
assert_property_type property_name, Integer, Float, BigDecimal
|
128
|
+
|
129
|
+
aggregate(query.merge(:fields => [ property_name.sum ]))
|
130
|
+
end
|
131
|
+
|
132
|
+
# Perform aggregate queries
|
133
|
+
#
|
134
|
+
# @example the count of friends
|
135
|
+
# Friend.aggregate(:all.count)
|
136
|
+
#
|
137
|
+
# @example the minimum age, the maximum age and the total age of friends
|
138
|
+
# Friend.aggregate(:age.min, :age.max, :age.sum)
|
139
|
+
#
|
140
|
+
# @example the average age, grouped by gender
|
141
|
+
# Friend.aggregate(:age.avg, :fields => [ :gender ])
|
142
|
+
#
|
143
|
+
# @param aggregates [Symbol, ...] operators to aggregate with
|
144
|
+
# @params query [Hash] the conditions
|
145
|
+
#
|
146
|
+
# @return [Array,Numeric,DateTime,Date,Time] the results of the
|
147
|
+
# aggregate query
|
148
|
+
#
|
149
|
+
# @api public
|
150
|
+
def aggregate(*args)
|
151
|
+
query = args.last.kind_of?(Hash) ? args.pop : {}
|
152
|
+
|
153
|
+
query[:fields] ||= []
|
154
|
+
query[:fields] |= args
|
155
|
+
query[:fields].map! { |f| normalize_field(f) }
|
156
|
+
query[:order] ||= query[:fields].select { |p| p.kind_of?(Property) }
|
157
|
+
|
158
|
+
raise ArgumentError, 'query[:fields] must not be empty' if query[:fields].empty?
|
159
|
+
|
160
|
+
query = scoped_query(query)
|
161
|
+
|
162
|
+
if query.fields.any? { |p| p.kind_of?(Property) }
|
163
|
+
query.repository.aggregate(query.update(:unique => true))
|
164
|
+
else
|
165
|
+
query.repository.aggregate(query).first # only return one row
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
private
|
170
|
+
|
171
|
+
def assert_property_type(name, *types)
|
172
|
+
if name.nil?
|
173
|
+
raise ArgumentError, 'property name must not be nil'
|
174
|
+
end
|
175
|
+
|
176
|
+
type = property_by_name(name).type
|
177
|
+
|
178
|
+
unless types.include?(type)
|
179
|
+
raise ArgumentError, "#{name} must be #{types * ' or '}, but was #{type}"
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def normalize_field(field)
|
184
|
+
assert_kind_of 'field', field, Query::Operator, Symbol, Property
|
185
|
+
|
186
|
+
case field
|
187
|
+
when Query::Operator
|
188
|
+
if field.target == :all
|
189
|
+
field
|
190
|
+
else
|
191
|
+
field.class.new(property_by_name(field.target), field.operator)
|
192
|
+
end
|
193
|
+
when Symbol
|
194
|
+
property_by_name(field)
|
195
|
+
when Property
|
196
|
+
field
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
@@ -1,18 +1,11 @@
|
|
1
1
|
module DataMapper
|
2
2
|
class Collection
|
3
|
-
include
|
3
|
+
include AggregateFunctions
|
4
4
|
|
5
5
|
private
|
6
6
|
|
7
|
-
def
|
8
|
-
|
9
|
-
property_name = args.first
|
10
|
-
|
11
|
-
query = scoped_query(query)
|
12
|
-
repository = query.repository
|
13
|
-
property = properties[property_name] if property_name
|
14
|
-
|
15
|
-
yield repository, property, query
|
7
|
+
def property_by_name(property_name)
|
8
|
+
properties[property_name]
|
16
9
|
end
|
17
10
|
end
|
18
11
|
end
|
data/lib/dm-aggregates/model.rb
CHANGED
@@ -1,18 +1,11 @@
|
|
1
1
|
module DataMapper
|
2
2
|
module Model
|
3
|
-
include
|
3
|
+
include AggregateFunctions
|
4
4
|
|
5
5
|
private
|
6
6
|
|
7
|
-
def
|
8
|
-
|
9
|
-
property_name = args.first
|
10
|
-
|
11
|
-
query = scoped_query(query)
|
12
|
-
repository = query.repository
|
13
|
-
property = properties(repository.name)[property_name] if property_name
|
14
|
-
|
15
|
-
yield repository, property, query
|
7
|
+
def property_by_name(property_name)
|
8
|
+
properties(repository.name)[property_name]
|
16
9
|
end
|
17
10
|
end
|
18
11
|
end
|
@@ -1,23 +1,7 @@
|
|
1
1
|
module DataMapper
|
2
2
|
class Repository
|
3
|
-
def
|
4
|
-
adapter.
|
5
|
-
end
|
6
|
-
|
7
|
-
def min(property, query)
|
8
|
-
adapter.min(property, query)
|
9
|
-
end
|
10
|
-
|
11
|
-
def max(property, query)
|
12
|
-
adapter.max(property, query)
|
13
|
-
end
|
14
|
-
|
15
|
-
def avg(property, query)
|
16
|
-
adapter.avg(property, query)
|
17
|
-
end
|
18
|
-
|
19
|
-
def sum(property, query)
|
20
|
-
adapter.sum(property, query)
|
3
|
+
def aggregate(query)
|
4
|
+
adapter.aggregate(query)
|
21
5
|
end
|
22
6
|
end
|
23
7
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Symbol
|
2
|
+
def count
|
3
|
+
DataMapper::Query::Operator.new(self, :count)
|
4
|
+
end
|
5
|
+
|
6
|
+
def min
|
7
|
+
DataMapper::Query::Operator.new(self, :min)
|
8
|
+
end
|
9
|
+
|
10
|
+
def max
|
11
|
+
DataMapper::Query::Operator.new(self, :max)
|
12
|
+
end
|
13
|
+
|
14
|
+
def avg
|
15
|
+
DataMapper::Query::Operator.new(self, :avg)
|
16
|
+
end
|
17
|
+
|
18
|
+
def sum
|
19
|
+
DataMapper::Query::Operator.new(self, :sum)
|
20
|
+
end
|
21
|
+
end # class Symbol
|
@@ -5,35 +5,55 @@ if HAS_SQLITE3 || HAS_MYSQL || HAS_POSTGRES
|
|
5
5
|
describe 'DataMapper::Resource' do
|
6
6
|
before :all do
|
7
7
|
# A simplistic example, using with an Integer property
|
8
|
+
class Knight
|
9
|
+
include DataMapper::Resource
|
10
|
+
|
11
|
+
property :id, Serial
|
12
|
+
property :name, String
|
13
|
+
end
|
14
|
+
|
8
15
|
class Dragon
|
9
16
|
include DataMapper::Resource
|
10
|
-
|
11
|
-
property :
|
17
|
+
|
18
|
+
property :id, Serial
|
19
|
+
property :name, String
|
12
20
|
property :is_fire_breathing, TrueClass
|
13
|
-
property :toes_on_claw,
|
21
|
+
property :toes_on_claw, Integer
|
22
|
+
property :birth_at, DateTime
|
23
|
+
property :birth_on, Date
|
24
|
+
property :birth_time, Time
|
14
25
|
|
15
|
-
|
26
|
+
belongs_to :knight
|
16
27
|
end
|
17
28
|
|
18
|
-
|
19
|
-
Dragon.create(:name => 'Puff', :is_fire_breathing => true, :toes_on_claw => 4)
|
20
|
-
Dragon.create(:name => nil, :is_fire_breathing => true, :toes_on_claw => 5)
|
29
|
+
|
21
30
|
# A more complex example, with BigDecimal and Float properties
|
22
31
|
# Statistics taken from CIA World Factbook:
|
23
32
|
# https://www.cia.gov/library/publications/the-world-factbook/
|
24
33
|
class Country
|
25
34
|
include DataMapper::Resource
|
26
35
|
|
27
|
-
property :id,
|
28
|
-
property :name, String,
|
36
|
+
property :id, Serial
|
37
|
+
property :name, String, :nullable => false
|
29
38
|
property :population, Integer
|
30
39
|
property :birth_rate, Float, :precision => 4, :scale => 2
|
31
40
|
property :gold_reserve_tonnes, Float, :precision => 6, :scale => 2
|
32
41
|
property :gold_reserve_value, BigDecimal, :precision => 15, :scale => 1 # approx. value in USD
|
33
|
-
|
34
|
-
auto_migrate!(:default)
|
35
42
|
end
|
36
43
|
|
44
|
+
[ Dragon, Country, Knight ].each { |m| m.auto_migrate! }
|
45
|
+
|
46
|
+
@birth_at = DateTime.now
|
47
|
+
@birth_on = Date.parse(@birth_at.to_s)
|
48
|
+
@birth_time = Time.parse(@birth_at.to_s)
|
49
|
+
|
50
|
+
@chuck = Knight.create( :name => 'Chuck' )
|
51
|
+
@larry = Knight.create( :name => 'Larry')
|
52
|
+
|
53
|
+
Dragon.create(:name => 'George', :is_fire_breathing => false, :toes_on_claw => 3, :birth_at => @birth_at, :birth_on => @birth_on, :birth_time => @birth_time, :knight => @chuck )
|
54
|
+
Dragon.create(:name => 'Puff', :is_fire_breathing => true, :toes_on_claw => 4, :birth_at => @birth_at, :birth_on => @birth_on, :birth_time => @birth_time, :knight => @larry )
|
55
|
+
Dragon.create(:name => nil, :is_fire_breathing => true, :toes_on_claw => 5, :birth_at => nil, :birth_on => nil, :birth_time => nil)
|
56
|
+
|
37
57
|
gold_kilo_price = 277738.70
|
38
58
|
@gold_tonne_price = gold_kilo_price * 10000
|
39
59
|
|
@@ -144,6 +164,21 @@ if HAS_SQLITE3 || HAS_MYSQL || HAS_POSTGRES
|
|
144
164
|
target(Country, target_type).min(:gold_reserve_value).should == BigDecimal('1217050983400.0')
|
145
165
|
end
|
146
166
|
|
167
|
+
it 'should provide the lowest value of a DateTime property' do
|
168
|
+
target(Dragon, target_type).min(:birth_at).should be_kind_of(DateTime)
|
169
|
+
target(Dragon, target_type).min(:birth_at).to_s.should == @birth_at.to_s
|
170
|
+
end
|
171
|
+
|
172
|
+
it 'should provide the lowest value of a Date property' do
|
173
|
+
target(Dragon, target_type).min(:birth_on).should be_kind_of(Date)
|
174
|
+
target(Dragon, target_type).min(:birth_on).to_s.should == @birth_on.to_s
|
175
|
+
end
|
176
|
+
|
177
|
+
it 'should provide the lowest value of a Time property' do
|
178
|
+
target(Dragon, target_type).min(:birth_time).should be_kind_of(Time)
|
179
|
+
target(Dragon, target_type).min(:birth_time).to_s.should == @birth_time.to_s
|
180
|
+
end
|
181
|
+
|
147
182
|
it 'should provide the lowest value when conditions provided' do
|
148
183
|
target(Dragon, target_type).min(:toes_on_claw, :is_fire_breathing => true).should == 4
|
149
184
|
target(Dragon, target_type).min(:toes_on_claw, :is_fire_breathing => false).should == 3
|
@@ -174,6 +209,21 @@ if HAS_SQLITE3 || HAS_MYSQL || HAS_POSTGRES
|
|
174
209
|
target(Country, target_type).max(:gold_reserve_value).should == BigDecimal('22589877164500.0')
|
175
210
|
end
|
176
211
|
|
212
|
+
it 'should provide the highest value of a DateTime property' do
|
213
|
+
target(Dragon, target_type).min(:birth_at).should be_kind_of(DateTime)
|
214
|
+
target(Dragon, target_type).min(:birth_at).to_s.should == @birth_at.to_s
|
215
|
+
end
|
216
|
+
|
217
|
+
it 'should provide the highest value of a Date property' do
|
218
|
+
target(Dragon, target_type).min(:birth_on).should be_kind_of(Date)
|
219
|
+
target(Dragon, target_type).min(:birth_on).to_s.should == @birth_on.to_s
|
220
|
+
end
|
221
|
+
|
222
|
+
it 'should provide the highest value of a Time property' do
|
223
|
+
target(Dragon, target_type).min(:birth_time).should be_kind_of(Time)
|
224
|
+
target(Dragon, target_type).min(:birth_time).to_s.should == @birth_time.to_s
|
225
|
+
end
|
226
|
+
|
177
227
|
it 'should provide the highest value when conditions provided' do
|
178
228
|
target(Dragon, target_type).max(:toes_on_claw, :is_fire_breathing => true).should == 5
|
179
229
|
target(Dragon, target_type).max(:toes_on_claw, :is_fire_breathing => false).should == 3
|
@@ -247,6 +297,35 @@ if HAS_SQLITE3 || HAS_MYSQL || HAS_POSTGRES
|
|
247
297
|
end
|
248
298
|
end
|
249
299
|
end
|
250
|
-
|
300
|
+
|
301
|
+
describe ".aggregate on a #{target_type}" do
|
302
|
+
describe 'with no arguments' do
|
303
|
+
it 'should raise an error' do
|
304
|
+
lambda { target(Dragon, target_type).aggregate }.should raise_error(ArgumentError)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
describe 'with only aggregate fields specified' do
|
309
|
+
it 'should provide aggregate results' do
|
310
|
+
results = target(Dragon, target_type).aggregate(:all.count, :name.count, :toes_on_claw.min, :toes_on_claw.max, :toes_on_claw.avg, :toes_on_claw.sum)
|
311
|
+
results.should == [ 3, 2, 3, 5, 4.0, 12 ]
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
describe 'with aggregate fields and a property to group by' do
|
316
|
+
it 'should provide aggregate results' do
|
317
|
+
results = target(Dragon, target_type).aggregate(:all.count, :name.count, :toes_on_claw.min, :toes_on_claw.max, :toes_on_claw.avg, :toes_on_claw.sum, :is_fire_breathing)
|
318
|
+
results.should == [ [ 1, 1, 3, 3, 3.0, 3, false ], [ 2, 1, 4, 5, 4.5, 9, true ] ]
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
describe "query path issue" do
|
324
|
+
it "should not break when a query path is specified" do
|
325
|
+
dragon = Dragon.first(Dragon.knight.name => 'Chuck')
|
326
|
+
dragon.name.should == 'George'
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
251
330
|
end
|
252
331
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -8,7 +8,7 @@ def load_driver(name, default_uri)
|
|
8
8
|
lib = "do_#{name}"
|
9
9
|
|
10
10
|
begin
|
11
|
-
gem lib, '=0.9.
|
11
|
+
gem lib, '=0.9.3'
|
12
12
|
require lib
|
13
13
|
DataMapper.setup(name, ENV["#{name.to_s.upcase}_SPEC_URI"] || default_uri)
|
14
14
|
DataMapper::Repository.adapters[:default] = DataMapper::Repository.adapters[name]
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dm-aggregates
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Foy Savas
|
@@ -9,47 +9,64 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-
|
12
|
+
date: 2008-07-24 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: dm-core
|
17
|
+
type: :runtime
|
17
18
|
version_requirement:
|
18
19
|
version_requirements: !ruby/object:Gem::Requirement
|
19
20
|
requirements:
|
20
21
|
- - "="
|
21
22
|
- !ruby/object:Gem::Version
|
22
|
-
version: 0.9.
|
23
|
+
version: 0.9.3
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: hoe
|
27
|
+
type: :development
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.7.0
|
23
34
|
version:
|
24
35
|
description: DataMapper plugin providing support for aggregates, functions on collections and datasets
|
25
|
-
email:
|
36
|
+
email:
|
37
|
+
- foysavas@gmail.com
|
26
38
|
executables: []
|
27
39
|
|
28
40
|
extensions: []
|
29
41
|
|
30
42
|
extra_rdoc_files:
|
31
|
-
- README
|
43
|
+
- README.txt
|
32
44
|
- LICENSE
|
33
45
|
- TODO
|
34
46
|
files:
|
47
|
+
- History.txt
|
48
|
+
- LICENSE
|
49
|
+
- Manifest.txt
|
50
|
+
- README.txt
|
51
|
+
- Rakefile
|
52
|
+
- TODO
|
53
|
+
- lib/dm-aggregates.rb
|
35
54
|
- lib/dm-aggregates/adapters/data_objects_adapter.rb
|
55
|
+
- lib/dm-aggregates/aggregate_functions.rb
|
36
56
|
- lib/dm-aggregates/collection.rb
|
37
|
-
- lib/dm-aggregates/functions.rb
|
38
57
|
- lib/dm-aggregates/model.rb
|
39
58
|
- lib/dm-aggregates/repository.rb
|
40
|
-
- lib/dm-aggregates.rb
|
59
|
+
- lib/dm-aggregates/support/symbol.rb
|
60
|
+
- lib/dm-aggregates/version.rb
|
41
61
|
- spec/integration/aggregates_spec.rb
|
42
|
-
- spec/spec_helper.rb
|
43
62
|
- spec/spec.opts
|
44
|
-
-
|
45
|
-
- README
|
46
|
-
- LICENSE
|
47
|
-
- TODO
|
63
|
+
- spec/spec_helper.rb
|
48
64
|
has_rdoc: true
|
49
65
|
homepage: http://github.com/sam/dm-more/tree/master/dm-aggregates
|
50
66
|
post_install_message:
|
51
|
-
rdoc_options:
|
52
|
-
|
67
|
+
rdoc_options:
|
68
|
+
- --main
|
69
|
+
- README.txt
|
53
70
|
require_paths:
|
54
71
|
- lib
|
55
72
|
required_ruby_version: !ruby/object:Gem::Requirement
|
@@ -66,8 +83,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
66
83
|
version:
|
67
84
|
requirements: []
|
68
85
|
|
69
|
-
rubyforge_project:
|
70
|
-
rubygems_version: 1.0
|
86
|
+
rubyforge_project: datamapper
|
87
|
+
rubygems_version: 1.2.0
|
71
88
|
signing_key:
|
72
89
|
specification_version: 2
|
73
90
|
summary: DataMapper plugin providing support for aggregates, functions on collections and datasets
|
@@ -1,118 +0,0 @@
|
|
1
|
-
module DataMapper
|
2
|
-
module Aggregates
|
3
|
-
|
4
|
-
# Count results (given the conditions)
|
5
|
-
#
|
6
|
-
# ==== Example
|
7
|
-
# Friend.count # returns count of all friends
|
8
|
-
# Friend.count(:age.gt => 18) # returns count of all friends older then 18
|
9
|
-
# Friend.count(:conditions => [ 'gender = ?', 'female' ]) # returns count of all your female friends
|
10
|
-
# Friend.count(:address) # returns count of all friends with an address (NULL values are not included)
|
11
|
-
# Friend.count(:address, :age.gt => 18) # returns count of all friends with an address that are older then 18
|
12
|
-
# Friend.count(:address, :conditions => [ 'gender = ?', 'female' ]) # returns count of all your female friends with an address
|
13
|
-
#
|
14
|
-
# ==== Parameters
|
15
|
-
# property<Symbol>:: of the property you with to count (optional)
|
16
|
-
# opts<Hash, Symbol>:: of the conditions
|
17
|
-
#
|
18
|
-
# ==== Returns
|
19
|
-
# <Integer>:: with the count of the results
|
20
|
-
#---
|
21
|
-
# @public
|
22
|
-
def count(*args)
|
23
|
-
with_repository_and_property(*args) do |repository,property,query|
|
24
|
-
repository.count(property, query.merge(:limit => 1))
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
# Get the lowest value of a property
|
29
|
-
#
|
30
|
-
# ==== Example
|
31
|
-
# Friend.min(:age) # returns the age of the youngest friend
|
32
|
-
# Friend.min(:age, :conditions => [ 'gender = ?', 'female' ]) # returns the age of the youngest female friends
|
33
|
-
#
|
34
|
-
# ==== Parameters
|
35
|
-
# property<Symbol>:: the property you wish to get the lowest value of
|
36
|
-
# opts<Hash, Symbol>:: the conditions
|
37
|
-
#
|
38
|
-
# ==== Returns
|
39
|
-
# <Integer>:: return the lowest value of a property given the conditions
|
40
|
-
#---
|
41
|
-
# @public
|
42
|
-
def min(*args)
|
43
|
-
with_repository_and_property(*args) do |repository,property,query|
|
44
|
-
check_property_is_number(property)
|
45
|
-
repository.min(property, query.merge(:limit => 1))
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
# Get the highest value of a property
|
50
|
-
#
|
51
|
-
# ==== Example
|
52
|
-
# Friend.max(:age) # returns the age of the oldest friend
|
53
|
-
# Friend.max(:age, :conditions => [ 'gender = ?', 'female' ]) # returns the age of the oldest female friends
|
54
|
-
#
|
55
|
-
# ==== Parameters
|
56
|
-
# property<Symbol>:: the property you wish to get the highest value of
|
57
|
-
# opts<Hash, Symbol>:: the conditions
|
58
|
-
#
|
59
|
-
# ==== Returns
|
60
|
-
# <Integer>:: return the highest value of a property given the conditions
|
61
|
-
#---
|
62
|
-
# @public
|
63
|
-
def max(*args)
|
64
|
-
with_repository_and_property(*args) do |repository,property,query|
|
65
|
-
check_property_is_number(property)
|
66
|
-
repository.max(property, query.merge(:limit => 1))
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
# Get the average value of a property
|
71
|
-
#
|
72
|
-
# ==== Example
|
73
|
-
# Friend.avg(:age) # returns the average age of friends
|
74
|
-
# Friend.avg(:age, :conditions => [ 'gender = ?', 'female' ]) # returns the average age of the female friends
|
75
|
-
#
|
76
|
-
# ==== Parameters
|
77
|
-
# property<Symbol>:: the property you wish to get the average value of
|
78
|
-
# opts<Hash, Symbol>:: the conditions
|
79
|
-
#
|
80
|
-
# ==== Returns
|
81
|
-
# <Integer>:: return the average value of a property given the conditions
|
82
|
-
#---
|
83
|
-
# @public
|
84
|
-
def avg(*args)
|
85
|
-
with_repository_and_property(*args) do |repository,property,query|
|
86
|
-
check_property_is_number(property)
|
87
|
-
repository.avg(property, query.merge(:limit => 1))
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
# Get the total value of a property
|
92
|
-
#
|
93
|
-
# ==== Example
|
94
|
-
# Friend.sum(:age) # returns total age of all friends
|
95
|
-
# Friend.max(:age, :conditions => [ 'gender = ?', 'female' ]) # returns the total age of all female friends
|
96
|
-
#
|
97
|
-
# ==== Parameters
|
98
|
-
# property<Symbol>:: the property you wish to get the total value of
|
99
|
-
# opts<Hash, Symbol>:: the conditions
|
100
|
-
#
|
101
|
-
# ==== Returns
|
102
|
-
# <Integer>:: return the total value of a property given the conditions
|
103
|
-
#---
|
104
|
-
# @public
|
105
|
-
def sum(*args)
|
106
|
-
with_repository_and_property(*args) do |repository,property,query|
|
107
|
-
check_property_is_number(property)
|
108
|
-
repository.sum(property, query.merge(:limit => 1))
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
private
|
113
|
-
|
114
|
-
def check_property_is_number(property)
|
115
|
-
raise ArgumentError, "+property+ should be an Integer, Float or BigDecimal, but was #{property.nil? ? 'nil' : property.type.class}" unless property && [ Integer, Float, BigDecimal ].include?(property.type)
|
116
|
-
end
|
117
|
-
end
|
118
|
-
end
|