summarizer 0.1.1
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/.gitignore +25 -0
- data/LICENSE +20 -0
- data/README.rdoc +77 -0
- data/Rakefile +57 -0
- data/VERSION +1 -0
- data/features/step_definitions/summarizer_steps.rb +91 -0
- data/features/summarizer.feature +19 -0
- data/features/support/env.rb +57 -0
- data/lib/summarizer/add_it_up.rb +49 -0
- data/lib/summarizer/base.rb +132 -0
- data/lib/summarizer/static.rb +22 -0
- data/lib/summarizer.rb +10 -0
- data/spec/database.yml +10 -0
- data/spec/factories.rb +12 -0
- data/spec/fixtures/bars.yml +15 -0
- data/spec/models/bar.rb +18 -0
- data/spec/models/foo.rb +32 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/summarizer_spec.rb +7 -0
- data/summarizer.gemspec +74 -0
- metadata +109 -0
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Christoph Petschnig
|
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.rdoc
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
= summarizer
|
2
|
+
|
3
|
+
== Summary
|
4
|
+
|
5
|
+
In the model:
|
6
|
+
|
7
|
+
class Foo < ActiveRecord::Base
|
8
|
+
include Summarizer::Base # add Summarizer to your class
|
9
|
+
summarize :net, :gross # which columns/methods do you want to sum up?
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
In the controller:
|
14
|
+
|
15
|
+
# find_with_sums extends ActiveRecord.find
|
16
|
+
@foos = Foo.find_with_sums(:all, :order => 'created_at',
|
17
|
+
:split_sections_on => Proc.new {|a,b| a.at.month != b.at.month},
|
18
|
+
:section_label => Proc.new{|obj| obj.at.strftime("%B %Y")})
|
19
|
+
|
20
|
+
|
21
|
+
In the view:
|
22
|
+
|
23
|
+
<table>
|
24
|
+
<% @foos.each_with_sums do |foo| %>
|
25
|
+
<%= render :partial => 'index_section_header', :locals => { :header => foo.section_label } if foo.new_section? %>
|
26
|
+
<tr>
|
27
|
+
<td><%=h foo.created_at %></td>
|
28
|
+
<td><%=h foo.name %></td>
|
29
|
+
<td><%=h foo.gross %></td>
|
30
|
+
<td><%=h foo.net %></td>
|
31
|
+
</tr>
|
32
|
+
<%= render :partial => 'index_sum', :locals => { :sum => foo.section_sum } if foo.end_of_section? %>
|
33
|
+
<% end %>
|
34
|
+
<%= render :partial => 'index_sum', :locals => { :sum => @foos.total } %>
|
35
|
+
</table>
|
36
|
+
|
37
|
+
The partials _index_section_header.html.erb
|
38
|
+
|
39
|
+
<tr>
|
40
|
+
<td colspan="4"><%= header %></td>
|
41
|
+
</tr>
|
42
|
+
|
43
|
+
and _index_sum.html.erb
|
44
|
+
|
45
|
+
<tr>
|
46
|
+
<td colspan="2">Total</td>
|
47
|
+
<td><%= sum.gross %></td>
|
48
|
+
<td><%= sum.net %></td>
|
49
|
+
</tr>
|
50
|
+
|
51
|
+
|
52
|
+
In the Browser:
|
53
|
+
|
54
|
+
November 2009
|
55
|
+
2009-11-29 03:32:27 Blah 160.46 91.25
|
56
|
+
Total 160.46 91.25
|
57
|
+
|
58
|
+
December 2009
|
59
|
+
2009-12-04 01:32:45 Blub 130.42 122.24
|
60
|
+
2009-12-05 18:02:33 Bar 77.53 135.57
|
61
|
+
2009-12-15 21:19:25 Blah 175.32 150.42
|
62
|
+
2009-12-31 17:21:49 Baz 192.05 135.04
|
63
|
+
Total 575.32 543.27
|
64
|
+
|
65
|
+
January 2010
|
66
|
+
2010-01-08 18:26:48 Blub 116.14 151.22
|
67
|
+
Total 116.14 151.22
|
68
|
+
----------------------------------------------
|
69
|
+
Total 851.92 785.74
|
70
|
+
|
71
|
+
== Usage
|
72
|
+
|
73
|
+
Soon to come...
|
74
|
+
|
75
|
+
== Copyright
|
76
|
+
|
77
|
+
Copyright (c) 2010 Christoph Petschnig. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "summarizer"
|
8
|
+
gem.summary = "Make sums and subtotals of your database tables"
|
9
|
+
gem.description = "Make sums and subtotals of your database tables"
|
10
|
+
gem.email = "info@purevirtual.de"
|
11
|
+
gem.homepage = "http://github.com/ChristophPetschnig/summarizer"
|
12
|
+
gem.authors = ["Christoph Petschnig"]
|
13
|
+
gem.add_development_dependency "rspec", ">= 1.2.9"
|
14
|
+
gem.add_development_dependency "yard", ">= 0"
|
15
|
+
gem.add_development_dependency "cucumber", ">= 0"
|
16
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
17
|
+
end
|
18
|
+
Jeweler::GemcutterTasks.new
|
19
|
+
rescue LoadError
|
20
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'spec/rake/spectask'
|
24
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
25
|
+
spec.libs << 'lib' << 'spec'
|
26
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
27
|
+
end
|
28
|
+
|
29
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
30
|
+
spec.libs << 'lib' << 'spec'
|
31
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
32
|
+
spec.rcov = true
|
33
|
+
end
|
34
|
+
|
35
|
+
task :spec => :check_dependencies
|
36
|
+
|
37
|
+
begin
|
38
|
+
require 'cucumber/rake/task'
|
39
|
+
Cucumber::Rake::Task.new(:features)
|
40
|
+
|
41
|
+
task :features => :check_dependencies
|
42
|
+
rescue LoadError
|
43
|
+
task :features do
|
44
|
+
abort "Cucumber is not available. In order to run features, you must: sudo gem install cucumber"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
task :default => :spec
|
49
|
+
|
50
|
+
begin
|
51
|
+
require 'yard'
|
52
|
+
YARD::Rake::YardocTask.new
|
53
|
+
rescue LoadError
|
54
|
+
task :yardoc do
|
55
|
+
abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
|
56
|
+
end
|
57
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.1
|
@@ -0,0 +1,91 @@
|
|
1
|
+
Given /^I need sub\-totals for each (.*)$/ do |sum_type|
|
2
|
+
# get a symbol key out of string sum_type
|
3
|
+
@sum_type = case sum_type
|
4
|
+
when 'letter in the alphabet for `name`' then :name_1st_char
|
5
|
+
else
|
6
|
+
sum_type.gsub(/[- ]/, '_').to_sym
|
7
|
+
end
|
8
|
+
|
9
|
+
case @sum_type
|
10
|
+
when :month then
|
11
|
+
@order_by = 'at ASC'
|
12
|
+
@proc = Proc.new{|obj| obj.at.strftime('%Y-%m')}
|
13
|
+
when :name_1st_char then
|
14
|
+
@order_by = 'name ASC'
|
15
|
+
@proc = Proc.new{|obj| obj.name[0,1].upcase}
|
16
|
+
when :different_bar then
|
17
|
+
@order_by = 'bar_id ASC'
|
18
|
+
@proc = Proc.new{|obj| obj.bar_id}
|
19
|
+
else
|
20
|
+
raise "Unknown test case `#{sum_type}`"
|
21
|
+
end
|
22
|
+
|
23
|
+
@split_sections_on = Proc.new {|a,b| @proc.call(a) != @proc.call(b)}
|
24
|
+
|
25
|
+
# create some data
|
26
|
+
100.times { Factory.create :foo }
|
27
|
+
|
28
|
+
@sum_comp = Hash.new
|
29
|
+
|
30
|
+
foos = Foo.find(:all, :order => @order_by)
|
31
|
+
foos.each do |foo|
|
32
|
+
key = @proc.call(foo)
|
33
|
+
|
34
|
+
#initialize pair
|
35
|
+
@sum_comp[key] ||= {:costs => 0.0, :price => 0.0, :diff => 0.0}
|
36
|
+
|
37
|
+
@sum_comp[key][:costs] += foo.costs
|
38
|
+
@sum_comp[key][:price] += foo.price
|
39
|
+
@sum_comp[key][:diff] += foo.diff
|
40
|
+
end
|
41
|
+
#@sum_comp.each{|k,v|puts "#{k}: #{v[:costs]} #{v[:price]} #{v[:diff]}"}
|
42
|
+
end
|
43
|
+
|
44
|
+
When /^I call `Foo::find_with_sums`$/ do
|
45
|
+
@foos = Foo.find_with_sums(:all,
|
46
|
+
:order => @order_by, :split_sections_on => @split_sections_on,
|
47
|
+
:section_label => @proc)
|
48
|
+
end
|
49
|
+
|
50
|
+
Then /^`each_with_sums` should return correct values$/ do
|
51
|
+
last_item = nil
|
52
|
+
@foos.each_with_sums do |item|
|
53
|
+
end_of_section = last_item && @proc.call(item) != @proc.call(last_item)
|
54
|
+
|
55
|
+
key = @proc.call(last_item) if last_item
|
56
|
+
|
57
|
+
if end_of_section
|
58
|
+
last_item.section_sum.costs.should == @sum_comp[key][:costs]
|
59
|
+
last_item.section_sum.price.should == @sum_comp[key][:price]
|
60
|
+
last_item.section_sum.diff.should == @sum_comp[key][:diff]
|
61
|
+
end
|
62
|
+
|
63
|
+
last_item = item
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
#When /^I use `each_with_sums`$/ do
|
68
|
+
# @foos.each_with_sums do |item|
|
69
|
+
#
|
70
|
+
#
|
71
|
+
# end
|
72
|
+
#end
|
73
|
+
|
74
|
+
Then /^I should see a table with sub\-totals$/ do
|
75
|
+
@foos.each_with_sums do |item|
|
76
|
+
puts "+--- #{item.section_label} ".ljust(60, '-') + '+' if item.new_section?
|
77
|
+
puts "| #{item.at.strftime('%Y-%m-%d')} #{item.name.ljust(8)} " +
|
78
|
+
"#{(item.bar ? item.bar.name : '').rjust(8)} #{format('%.2f', item.costs).rjust(8)} " +
|
79
|
+
"#{format('%.2f', item.price).rjust(8)} #{format('%.2f', item.diff).rjust(7)} |"
|
80
|
+
if item.end_of_section?
|
81
|
+
puts "+" + '-' * 59 + '+'
|
82
|
+
puts "| Total #{item.section_label} ".ljust(34) + "#{format('%.2f', item.section_sum.costs).rjust(8)} " +
|
83
|
+
"#{format('%.2f', item.section_sum.price).rjust(8)} #{format('%.2f', item.section_sum.diff).rjust(7)} |\n"
|
84
|
+
puts "+" + '-' * 59 + "+\n\n"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
puts "+" + '-' * 59 + '+'
|
88
|
+
puts "| Grand Total".ljust(34) + "#{format('%.2f', @foos.total.costs).rjust(8)} " +
|
89
|
+
"#{format('%.2f', @foos.total.price).rjust(8)} #{format('%.2f', @foos.total.diff).rjust(7)} |\n"
|
90
|
+
puts "+" + '-' * 59 + "+\n\n"
|
91
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
Feature: Summarizer Base
|
2
|
+
|
3
|
+
Scenario: Table with a section for each month
|
4
|
+
Given I need sub-totals for each month
|
5
|
+
When I call `Foo::find_with_sums`
|
6
|
+
#Then I should see a table with sub-totals
|
7
|
+
Then `each_with_sums` should return correct values
|
8
|
+
|
9
|
+
Scenario: Table with a section for every letter in the alphabet
|
10
|
+
Given I need sub-totals for each letter in the alphabet for `name`
|
11
|
+
When I call `Foo::find_with_sums`
|
12
|
+
#Then I should see a table with sub-totals
|
13
|
+
Then `each_with_sums` should return correct values
|
14
|
+
|
15
|
+
Scenario: Table with a section for each `belongs_to` entity `Bar`
|
16
|
+
Given I need sub-totals for each different bar
|
17
|
+
When I call `Foo::find_with_sums`
|
18
|
+
#Then I should see a table with sub-totals
|
19
|
+
Then `each_with_sums` should return correct values
|
@@ -0,0 +1,57 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
|
2
|
+
require 'summarizer'
|
3
|
+
|
4
|
+
require 'spec/expectations'
|
5
|
+
|
6
|
+
require 'factory_girl'
|
7
|
+
require 'active_record'
|
8
|
+
require 'database_cleaner'
|
9
|
+
require 'database_cleaner/cucumber'
|
10
|
+
|
11
|
+
require File.dirname(__FILE__) + '/../../spec/models/foo'
|
12
|
+
require File.dirname(__FILE__) + '/../../spec/models/bar'
|
13
|
+
|
14
|
+
|
15
|
+
# taken from will_paginate, activerecord_test_connector.rb
|
16
|
+
def setup_connection
|
17
|
+
db = ENV['DB'].blank?? 'mysql' : ENV['DB']
|
18
|
+
|
19
|
+
configurations = YAML.load_file(File.join(File.dirname(__FILE__), '..', '..', 'spec', 'database.yml'))
|
20
|
+
raise "no configuration for '#{db}'" unless configurations.key? db
|
21
|
+
configuration = configurations[db]
|
22
|
+
|
23
|
+
ActiveRecord::Base.logger = Logger.new(STDOUT) if $0 == 'irb'
|
24
|
+
puts "using #{configuration['adapter']} adapter" unless ENV['DB'].blank?
|
25
|
+
|
26
|
+
gem 'sqlite3-ruby' if 'sqlite3' == db
|
27
|
+
|
28
|
+
ActiveRecord::Base.establish_connection(configuration)
|
29
|
+
ActiveRecord::Base.configurations = { db => configuration }
|
30
|
+
#prepare ActiveRecord::Base.connection
|
31
|
+
|
32
|
+
unless Object.const_defined?(:QUOTED_TYPE)
|
33
|
+
Object.send :const_set, :QUOTED_TYPE, ActiveRecord::Base.connection.quote_column_name('type')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
setup_connection
|
39
|
+
|
40
|
+
|
41
|
+
DatabaseCleaner.strategy = :truncation
|
42
|
+
|
43
|
+
|
44
|
+
begin
|
45
|
+
CreateFoos.migrate(:up) unless Foo.table_exists?
|
46
|
+
CreateBars.migrate(:up) unless Bar.table_exists?
|
47
|
+
rescue
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
DatabaseCleaner.clean
|
52
|
+
|
53
|
+
|
54
|
+
require 'active_record/fixtures'
|
55
|
+
|
56
|
+
Fixtures.create_fixtures(File.join(File.dirname(__FILE__), '..', '..', 'spec', 'fixtures'), 'bars')
|
57
|
+
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# Adds a class to sum up attributes of the class where it's included
|
2
|
+
module Summarizer
|
3
|
+
module Base
|
4
|
+
class AddItUp
|
5
|
+
|
6
|
+
def initialize(tied_to_class)
|
7
|
+
# get options from referring class
|
8
|
+
options = tied_to_class.read_inheritable_attribute 'options'
|
9
|
+
# initialize attributes
|
10
|
+
@attributes = {}
|
11
|
+
(tied_to_class.read_inheritable_attribute('attributes') || []).each{|a| @attributes[a] = 0.0}
|
12
|
+
@digits = options[:digits] || 2
|
13
|
+
@digits_factor = 1.0
|
14
|
+
@digits.times{ @digits_factor *= 10 }
|
15
|
+
@empty = true
|
16
|
+
end
|
17
|
+
|
18
|
+
# Add attribute values of referring class instance
|
19
|
+
def add(obj)
|
20
|
+
@attributes.each{|key, value| @attributes[key] = value + (obj.send(key) * @digits_factor).round / @digits_factor}
|
21
|
+
@empty = false
|
22
|
+
end
|
23
|
+
|
24
|
+
def reset
|
25
|
+
@attributes.each_key{|key| @attributes[key] = 0.0}
|
26
|
+
@empty = true
|
27
|
+
end
|
28
|
+
|
29
|
+
def empty?
|
30
|
+
@empty
|
31
|
+
end
|
32
|
+
|
33
|
+
def static
|
34
|
+
Static.new(@attributes)
|
35
|
+
end
|
36
|
+
|
37
|
+
def method_missing(symbol, *args)
|
38
|
+
return @attributes[symbol] if @attributes.has_key? symbol
|
39
|
+
super symbol, *args
|
40
|
+
end
|
41
|
+
|
42
|
+
def respond_to?(symbol, include_priv = false)
|
43
|
+
return true if @attributes.has_key? symbol
|
44
|
+
super symbol, include_priv
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# Adds a class to sum up attributes of the class where it's included
|
2
|
+
module Summarizer
|
3
|
+
module Base
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
# Class enhancements
|
7
|
+
base.extend(ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
# the methods in this module will become class methods
|
11
|
+
module ClassMethods
|
12
|
+
# macro: store the option and the attribute to summarize
|
13
|
+
def summarize(*args)
|
14
|
+
options = args.last.kind_of?(Hash) ? args.pop : {}
|
15
|
+
|
16
|
+
write_inheritable_attribute 'attributes', args
|
17
|
+
write_inheritable_attribute 'options', options
|
18
|
+
end
|
19
|
+
|
20
|
+
def find_with_sums(*args)
|
21
|
+
|
22
|
+
options = args.extract_options!
|
23
|
+
|
24
|
+
raise ArgumentError unless args.first.nil? || args.first == :all
|
25
|
+
|
26
|
+
# extract method specific options before call to +find+
|
27
|
+
split_sections_on = options.delete(:split_sections_on)
|
28
|
+
section_label = options.delete(:section_label)
|
29
|
+
|
30
|
+
ar = self.find(:all, options)
|
31
|
+
|
32
|
+
class << ar
|
33
|
+
attr_reader :total
|
34
|
+
attr_writer :referring_class, :split_sections_on
|
35
|
+
attr_accessor :section_label
|
36
|
+
|
37
|
+
def each_with_sums(&block)
|
38
|
+
|
39
|
+
@total = AddItUp.new(@referring_class)
|
40
|
+
sub_total = AddItUp.new(@referring_class)
|
41
|
+
previous_item = nil
|
42
|
+
|
43
|
+
self.each_with_index do |item, index|
|
44
|
+
class << item
|
45
|
+
attr_writer :section_label
|
46
|
+
def set_table_info(new_group, end_of_group, sum_group)
|
47
|
+
@new_section = new_group
|
48
|
+
@end_of_section = end_of_group
|
49
|
+
@sum_section = sum_group.static if end_of_group
|
50
|
+
end
|
51
|
+
# Returns +true+, if this items is the first of a new section
|
52
|
+
def new_section?
|
53
|
+
@new_section
|
54
|
+
end
|
55
|
+
# Returns +true+, if this items is the last of a section
|
56
|
+
def end_of_section?
|
57
|
+
@end_of_section
|
58
|
+
end
|
59
|
+
# Returns the summarizer object of the given section
|
60
|
+
def section_sum
|
61
|
+
@sum_section
|
62
|
+
end
|
63
|
+
def section_label
|
64
|
+
if @section_label.kind_of?(Proc)
|
65
|
+
@section_label.call(self)
|
66
|
+
elsif @section_label.kind_of?(Symbol)
|
67
|
+
# if a Symbol is given, call the matching method
|
68
|
+
self.send(@section_label)
|
69
|
+
else
|
70
|
+
raise ArgumentError.new("@section_label has class #{@section_label.class}, but must be either Proc or Symbol.")
|
71
|
+
raise ArgumentError.new("Value of :section_label has class #{@section_label.class}, but must be either Proc or Symbol.")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# check, if item opens a new section in the array
|
77
|
+
new_section = previous_item.nil? || check_for_different_sections(previous_item, item)
|
78
|
+
|
79
|
+
# peek at the next item to see if item is the last of the section
|
80
|
+
next_item = self.at(index + 1)
|
81
|
+
|
82
|
+
end_of_section = next_item.nil? || check_for_different_sections(item, next_item)
|
83
|
+
|
84
|
+
# add it up!
|
85
|
+
@total.add(item)
|
86
|
+
sub_total.add(item)
|
87
|
+
|
88
|
+
# store information in item
|
89
|
+
item.set_table_info(new_section, end_of_section, sub_total)
|
90
|
+
|
91
|
+
item.section_label = self.section_label
|
92
|
+
|
93
|
+
# yield the block
|
94
|
+
yield item
|
95
|
+
|
96
|
+
# reset summarizer if neccesary
|
97
|
+
sub_total.reset if end_of_section
|
98
|
+
|
99
|
+
# loop!
|
100
|
+
previous_item = item
|
101
|
+
end
|
102
|
+
|
103
|
+
self
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
def check_for_different_sections(a, b)
|
108
|
+
if @split_sections_on.kind_of?(Proc)
|
109
|
+
# evaluate Proc to check, if item is in a new section
|
110
|
+
@split_sections_on.call(a, b)
|
111
|
+
elsif @split_sections_on.kind_of?(Symbol)
|
112
|
+
# if a Symbol is given, compare the return values of their functions
|
113
|
+
a.send(@split_sections_on) != b.send(@split_sections_on)
|
114
|
+
else
|
115
|
+
raise ArgumentError.new("Value of :split_sections_on has class #{@split_sections_on.class}, but must be either Proc or Symbol.")
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
ar.split_sections_on = split_sections_on
|
121
|
+
ar.section_label = section_label
|
122
|
+
|
123
|
+
# Array just wouldn't know otherwise, with class it belongs to
|
124
|
+
ar.referring_class = self
|
125
|
+
|
126
|
+
ar
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Serves as a copy of AddItUp with static data
|
2
|
+
module Summarizer
|
3
|
+
module Base
|
4
|
+
class Static
|
5
|
+
|
6
|
+
def initialize(attributes)
|
7
|
+
@attributes = attributes.clone
|
8
|
+
end
|
9
|
+
|
10
|
+
def method_missing(symbol, *args)
|
11
|
+
return @attributes[symbol] if @attributes.has_key? symbol
|
12
|
+
super symbol, *args
|
13
|
+
end
|
14
|
+
|
15
|
+
def respond_to?(symbol, include_priv = false)
|
16
|
+
return true if @attributes.has_key? symbol
|
17
|
+
super symbol, include_priv
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/summarizer.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
module Summarizer
|
5
|
+
VERSION = '0.0.1'
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'summarizer/base'
|
9
|
+
require 'summarizer/add_it_up'
|
10
|
+
require 'summarizer/static'
|
data/spec/database.yml
ADDED
data/spec/factories.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
|
2
|
+
NAMES = %w{ Fred Suzie John Mary Mike Amy Bill Kim Steve Katie Larry Mel Alex Lisa Jack Betty Sam Pam Drew Janet Frank Ruby Willy Rita Andy }
|
3
|
+
|
4
|
+
Factory.define :foo do |f|
|
5
|
+
f.name {NAMES[rand(NAMES.length)] }
|
6
|
+
#f.at {rand(365 + 28).days.ago}
|
7
|
+
f.at {rand(100).days.ago}
|
8
|
+
f.bar_id {1 + rand(4)}
|
9
|
+
f.costs {|obj| 100 + obj.bar_id * 30 + 70.0 * rand}
|
10
|
+
f.price {|obj| obj.costs + 50 + 50.0 * rand}
|
11
|
+
end
|
12
|
+
|
data/spec/models/bar.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
class Bar < ActiveRecord::Base
|
2
|
+
#data 'Alpha', 'Beta', 'Gamma', 'Omega'
|
3
|
+
end
|
4
|
+
|
5
|
+
|
6
|
+
|
7
|
+
class CreateBars < ActiveRecord::Migration
|
8
|
+
def self.up
|
9
|
+
create_table :bars do |t|
|
10
|
+
t.string :name
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.down
|
15
|
+
drop_table :bars
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
data/spec/models/foo.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
class Foo < ActiveRecord::Base
|
2
|
+
|
3
|
+
include Summarizer::Base
|
4
|
+
|
5
|
+
summarize :price, :costs, :diff
|
6
|
+
|
7
|
+
belongs_to :bar
|
8
|
+
|
9
|
+
def diff
|
10
|
+
self.price - self.costs
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
class CreateFoos < ActiveRecord::Migration
|
17
|
+
def self.up
|
18
|
+
create_table :foos do |t|
|
19
|
+
t.string :name
|
20
|
+
t.datetime :at
|
21
|
+
t.integer :bar_id
|
22
|
+
t.decimal :price, :precision => 12, :scale => 2
|
23
|
+
t.decimal :costs, :precision => 12, :scale => 2
|
24
|
+
t.timestamps
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.down
|
29
|
+
drop_table :foos
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
data/summarizer.gemspec
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{summarizer}
|
8
|
+
s.version = "0.1.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Christoph Petschnig"]
|
12
|
+
s.date = %q{2010-02-07}
|
13
|
+
s.description = %q{Make sums and subtotals of your database tables}
|
14
|
+
s.email = %q{info@purevirtual.de}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".gitignore",
|
21
|
+
"LICENSE",
|
22
|
+
"README.rdoc",
|
23
|
+
"Rakefile",
|
24
|
+
"VERSION",
|
25
|
+
"features/step_definitions/summarizer_steps.rb",
|
26
|
+
"features/summarizer.feature",
|
27
|
+
"features/support/env.rb",
|
28
|
+
"lib/summarizer.rb",
|
29
|
+
"lib/summarizer/add_it_up.rb",
|
30
|
+
"lib/summarizer/base.rb",
|
31
|
+
"lib/summarizer/static.rb",
|
32
|
+
"spec/database.yml",
|
33
|
+
"spec/factories.rb",
|
34
|
+
"spec/fixtures/bars.yml",
|
35
|
+
"spec/models/bar.rb",
|
36
|
+
"spec/models/foo.rb",
|
37
|
+
"spec/spec.opts",
|
38
|
+
"spec/spec_helper.rb",
|
39
|
+
"spec/summarizer_spec.rb",
|
40
|
+
"summarizer.gemspec"
|
41
|
+
]
|
42
|
+
s.homepage = %q{http://github.com/ChristophPetschnig/summarizer}
|
43
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
44
|
+
s.require_paths = ["lib"]
|
45
|
+
s.rubygems_version = %q{1.3.5}
|
46
|
+
s.summary = %q{Make sums and subtotals of your database tables}
|
47
|
+
s.test_files = [
|
48
|
+
"spec/models/foo.rb",
|
49
|
+
"spec/models/bar.rb",
|
50
|
+
"spec/spec_helper.rb",
|
51
|
+
"spec/summarizer_spec.rb",
|
52
|
+
"spec/factories.rb"
|
53
|
+
]
|
54
|
+
|
55
|
+
if s.respond_to? :specification_version then
|
56
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
57
|
+
s.specification_version = 3
|
58
|
+
|
59
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
60
|
+
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
61
|
+
s.add_development_dependency(%q<yard>, [">= 0"])
|
62
|
+
s.add_development_dependency(%q<cucumber>, [">= 0"])
|
63
|
+
else
|
64
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
65
|
+
s.add_dependency(%q<yard>, [">= 0"])
|
66
|
+
s.add_dependency(%q<cucumber>, [">= 0"])
|
67
|
+
end
|
68
|
+
else
|
69
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
70
|
+
s.add_dependency(%q<yard>, [">= 0"])
|
71
|
+
s.add_dependency(%q<cucumber>, [">= 0"])
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
metadata
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: summarizer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Christoph Petschnig
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-02-07 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rspec
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.2.9
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: yard
|
27
|
+
type: :development
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: cucumber
|
37
|
+
type: :development
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: "0"
|
44
|
+
version:
|
45
|
+
description: Make sums and subtotals of your database tables
|
46
|
+
email: info@purevirtual.de
|
47
|
+
executables: []
|
48
|
+
|
49
|
+
extensions: []
|
50
|
+
|
51
|
+
extra_rdoc_files:
|
52
|
+
- LICENSE
|
53
|
+
- README.rdoc
|
54
|
+
files:
|
55
|
+
- .gitignore
|
56
|
+
- LICENSE
|
57
|
+
- README.rdoc
|
58
|
+
- Rakefile
|
59
|
+
- VERSION
|
60
|
+
- features/step_definitions/summarizer_steps.rb
|
61
|
+
- features/summarizer.feature
|
62
|
+
- features/support/env.rb
|
63
|
+
- lib/summarizer.rb
|
64
|
+
- lib/summarizer/add_it_up.rb
|
65
|
+
- lib/summarizer/base.rb
|
66
|
+
- lib/summarizer/static.rb
|
67
|
+
- spec/database.yml
|
68
|
+
- spec/factories.rb
|
69
|
+
- spec/fixtures/bars.yml
|
70
|
+
- spec/models/bar.rb
|
71
|
+
- spec/models/foo.rb
|
72
|
+
- spec/spec.opts
|
73
|
+
- spec/spec_helper.rb
|
74
|
+
- spec/summarizer_spec.rb
|
75
|
+
- summarizer.gemspec
|
76
|
+
has_rdoc: true
|
77
|
+
homepage: http://github.com/ChristophPetschnig/summarizer
|
78
|
+
licenses: []
|
79
|
+
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options:
|
82
|
+
- --charset=UTF-8
|
83
|
+
require_paths:
|
84
|
+
- lib
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: "0"
|
90
|
+
version:
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: "0"
|
96
|
+
version:
|
97
|
+
requirements: []
|
98
|
+
|
99
|
+
rubyforge_project:
|
100
|
+
rubygems_version: 1.3.5
|
101
|
+
signing_key:
|
102
|
+
specification_version: 3
|
103
|
+
summary: Make sums and subtotals of your database tables
|
104
|
+
test_files:
|
105
|
+
- spec/models/foo.rb
|
106
|
+
- spec/models/bar.rb
|
107
|
+
- spec/spec_helper.rb
|
108
|
+
- spec/summarizer_spec.rb
|
109
|
+
- spec/factories.rb
|