ruport_report_builder 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/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 MileMeter, Inc.
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,17 @@
1
+ = MileMeter, Inc.
2
+
3
+ Description goes here.
4
+
5
+ == Note on Patches/Pull Requests
6
+
7
+ * Fork the project.
8
+ * Make your feature addition or bug fix.
9
+ * Add tests for it. This is important so I don't break it in a
10
+ future version unintentionally.
11
+ * Commit, do not mess with rakefile, version, or history.
12
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
13
+ * Send me a pull request. Bonus points for topic branches.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2010 MileMeter, Inc. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,47 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "ruport_report_builder"
8
+ gem.summary = %Q{Simple wrapper for common ruport tasks}
9
+ gem.description = %Q{Simple wrapper for common ruport tasks}
10
+ gem.email = "code@milemeter.com"
11
+ gem.homepage = "http://github.com/milemeter/ruport_report_builder"
12
+ gem.authors = ["Doug Bryant", "John Riney"]
13
+ gem.add_dependency('ruport', '>= 1.4.0')
14
+ gem.add_dependency('activerecord', '>= 2.0.0')
15
+ gem.add_development_dependency "rspec", ">= 1.2.9"
16
+ gem.add_development_dependency 'sqlite3-ruby'
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
+ task :default => :spec
38
+
39
+ require 'rake/rdoctask'
40
+ Rake::RDocTask.new do |rdoc|
41
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
42
+
43
+ rdoc.rdoc_dir = 'rdoc'
44
+ rdoc.title = "Ruport Report Builder #{version}"
45
+ rdoc.rdoc_files.include('README*')
46
+ rdoc.rdoc_files.include('lib/**/*.rb')
47
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.1
@@ -0,0 +1,43 @@
1
+ # Maintains a hash in specified order when retrieving keys/values
2
+ module RuportReportBuilderUtil
3
+ class OrderedHash < Hash
4
+ def initialize
5
+ @keys = []
6
+ end
7
+
8
+ def []=(key, val)
9
+ @keys << key
10
+ super
11
+ end
12
+
13
+ def keys
14
+ @keys
15
+ end
16
+
17
+ def values
18
+ result = []
19
+ each_value{|v| result << v}
20
+ result
21
+ end
22
+
23
+ def delete(key)
24
+ @keys.delete(key)
25
+ super
26
+ end
27
+
28
+ def each
29
+ @keys.each { |k| yield k, self[k] }
30
+ end
31
+
32
+ def each_key
33
+ @keys.each { |k| yield k }
34
+ end
35
+
36
+ def each_value
37
+ @keys.each { |k| yield self[k] }
38
+ end
39
+
40
+ alias_method :store, :[]=
41
+ alias_method :each_pair, :each
42
+ end
43
+ end
@@ -0,0 +1,151 @@
1
+ #
2
+ # NOTES: When defining reports with define_report method, place the query methods *above* the report definition
3
+ # otherwise the report query will not be in scope
4
+ #
5
+ #
6
+ # Look at the test to figure out more details about how to use
7
+ # This is the jist of it
8
+ #
9
+ # class FooReport
10
+ # include RuportReportBuilder
11
+ #
12
+ # # Define a sort method iff you want it sorted. should return a hash similar to below. default order is ASC. :order is optional
13
+ # def sort
14
+ # {:columns => "quote_number", :order => :descending}
15
+ # end
16
+ #
17
+ # Define a query in two ways. First is with a properly named method.
18
+ # The method should be the same as the report name with _query appended at the end.
19
+ # Queries can return an array of objects such as [Foo.new, Foo.new, Foo.new]
20
+ # or include a sql statement such as select * from foo
21
+ #
22
+ # def foo_query
23
+ # .....
24
+ # end
25
+ #
26
+ # # Define a report. This should be done *below* the query definition if applicable because of scoping issues
27
+ # # What ever type of objects are returned from the "query" method
28
+ # define_report("foo") do |row, foo|
29
+ # row.store "Name", foo.name # typical usage for objects returned from query
30
+ # row.store "My Age", foo["age"] # typical usage for if query was sql
31
+ # end
32
+ # end
33
+ #
34
+ # Typical usage
35
+ #
36
+ # foo_report = Foo.new
37
+ # rpt = foo.build_report
38
+ # rpt.{as_text|as_html|as_pdf|as_excel}
39
+ #
40
+
41
+ require 'rubygems'
42
+ gem 'activerecord'
43
+ require 'active_record'
44
+ gem 'ruport'
45
+ require 'ruport'
46
+ require 'ordered_hash'
47
+
48
+ module RuportReportBuilder
49
+
50
+ def self.included(base)
51
+ base.extend ClassMethods
52
+ base.send :include, InstanceMethods
53
+ end
54
+
55
+ module ClassMethods
56
+ def reports
57
+ @reports ||= RuportReportBuilderUtil::OrderedHash.new
58
+ end
59
+
60
+ def report_names
61
+ reports.keys
62
+ end
63
+
64
+ def define_report(name, query = nil, &proc)
65
+ raise ArgumentError, "The report name [name] is incorrect. Report names must not contain a space!" if name.to_s.empty? || name.to_s.include?(" ")
66
+ raise ArgumentError, "You must provide a query or properly name it as a method. Looking for #{query_method_name(name)}. !!If using a query method, make sure it is defined *above* the report definition!!" if query.nil? && !self.instance_methods.include?(query_method_name(name))
67
+ reports[name] = {:query => query, :action => proc}
68
+ end
69
+
70
+ def report(name)
71
+ reports[name]
72
+ end
73
+
74
+ def query_method_name(name)
75
+ "#{name.gsub(/\s/, "_")}_query"
76
+ end
77
+ end
78
+
79
+ module InstanceMethods
80
+
81
+ #
82
+ # Returns a Ruport table object you can then call methods on
83
+ #
84
+ def build_report
85
+ main_report, *appended_reports = self.class.report_names
86
+
87
+ table_data = generate_report_data(main_report)
88
+
89
+ return Ruport::Data::Table.new if table_data.empty?
90
+
91
+ # build the table and set the column names based on the data from the "Primary" report
92
+ table = Ruport::Data::Table.new(:column_names => table_data.first.keys, :data => table_data.map{|d| d.values})
93
+
94
+
95
+ appended_reports.each do |rpt|
96
+ addl_rpt_data = generate_report_data(rpt)
97
+ addl_rpt_data.each{|row| table << row}
98
+ end
99
+ do_sort(table)
100
+ end
101
+ alias :build :build_report
102
+
103
+ protected
104
+
105
+ def generate_report_data(rpt)
106
+ if data = execute_query_for(rpt)
107
+ action = self.class.report(rpt)[:action]
108
+ return data.map do |d|
109
+ hash = RuportReportBuilderUtil::OrderedHash.new
110
+ action.call(hash, d)
111
+ hash
112
+ end
113
+ else
114
+ nil
115
+ end
116
+ end
117
+
118
+ def do_sort(table)
119
+ if self.respond_to?(:sort)
120
+ sort_by = self.sort
121
+ if sort_by.kind_of?(Hash)
122
+ table = table.sort_rows_by(sort_by[:columns], :order => sort_by[:order])
123
+ else
124
+ raise ArgumentError, "Sorting must return a hash!"
125
+ end
126
+ else
127
+ table
128
+ end
129
+ end
130
+
131
+ def execute_query_for(report)
132
+ rpt = self.class.report(report)
133
+ if rpt[:query].nil?
134
+ query = self.send self.class.query_method_name(report).to_sym
135
+ else
136
+ query = rpt[:query]
137
+ end
138
+
139
+ if query.kind_of?(Array)
140
+ result = query
141
+ else
142
+ result = ActiveRecord::Base.connection.execute query
143
+ end
144
+
145
+ result
146
+ end
147
+
148
+ end
149
+
150
+ end
151
+
@@ -0,0 +1,175 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ ##
4
+ # Support classes which will be under/used as test data
5
+ #
6
+
7
+ class Order < ActiveRecord::Base
8
+ end
9
+
10
+ class AskMe
11
+ def method_missing(symbol, *args)
12
+ "#{symbol.id2name}_answer"
13
+ end
14
+ end
15
+
16
+ class ReportBuilderUnderForMod
17
+ include RuportReportBuilder
18
+ end
19
+
20
+ class ReportBuilderUnderTestSql
21
+ include RuportReportBuilder
22
+
23
+ def rspec_sql_query
24
+ "select * from orders where status = 'paid' order by order_number ASC ;"
25
+ end
26
+
27
+ def sort
28
+ {:columns => "order_number", :order => :descending}
29
+ end
30
+
31
+ def rspec_object_query
32
+ (1..5).to_a.map{|x| AskMe.new}
33
+ end
34
+
35
+ define_report "rspec_sql" do |report, obj|
36
+ report.store "First Name", obj["first_name"]
37
+ report.store "Last Name", obj["last_name"]
38
+ report.store "Order Number", obj["order_number"]
39
+ report.store "Payment Status", obj["status"]
40
+ end
41
+
42
+ end
43
+
44
+ class ReportBuilderUnderTestNoSql
45
+ include RuportReportBuilder
46
+
47
+ def rspec_object_query
48
+ (1..5).to_a.map{|x| AskMe.new}
49
+ end
50
+
51
+ define_report "rspec_object" do |report, obj|
52
+ report.store "Status", obj.status
53
+ report.store "Miles Purchased", obj.miles_purchased
54
+ report.store "Term In Months", obj.term_in_months
55
+ end
56
+ end
57
+
58
+ ############
59
+ # Let the testing begin!
60
+ #
61
+
62
+ describe "ReportBuilder" do
63
+
64
+ it "should raise an ArgumentError if you try to define a report without a query and no default method" do
65
+ lambda {
66
+ ReportBuilderUnderForMod.instance_eval %Q{
67
+ define_report "fail" do |report, o|
68
+ report.store "FOOBAR", o.name
69
+ end
70
+ }
71
+ }.should raise_error(ArgumentError)
72
+ end
73
+
74
+ it "should raise an ArgumentError if the report name has a space in it" do
75
+ lambda {
76
+ ReportBuilderUnderForMod.instance_eval %Q{
77
+ define_report "gonna fail", "select * from temp" do |report, o|
78
+ report.store "FOOBAR", o.name
79
+ end
80
+ }
81
+ }.should raise_error(ArgumentError)
82
+ end
83
+
84
+ it "should not try to raise an error if you give it a query manually rather than defined as a method" do
85
+ lambda {
86
+ ReportBuilderUnderForMod.instance_eval %Q{
87
+ define_report "noFail", "select * from temp" do |report, o|
88
+ map "FOOBAR", o.name
89
+ end
90
+ }
91
+ }.should_not raise_error(ArgumentError)
92
+ end
93
+
94
+ it "should add a report to the list when it defines a report" do
95
+ ReportBuilderUnderTestNoSql.report("rspec_object").should_not be_nil
96
+ end
97
+
98
+ it "should be able to return a list of report names" do
99
+ ReportBuilderUnderTestNoSql.report_names.should == ["rspec_object"]
100
+ end
101
+
102
+ it "should be able to return a specific report" do
103
+ ReportBuilderUnderTestNoSql.report("rspec_object").should_not be_nil
104
+ end
105
+
106
+ it "should respond to the alias of build (alias of build_report)" do
107
+ ReportBuilderUnderTestNoSql.new.should respond_to(:build)
108
+ end
109
+
110
+
111
+ describe "Report using Objects" do
112
+ before(:each) do
113
+ @report = ReportBuilderUnderTestNoSql.new
114
+ end
115
+
116
+ it "should be able to generate a report with the query returning the data" do
117
+ result = @report.build_report
118
+ result.size.should == 5
119
+ 4.times {|row|
120
+ result[row][0].should == "status_answer"
121
+ result[row][1].should == "miles_purchased_answer"
122
+ result[row][2].should == "term_in_months_answer"
123
+ }
124
+ end
125
+ end
126
+
127
+ describe "Report using SQL" do
128
+ before(:each) do
129
+ ActiveRecord::Base.establish_connection("adapter"=>"sqlite3", "database"=>":memory:")
130
+ ActiveRecord::Base.logger = Logger.new(File.open('/dev/null', 'a'))
131
+ ActiveRecord::Base.connection.execute %Q{
132
+ create table orders (
133
+ first_name varchar2(30),
134
+ last_name varchar2(30),
135
+ order_number varchar2(30),
136
+ status varchar2(30)
137
+ );
138
+ }
139
+
140
+ Order.create!(:first_name => "doug" ,:last_name => "bryant", :order_number => "ABC087" ,:status => "failed")
141
+ Order.create!(:first_name => "john" ,:last_name => "riney", :order_number => "ABC042" ,:status => "pending")
142
+ Order.create!(:first_name => "chris" ,:last_name => "gay", :order_number => "ABC076" ,:status => "paid")
143
+ Order.create!(:first_name => "tom" ,:last_name => "mccall", :order_number => "ABC027" ,:status => "paid")
144
+ Order.create!(:first_name => "kenny" ,:last_name => "roberts", :order_number => "ABC001" ,:status => "paid")
145
+ Order.create!(:first_name => "ben" ,:last_name => "spies", :order_number => "ABC011" ,:status => "paid")
146
+ Order.create!(:first_name => "nicky" ,:last_name => "hayden", :order_number => "ABC069" ,:status => "paid")
147
+ Order.create!(:first_name => "colin" ,:last_name => "edwards", :order_number => "ABC005" ,:status => "paid")
148
+ end
149
+
150
+ after(:each) do
151
+ ActiveRecord::Base.connection.execute "drop table orders"
152
+ end
153
+
154
+ it "should be able to generate a report with SQL" do
155
+ report = ReportBuilderUnderTestSql.new
156
+ result = report.build_report
157
+ result.size.should == 6
158
+ end
159
+
160
+ it "should order the results properly" do
161
+ report = ReportBuilderUnderTestSql.new
162
+ result = report.build_report
163
+
164
+ # test that it is descending...
165
+ result[0][2].should == "ABC076"
166
+ result[1][2].should == "ABC069"
167
+ result[2][2].should == "ABC027"
168
+ result[3][2].should == "ABC011"
169
+ result[4][2].should == "ABC005"
170
+ result[5][2].should == "ABC001"
171
+ end
172
+
173
+ end
174
+
175
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,10 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'ruport_report_builder'
4
+ require 'sqlite3'
5
+ require 'spec'
6
+ require 'spec/autorun'
7
+
8
+ Spec::Runner.configure do |config|
9
+
10
+ end
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruport_report_builder
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 1
9
+ version: 0.1.1
10
+ platform: ruby
11
+ authors:
12
+ - Doug Bryant
13
+ - John Riney
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-05-10 00:00:00 -05:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: ruport
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 1
30
+ - 4
31
+ - 0
32
+ version: 1.4.0
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: activerecord
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ segments:
43
+ - 2
44
+ - 0
45
+ - 0
46
+ version: 2.0.0
47
+ type: :runtime
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ name: rspec
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ segments:
57
+ - 1
58
+ - 2
59
+ - 9
60
+ version: 1.2.9
61
+ type: :development
62
+ version_requirements: *id003
63
+ - !ruby/object:Gem::Dependency
64
+ name: sqlite3-ruby
65
+ prerelease: false
66
+ requirement: &id004 !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ segments:
71
+ - 0
72
+ version: "0"
73
+ type: :development
74
+ version_requirements: *id004
75
+ description: Simple wrapper for common ruport tasks
76
+ email: code@milemeter.com
77
+ executables: []
78
+
79
+ extensions: []
80
+
81
+ extra_rdoc_files:
82
+ - LICENSE
83
+ - README.rdoc
84
+ files:
85
+ - .document
86
+ - .gitignore
87
+ - LICENSE
88
+ - README.rdoc
89
+ - Rakefile
90
+ - VERSION
91
+ - lib/ordered_hash.rb
92
+ - lib/ruport_report_builder.rb
93
+ - spec/ruport_report_builder_spec.rb
94
+ - spec/spec.opts
95
+ - spec/spec_helper.rb
96
+ has_rdoc: true
97
+ homepage: http://github.com/milemeter/ruport_report_builder
98
+ licenses: []
99
+
100
+ post_install_message:
101
+ rdoc_options:
102
+ - --charset=UTF-8
103
+ require_paths:
104
+ - lib
105
+ required_ruby_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ segments:
110
+ - 0
111
+ version: "0"
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ segments:
117
+ - 0
118
+ version: "0"
119
+ requirements: []
120
+
121
+ rubyforge_project:
122
+ rubygems_version: 1.3.6
123
+ signing_key:
124
+ specification_version: 3
125
+ summary: Simple wrapper for common ruport tasks
126
+ test_files:
127
+ - spec/ruport_report_builder_spec.rb
128
+ - spec/spec_helper.rb