dm-aggregates 0.9.2 → 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt 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