dm-aggregates 0.9.2 → 0.9.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|