summarizer 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|