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