dm-aggregates 0.9.2
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/LICENSE +20 -0
- data/README +45 -0
- data/Rakefile +65 -0
- data/TODO +6 -0
- data/lib/dm-aggregates.rb +12 -0
- data/lib/dm-aggregates/adapters/data_objects_adapter.rb +72 -0
- data/lib/dm-aggregates/collection.rb +18 -0
- data/lib/dm-aggregates/functions.rb +118 -0
- data/lib/dm-aggregates/model.rb +18 -0
- data/lib/dm-aggregates/repository.rb +23 -0
- data/spec/integration/aggregates_spec.rb +252 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +26 -0
- metadata +75 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 Foy Savas
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
dm-aggregates
|
2
|
+
=============
|
3
|
+
|
4
|
+
DataMapper plugin providing support for aggregates, functions on collections and datasets.
|
5
|
+
|
6
|
+
It provides the following functions:
|
7
|
+
|
8
|
+
== count
|
9
|
+
|
10
|
+
Count results (given the conditions)
|
11
|
+
|
12
|
+
Friend.count # returns count of all friends
|
13
|
+
Friend.count(:age.gt => 18) # returns count of all friends older then 18
|
14
|
+
Friend.count(:conditions => [ 'gender = ?', 'female' ]) # returns count of all your female friends
|
15
|
+
Friend.count(:address) # returns count of all friends with an address (NULL values are not included)
|
16
|
+
Friend.count(:address, :age.gt => 18) # returns count of all friends with an address that are older then 18
|
17
|
+
Friend.count(:address, :conditions => [ 'gender = ?', 'female' ]) # returns count of all your female friends with an address
|
18
|
+
|
19
|
+
== min
|
20
|
+
|
21
|
+
Get the lowest value of a property
|
22
|
+
|
23
|
+
Friend.min(:age) # returns the age of the youngest friend
|
24
|
+
Friend.min(:age, :conditions => [ 'gender = ?', 'female' ]) # returns the age of the youngest female friends
|
25
|
+
|
26
|
+
== max
|
27
|
+
|
28
|
+
Get the highest value of a property
|
29
|
+
|
30
|
+
Friend.max(:age) # returns the age of the oldest friend
|
31
|
+
Friend.max(:age, :conditions => [ 'gender = ?', 'female' ]) # returns the age of the oldest female friends
|
32
|
+
|
33
|
+
== avg
|
34
|
+
|
35
|
+
Get the average value of a property
|
36
|
+
|
37
|
+
Friend.avg(:age) # returns the average age of friends
|
38
|
+
Friend.avg(:age, :conditions => [ 'gender = ?', 'female' ]) # returns the average age of the female friends
|
39
|
+
|
40
|
+
== sum
|
41
|
+
|
42
|
+
Get the total value of a property
|
43
|
+
|
44
|
+
Friend.sum(:age) # returns total age of all friends
|
45
|
+
Friend.max(:age, :conditions => [ 'gender = ?', 'female' ]) # returns the total age of all female friends
|
data/Rakefile
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'spec'
|
3
|
+
require 'rake/clean'
|
4
|
+
require 'rake/gempackagetask'
|
5
|
+
require 'spec/rake/spectask'
|
6
|
+
require 'pathname'
|
7
|
+
|
8
|
+
CLEAN.include '{log,pkg}/'
|
9
|
+
|
10
|
+
spec = Gem::Specification.new do |s|
|
11
|
+
s.name = 'dm-aggregates'
|
12
|
+
s.version = '0.9.2'
|
13
|
+
s.platform = Gem::Platform::RUBY
|
14
|
+
s.has_rdoc = true
|
15
|
+
s.extra_rdoc_files = %w[ README LICENSE TODO ]
|
16
|
+
s.summary = 'DataMapper plugin providing support for aggregates, functions on collections and datasets'
|
17
|
+
s.description = s.summary
|
18
|
+
s.author = 'Foy Savas'
|
19
|
+
s.email = 'foysavas@gmail.com'
|
20
|
+
s.homepage = 'http://github.com/sam/dm-more/tree/master/dm-aggregates'
|
21
|
+
s.require_path = 'lib'
|
22
|
+
s.files = FileList[ '{lib,spec}/**/*.rb', 'spec/spec.opts', 'Rakefile', *s.extra_rdoc_files ]
|
23
|
+
s.add_dependency('dm-core', "=#{s.version}")
|
24
|
+
end
|
25
|
+
|
26
|
+
task :default => [ :spec ]
|
27
|
+
|
28
|
+
WIN32 = (RUBY_PLATFORM =~ /win32|mingw|cygwin/) rescue nil
|
29
|
+
SUDO = WIN32 ? '' : ('sudo' unless ENV['SUDOLESS'])
|
30
|
+
|
31
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
32
|
+
pkg.gem_spec = spec
|
33
|
+
end
|
34
|
+
|
35
|
+
desc "Install #{spec.name} #{spec.version} (default ruby)"
|
36
|
+
task :install => [ :package ] do
|
37
|
+
sh "#{SUDO} gem install --local pkg/#{spec.name}-#{spec.version} --no-update-sources", :verbose => false
|
38
|
+
end
|
39
|
+
|
40
|
+
desc "Uninstall #{spec.name} #{spec.version} (default ruby)"
|
41
|
+
task :uninstall => [ :clobber ] do
|
42
|
+
sh "#{SUDO} gem uninstall #{spec.name} -v#{spec.version} -I -x", :verbose => false
|
43
|
+
end
|
44
|
+
|
45
|
+
namespace :jruby do
|
46
|
+
desc "Install #{spec.name} #{spec.version} with JRuby"
|
47
|
+
task :install => [ :package ] do
|
48
|
+
sh %{#{SUDO} jruby -S gem install --local pkg/#{spec.name}-#{spec.version} --no-update-sources}, :verbose => false
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
desc 'Run specifications'
|
53
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
54
|
+
t.spec_opts << '--options' << 'spec/spec.opts' if File.exists?('spec/spec.opts')
|
55
|
+
t.spec_files = Pathname.glob(Pathname.new(__FILE__).dirname + 'spec/**/*_spec.rb')
|
56
|
+
|
57
|
+
begin
|
58
|
+
t.rcov = ENV.has_key?('NO_RCOV') ? ENV['NO_RCOV'] != 'true' : true
|
59
|
+
t.rcov_opts << '--exclude' << 'spec'
|
60
|
+
t.rcov_opts << '--text-summary'
|
61
|
+
t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
|
62
|
+
rescue Exception
|
63
|
+
# rcov not installed
|
64
|
+
end
|
65
|
+
end
|
data/TODO
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
gem 'dm-core', '=0.9.2'
|
4
|
+
require 'dm-core'
|
5
|
+
|
6
|
+
dir = Pathname(__FILE__).dirname.expand_path / 'dm-aggregates'
|
7
|
+
|
8
|
+
require dir / 'functions'
|
9
|
+
require dir / 'model'
|
10
|
+
require dir / 'repository'
|
11
|
+
require dir / 'collection'
|
12
|
+
require dir / 'adapters' / 'data_objects_adapter'
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Adapters
|
3
|
+
class DataObjectsAdapter
|
4
|
+
def count(property, query)
|
5
|
+
query(aggregate_read_statement(:count, property, query), *query.bind_values).first
|
6
|
+
end
|
7
|
+
|
8
|
+
def min(property, query)
|
9
|
+
min = query(aggregate_read_statement(:min, property, query), *query.bind_values).first
|
10
|
+
property.typecast(min)
|
11
|
+
end
|
12
|
+
|
13
|
+
def max(property, query)
|
14
|
+
max = query(aggregate_read_statement(:max, property, query), *query.bind_values).first
|
15
|
+
property.typecast(max)
|
16
|
+
end
|
17
|
+
|
18
|
+
def avg(property, query)
|
19
|
+
avg = query(aggregate_read_statement(:avg, property, query), *query.bind_values).first
|
20
|
+
property.type == Integer ? avg.to_f : property.typecast(avg)
|
21
|
+
end
|
22
|
+
|
23
|
+
def sum(property, query)
|
24
|
+
sum = query(aggregate_read_statement(:sum, property, query), *query.bind_values).first
|
25
|
+
property.typecast(sum)
|
26
|
+
end
|
27
|
+
|
28
|
+
module SQL
|
29
|
+
private
|
30
|
+
|
31
|
+
def aggregate_read_statement(aggregate_function, property, query)
|
32
|
+
statement = "SELECT #{aggregate_field_statement(query.repository, aggregate_function, property, query.links.any?)}"
|
33
|
+
statement << " FROM #{quote_table_name(query.model.storage_name(query.repository.name))}"
|
34
|
+
statement << links_statement(query) if query.links.any?
|
35
|
+
statement << " WHERE #{conditions_statement(query)}" if query.conditions.any?
|
36
|
+
|
37
|
+
# TODO: when GROUP BY support added, uncomment this, and (by default) have
|
38
|
+
# it sort on the non-aggregate fields being SELECTed
|
39
|
+
#statement << " ORDER BY #{order_statement(query)}" if query.order.any?
|
40
|
+
|
41
|
+
statement << " LIMIT #{quote_column_value(query.limit)}" if query.limit
|
42
|
+
statement << " OFFSET #{quote_column_value(query.offset)}" if query.offset && query.offset > 0
|
43
|
+
statement
|
44
|
+
rescue => e
|
45
|
+
DataMapper.logger.error("QUERY INVALID: #{query.inspect} (#{e})")
|
46
|
+
raise e
|
47
|
+
end
|
48
|
+
|
49
|
+
def aggregate_field_statement(repository, aggregate_function, property, qualify)
|
50
|
+
column_name = if aggregate_function == :count && property.nil?
|
51
|
+
'*'
|
52
|
+
else
|
53
|
+
property_to_column_name(repository, property, qualify)
|
54
|
+
end
|
55
|
+
|
56
|
+
function_name = case aggregate_function
|
57
|
+
when :count then 'COUNT'
|
58
|
+
when :min then 'MIN'
|
59
|
+
when :max then 'MAX'
|
60
|
+
when :avg then 'AVG'
|
61
|
+
when :sum then 'SUM'
|
62
|
+
else raise "Invalid aggregate function: #{aggregate_function.inspect}"
|
63
|
+
end
|
64
|
+
|
65
|
+
"#{function_name}(#{column_name})"
|
66
|
+
end
|
67
|
+
end # module SQL
|
68
|
+
|
69
|
+
include SQL
|
70
|
+
end # class DataObjectsAdapter
|
71
|
+
end # module Adapters
|
72
|
+
end # module DataMapper
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module DataMapper
|
2
|
+
class Collection
|
3
|
+
include Aggregates
|
4
|
+
|
5
|
+
private
|
6
|
+
|
7
|
+
def with_repository_and_property(*args, &block)
|
8
|
+
query = args.last.respond_to?(:merge) ? args.pop : {}
|
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
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,118 @@
|
|
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
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Model
|
3
|
+
include Aggregates
|
4
|
+
|
5
|
+
private
|
6
|
+
|
7
|
+
def with_repository_and_property(*args, &block)
|
8
|
+
query = args.last.respond_to?(:merge) ? args.pop : {}
|
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
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module DataMapper
|
2
|
+
class Repository
|
3
|
+
def count(property, query)
|
4
|
+
adapter.count(property, query)
|
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)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,252 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require Pathname(__FILE__).dirname.expand_path.parent + 'spec_helper'
|
3
|
+
|
4
|
+
if HAS_SQLITE3 || HAS_MYSQL || HAS_POSTGRES
|
5
|
+
describe 'DataMapper::Resource' do
|
6
|
+
before :all do
|
7
|
+
# A simplistic example, using with an Integer property
|
8
|
+
class Dragon
|
9
|
+
include DataMapper::Resource
|
10
|
+
property :id, Serial
|
11
|
+
property :name, String
|
12
|
+
property :is_fire_breathing, TrueClass
|
13
|
+
property :toes_on_claw, Integer
|
14
|
+
|
15
|
+
auto_migrate!(:default)
|
16
|
+
end
|
17
|
+
|
18
|
+
Dragon.create(:name => 'George', :is_fire_breathing => false, :toes_on_claw => 3)
|
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)
|
21
|
+
# A more complex example, with BigDecimal and Float properties
|
22
|
+
# Statistics taken from CIA World Factbook:
|
23
|
+
# https://www.cia.gov/library/publications/the-world-factbook/
|
24
|
+
class Country
|
25
|
+
include DataMapper::Resource
|
26
|
+
|
27
|
+
property :id, Integer, :serial => true
|
28
|
+
property :name, String, :nullable => false
|
29
|
+
property :population, Integer
|
30
|
+
property :birth_rate, Float, :precision => 4, :scale => 2
|
31
|
+
property :gold_reserve_tonnes, Float, :precision => 6, :scale => 2
|
32
|
+
property :gold_reserve_value, BigDecimal, :precision => 15, :scale => 1 # approx. value in USD
|
33
|
+
|
34
|
+
auto_migrate!(:default)
|
35
|
+
end
|
36
|
+
|
37
|
+
gold_kilo_price = 277738.70
|
38
|
+
@gold_tonne_price = gold_kilo_price * 10000
|
39
|
+
|
40
|
+
Country.create(:name => 'China',
|
41
|
+
:population => 1330044605,
|
42
|
+
:birth_rate => 13.71,
|
43
|
+
:gold_reserve_tonnes => 600.0,
|
44
|
+
:gold_reserve_value => 600.0 * @gold_tonne_price) # 32150000
|
45
|
+
Country.create(:name => 'United States',
|
46
|
+
:population => 303824646,
|
47
|
+
:birth_rate => 14.18,
|
48
|
+
:gold_reserve_tonnes => 8133.5,
|
49
|
+
:gold_reserve_value => 8133.5 * @gold_tonne_price)
|
50
|
+
Country.create(:name => 'Brazil',
|
51
|
+
:population => 191908598,
|
52
|
+
:birth_rate => 16.04,
|
53
|
+
:gold_reserve_tonnes => nil) # example of no stats available
|
54
|
+
Country.create(:name => 'Russia',
|
55
|
+
:population => 140702094,
|
56
|
+
:birth_rate => 11.03,
|
57
|
+
:gold_reserve_tonnes => 438.2,
|
58
|
+
:gold_reserve_value => 438.2 * @gold_tonne_price)
|
59
|
+
Country.create(:name => 'Japan',
|
60
|
+
:population => 127288419,
|
61
|
+
:birth_rate => 7.87,
|
62
|
+
:gold_reserve_tonnes => 765.2,
|
63
|
+
:gold_reserve_value => 765.2 * @gold_tonne_price)
|
64
|
+
Country.create(:name => 'Mexico',
|
65
|
+
:population => 109955400,
|
66
|
+
:birth_rate => 20.04,
|
67
|
+
:gold_reserve_tonnes => nil) # example of no stats available
|
68
|
+
Country.create(:name => 'Germany',
|
69
|
+
:population => 82369548,
|
70
|
+
:birth_rate => 8.18,
|
71
|
+
:gold_reserve_tonnes => 3417.4,
|
72
|
+
:gold_reserve_value => 3417.4 * @gold_tonne_price)
|
73
|
+
|
74
|
+
@approx_by = 0.000001
|
75
|
+
end
|
76
|
+
|
77
|
+
def target(klass, target_type)
|
78
|
+
target_type == :collection ? klass.all : klass
|
79
|
+
end
|
80
|
+
|
81
|
+
[ :model, :collection ].each do |target_type|
|
82
|
+
describe ".count on a #{target_type}" do
|
83
|
+
describe 'with no arguments' do
|
84
|
+
it 'should count the results' do
|
85
|
+
target(Dragon, target_type).count.should == 3
|
86
|
+
|
87
|
+
target(Country, target_type).count.should == 7
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'should count the results with conditions having operators' do
|
91
|
+
target(Dragon, target_type).count(:toes_on_claw.gt => 3).should == 2
|
92
|
+
|
93
|
+
target(Country, target_type).count(:birth_rate.lt => 12).should == 3
|
94
|
+
target(Country, target_type).count(:population.gt => 1000000000).should == 1
|
95
|
+
target(Country, target_type).count(:population.gt => 2000000000).should == 0
|
96
|
+
target(Country, target_type).count(:population.lt => 10).should == 0
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'should count the results with raw conditions' do
|
100
|
+
dragon_statement = 'is_fire_breathing = ?'
|
101
|
+
target(Dragon, target_type).count(:conditions => [ dragon_statement, false ]).should == 1
|
102
|
+
target(Dragon, target_type).count(:conditions => [ dragon_statement, true ]).should == 2
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe 'with a property name' do
|
107
|
+
it 'should count the results' do
|
108
|
+
target(Dragon, target_type).count(:name).should == 2
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'should count the results with conditions having operators' do
|
112
|
+
target(Dragon, target_type).count(:name, :toes_on_claw.gt => 3).should == 1
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'should count the results with raw conditions' do
|
116
|
+
statement = 'is_fire_breathing = ?'
|
117
|
+
target(Dragon, target_type).count(:name, :conditions => [ statement, false ]).should == 1
|
118
|
+
target(Dragon, target_type).count(:name, :conditions => [ statement, true ]).should == 1
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe ".min on a #{target_type}" do
|
124
|
+
describe 'with no arguments' do
|
125
|
+
it 'should raise an error' do
|
126
|
+
lambda { target(Dragon, target_type).min }.should raise_error(ArgumentError)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe 'with a property name' do
|
131
|
+
it 'should provide the lowest value of an Integer property' do
|
132
|
+
target(Dragon, target_type).min(:toes_on_claw).should == 3
|
133
|
+
target(Country, target_type).min(:population).should == 82369548
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'should provide the lowest value of a Float property' do
|
137
|
+
target(Country, target_type).min(:birth_rate).should be_kind_of(Float)
|
138
|
+
target(Country, target_type).min(:birth_rate).should >= 7.87 - @approx_by # approx match
|
139
|
+
target(Country, target_type).min(:birth_rate).should <= 7.87 + @approx_by # approx match
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'should provide the lowest value of a BigDecimal property' do
|
143
|
+
target(Country, target_type).min(:gold_reserve_value).should be_kind_of(BigDecimal)
|
144
|
+
target(Country, target_type).min(:gold_reserve_value).should == BigDecimal('1217050983400.0')
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'should provide the lowest value when conditions provided' do
|
148
|
+
target(Dragon, target_type).min(:toes_on_claw, :is_fire_breathing => true).should == 4
|
149
|
+
target(Dragon, target_type).min(:toes_on_claw, :is_fire_breathing => false).should == 3
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
describe ".max on a #{target_type}" do
|
155
|
+
describe 'with no arguments' do
|
156
|
+
it 'should raise an error' do
|
157
|
+
lambda { target(Dragon, target_type).max }.should raise_error(ArgumentError)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
describe 'with a property name' do
|
162
|
+
it 'should provide the highest value of an Integer property' do
|
163
|
+
target(Dragon, target_type).max(:toes_on_claw).should == 5
|
164
|
+
target(Country, target_type).max(:population).should == 1330044605
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'should provide the highest value of a Float property' do
|
168
|
+
target(Country, target_type).max(:birth_rate).should be_kind_of(Float)
|
169
|
+
target(Country, target_type).max(:birth_rate).should >= 20.04 - @approx_by # approx match
|
170
|
+
target(Country, target_type).max(:birth_rate).should <= 20.04 + @approx_by # approx match
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'should provide the highest value of a BigDecimal property' do
|
174
|
+
target(Country, target_type).max(:gold_reserve_value).should == BigDecimal('22589877164500.0')
|
175
|
+
end
|
176
|
+
|
177
|
+
it 'should provide the highest value when conditions provided' do
|
178
|
+
target(Dragon, target_type).max(:toes_on_claw, :is_fire_breathing => true).should == 5
|
179
|
+
target(Dragon, target_type).max(:toes_on_claw, :is_fire_breathing => false).should == 3
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
describe ".avg on a #{target_type}" do
|
185
|
+
describe 'with no arguments' do
|
186
|
+
it 'should raise an error' do
|
187
|
+
lambda { target(Dragon, target_type).avg }.should raise_error(ArgumentError)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
describe 'with a property name' do
|
192
|
+
it 'should provide the average value of an Integer property' do
|
193
|
+
target(Dragon, target_type).avg(:toes_on_claw).should be_kind_of(Float)
|
194
|
+
target(Dragon, target_type).avg(:toes_on_claw).should == 4.0
|
195
|
+
end
|
196
|
+
|
197
|
+
it 'should provide the average value of a Float property' do
|
198
|
+
mean_birth_rate = (13.71 + 14.18 + 16.04 + 11.03 + 7.87 + 20.04 + 8.18) / 7
|
199
|
+
target(Country, target_type).avg(:birth_rate).should be_kind_of(Float)
|
200
|
+
target(Country, target_type).avg(:birth_rate).should >= mean_birth_rate - @approx_by # approx match
|
201
|
+
target(Country, target_type).avg(:birth_rate).should <= mean_birth_rate + @approx_by # approx match
|
202
|
+
end
|
203
|
+
|
204
|
+
it 'should provide the average value of a BigDecimal property' do
|
205
|
+
mean_gold_reserve_value = ((600.0 + 8133.50 + 438.20 + 765.20 + 3417.40) * @gold_tonne_price) / 5
|
206
|
+
target(Country, target_type).avg(:gold_reserve_value).should be_kind_of(BigDecimal)
|
207
|
+
target(Country, target_type).avg(:gold_reserve_value).should == BigDecimal(mean_gold_reserve_value.to_s)
|
208
|
+
end
|
209
|
+
|
210
|
+
it 'should provide the average value when conditions provided' do
|
211
|
+
target(Dragon, target_type).avg(:toes_on_claw, :is_fire_breathing => true).should == 4.5
|
212
|
+
target(Dragon, target_type).avg(:toes_on_claw, :is_fire_breathing => false).should == 3
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
describe ".sum on a #{target_type}" do
|
218
|
+
describe 'with no arguments' do
|
219
|
+
it 'should raise an error' do
|
220
|
+
lambda { target(Dragon, target_type).sum }.should raise_error(ArgumentError)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
describe 'with a property name' do
|
225
|
+
it 'should provide the sum of values for an Integer property' do
|
226
|
+
target(Dragon, target_type).sum(:toes_on_claw).should == 12
|
227
|
+
|
228
|
+
total_population = 1330044605 + 303824646 + 191908598 + 140702094 +
|
229
|
+
127288419 + 109955400 + 82369548
|
230
|
+
target(Country, target_type).sum(:population).should == total_population
|
231
|
+
end
|
232
|
+
|
233
|
+
it 'should provide the sum of values for a Float property' do
|
234
|
+
total_tonnes = 600.0 + 8133.5 + 438.2 + 765.2 + 3417.4
|
235
|
+
target(Country, target_type).sum(:gold_reserve_tonnes).should be_kind_of(Float)
|
236
|
+
target(Country, target_type).sum(:gold_reserve_tonnes).should >= total_tonnes - @approx_by # approx match
|
237
|
+
target(Country, target_type).sum(:gold_reserve_tonnes).should <= total_tonnes + @approx_by # approx match
|
238
|
+
end
|
239
|
+
|
240
|
+
it 'should provide the sum of values for a BigDecimal property' do
|
241
|
+
target(Country, target_type).sum(:gold_reserve_value).should == BigDecimal('37090059214100.0')
|
242
|
+
end
|
243
|
+
|
244
|
+
it 'should provide the average value when conditions provided' do
|
245
|
+
target(Dragon, target_type).sum(:toes_on_claw, :is_fire_breathing => true).should == 9
|
246
|
+
target(Dragon, target_type).sum(:toes_on_claw, :is_fire_breathing => false).should == 3
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'pathname'
|
3
|
+
require Pathname(__FILE__).dirname.expand_path.parent + 'lib/dm-aggregates'
|
4
|
+
|
5
|
+
def load_driver(name, default_uri)
|
6
|
+
return false if ENV['ADAPTER'] != name.to_s
|
7
|
+
|
8
|
+
lib = "do_#{name}"
|
9
|
+
|
10
|
+
begin
|
11
|
+
gem lib, '=0.9.2'
|
12
|
+
require lib
|
13
|
+
DataMapper.setup(name, ENV["#{name.to_s.upcase}_SPEC_URI"] || default_uri)
|
14
|
+
DataMapper::Repository.adapters[:default] = DataMapper::Repository.adapters[name]
|
15
|
+
true
|
16
|
+
rescue Gem::LoadError => e
|
17
|
+
warn "Could not load #{lib}: #{e}"
|
18
|
+
false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
ENV['ADAPTER'] ||= 'sqlite3'
|
23
|
+
|
24
|
+
HAS_SQLITE3 = load_driver(:sqlite3, 'sqlite3::memory:')
|
25
|
+
HAS_MYSQL = load_driver(:mysql, 'mysql://localhost/dm_core_test')
|
26
|
+
HAS_POSTGRES = load_driver(:postgres, 'postgres://postgres@localhost/dm_core_test')
|
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dm-aggregates
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Foy Savas
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-06-25 00:00:00 -05:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: dm-core
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - "="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.9.2
|
23
|
+
version:
|
24
|
+
description: DataMapper plugin providing support for aggregates, functions on collections and datasets
|
25
|
+
email: foysavas@gmail.com
|
26
|
+
executables: []
|
27
|
+
|
28
|
+
extensions: []
|
29
|
+
|
30
|
+
extra_rdoc_files:
|
31
|
+
- README
|
32
|
+
- LICENSE
|
33
|
+
- TODO
|
34
|
+
files:
|
35
|
+
- lib/dm-aggregates/adapters/data_objects_adapter.rb
|
36
|
+
- lib/dm-aggregates/collection.rb
|
37
|
+
- lib/dm-aggregates/functions.rb
|
38
|
+
- lib/dm-aggregates/model.rb
|
39
|
+
- lib/dm-aggregates/repository.rb
|
40
|
+
- lib/dm-aggregates.rb
|
41
|
+
- spec/integration/aggregates_spec.rb
|
42
|
+
- spec/spec_helper.rb
|
43
|
+
- spec/spec.opts
|
44
|
+
- Rakefile
|
45
|
+
- README
|
46
|
+
- LICENSE
|
47
|
+
- TODO
|
48
|
+
has_rdoc: true
|
49
|
+
homepage: http://github.com/sam/dm-more/tree/master/dm-aggregates
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options: []
|
52
|
+
|
53
|
+
require_paths:
|
54
|
+
- lib
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: "0"
|
60
|
+
version:
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: "0"
|
66
|
+
version:
|
67
|
+
requirements: []
|
68
|
+
|
69
|
+
rubyforge_project:
|
70
|
+
rubygems_version: 1.0.1
|
71
|
+
signing_key:
|
72
|
+
specification_version: 2
|
73
|
+
summary: DataMapper plugin providing support for aggregates, functions on collections and datasets
|
74
|
+
test_files: []
|
75
|
+
|