dm-aggregates 0.9.2 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/History.txt 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
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
- CLEAN.include '{log,pkg}/'
6
+ ROOT = Pathname(__FILE__).dirname.expand_path
7
+ require ROOT + 'lib/dm-aggregates/version'
9
8
 
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
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
- Rake::GemPackageTask.new(spec) do |pkg|
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/#{spec.name}-#{spec.version} --no-update-sources", :verbose => false
30
+ sh "#{SUDO} gem install --local pkg/#{GEM_NAME}-#{GEM_VERSION} --no-update-sources", :verbose => false
38
31
  end
39
32
 
40
- desc "Uninstall #{spec.name} #{spec.version} (default ruby)"
33
+ desc "Uninstall #{GEM_NAME} #{GEM_VERSION} (default ruby)"
41
34
  task :uninstall => [ :clobber ] do
42
- sh "#{SUDO} gem uninstall #{spec.name} -v#{spec.version} -I -x", :verbose => false
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 #{spec.name} #{spec.version} with JRuby"
39
+ desc "Install #{GEM_NAME} #{GEM_VERSION} with JRuby"
47
40
  task :install => [ :package ] do
48
- sh %{#{SUDO} jruby -S gem install --local pkg/#{spec.name}-#{spec.version} --no-update-sources}, :verbose => false
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
- gem 'dm-core', '=0.9.2'
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 / 'functions'
9
- require dir / 'model'
10
- require dir / 'repository'
11
- require dir / 'collection'
12
- require dir / 'adapters' / 'data_objects_adapter'
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 count(property, query)
5
- query(aggregate_read_statement(:count, property, query), *query.bind_values).first
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
- def min(property, query)
9
- min = query(aggregate_read_statement(:min, property, query), *query.bind_values).first
10
- property.typecast(min)
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, query)
14
- max = query(aggregate_read_statement(:max, property, query), *query.bind_values).first
15
- property.typecast(max)
34
+ def max(property, value)
35
+ property.typecast(value)
16
36
  end
17
37
 
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)
38
+ def avg(property, value)
39
+ property.type == Integer ? value.to_f : property.typecast(value)
21
40
  end
22
41
 
23
- def sum(property, query)
24
- sum = query(aggregate_read_statement(:sum, property, query), *query.bind_values).first
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
- 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
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 = if aggregate_function == :count && property.nil?
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 Aggregates
3
+ include AggregateFunctions
4
4
 
5
5
  private
6
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
7
+ def property_by_name(property_name)
8
+ properties[property_name]
16
9
  end
17
10
  end
18
11
  end
@@ -1,18 +1,11 @@
1
1
  module DataMapper
2
2
  module Model
3
- include Aggregates
3
+ include AggregateFunctions
4
4
 
5
5
  private
6
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
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 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)
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
@@ -0,0 +1,7 @@
1
+ module DataMapper
2
+ module More
3
+ module Aggregates
4
+ VERSION = "0.9.3"
5
+ end
6
+ end
7
+ end
@@ -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
- property :id, Serial
11
- property :name, String
17
+
18
+ property :id, Serial
19
+ property :name, String
12
20
  property :is_fire_breathing, TrueClass
13
- property :toes_on_claw, Integer
21
+ property :toes_on_claw, Integer
22
+ property :birth_at, DateTime
23
+ property :birth_on, Date
24
+ property :birth_time, Time
14
25
 
15
- auto_migrate!(:default)
26
+ belongs_to :knight
16
27
  end
17
28
 
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)
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, Integer, :serial => true
28
- property :name, String, :nullable => false
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
- end
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.2'
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.2
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-06-25 00:00:00 -05:00
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.2
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: foysavas@gmail.com
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
- - Rakefile
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.1
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