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 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,6 @@
1
+ TODO
2
+ ====
3
+
4
+ ---
5
+ TODO tickets may also be found in the DataMapper Issue Tracker:
6
+ http://wm.lighthouseapp.com/projects/4819-datamapper/overview
@@ -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
@@ -0,0 +1,2 @@
1
+ --format specdoc
2
+ --colour
@@ -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
+