bisque 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.3@bisque
data/Gemfile ADDED
@@ -0,0 +1,18 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+ gem 'activerecord', '>= 3.0.0'
6
+ gem 'pg'
7
+
8
+ # Add dependencies to develop your gem here.
9
+ # Include everything needed to run rake, tests, features, etc.
10
+ group :development do
11
+ gem "rspec", "~> 2.8.0"
12
+ gem "rdoc", "~> 3.12"
13
+ gem "bundler", "~> 1.1.0"
14
+ gem "jeweler", "~> 1.8.4"
15
+ gem "simplecov", ">= 0"
16
+ gem 'spork'
17
+ gem 'pry'
18
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,65 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activemodel (3.2.8)
5
+ activesupport (= 3.2.8)
6
+ builder (~> 3.0.0)
7
+ activerecord (3.2.8)
8
+ activemodel (= 3.2.8)
9
+ activesupport (= 3.2.8)
10
+ arel (~> 3.0.2)
11
+ tzinfo (~> 0.3.29)
12
+ activesupport (3.2.8)
13
+ i18n (~> 0.6)
14
+ multi_json (~> 1.0)
15
+ arel (3.0.2)
16
+ builder (3.0.0)
17
+ coderay (1.0.7)
18
+ diff-lcs (1.1.3)
19
+ git (1.2.5)
20
+ i18n (0.6.0)
21
+ jeweler (1.8.4)
22
+ bundler (~> 1.0)
23
+ git (>= 1.2.5)
24
+ rake
25
+ rdoc
26
+ json (1.7.5)
27
+ method_source (0.8)
28
+ multi_json (1.3.6)
29
+ pg (0.14.0)
30
+ pry (0.9.10)
31
+ coderay (~> 1.0.5)
32
+ method_source (~> 0.8)
33
+ slop (~> 3.3.1)
34
+ rake (0.9.2.2)
35
+ rdoc (3.12)
36
+ json (~> 1.4)
37
+ rspec (2.8.0)
38
+ rspec-core (~> 2.8.0)
39
+ rspec-expectations (~> 2.8.0)
40
+ rspec-mocks (~> 2.8.0)
41
+ rspec-core (2.8.0)
42
+ rspec-expectations (2.8.0)
43
+ diff-lcs (~> 1.1.2)
44
+ rspec-mocks (2.8.0)
45
+ simplecov (0.6.4)
46
+ multi_json (~> 1.0)
47
+ simplecov-html (~> 0.5.3)
48
+ simplecov-html (0.5.3)
49
+ slop (3.3.3)
50
+ spork (0.9.2)
51
+ tzinfo (0.3.33)
52
+
53
+ PLATFORMS
54
+ ruby
55
+
56
+ DEPENDENCIES
57
+ activerecord (>= 3.0.0)
58
+ bundler (~> 1.1.0)
59
+ jeweler (~> 1.8.4)
60
+ pg
61
+ pry
62
+ rdoc (~> 3.12)
63
+ rspec (~> 2.8.0)
64
+ simplecov
65
+ spork
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Jeremy Holland
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,108 @@
1
+ = bisque
2
+
3
+ Bisque is meant to ease the pain of "reporting"-style ad-hoc SQL queries in ActiveRecord. Most of the time, I see (and am guilty of having executed myself) solutions to this problem in one of two styles:
4
+
5
+ == Fugly: New scopes / class methods / find_by_sql
6
+
7
+ *Example*:
8
+
9
+ class HooHah < ActiveRecord::Base
10
+ scope :some_ridiculous_reporting_query, select(<<-RUBY
11
+ MOFO_OBSCURE_SQL_FUNCTION(some_table.some_column) AS some_non_column_field
12
+ RUBY
13
+ ).joins(<<-RUBY
14
+ INNER JOIN god_knows_what ON some_computationally_difficult_calculation = 5
15
+ RUBY
16
+ )
17
+ end
18
+
19
+ *Pros*: You get to use the existing ActiveRecord::Base hot mustard to execute your queries, parse them to fields on instances of the class, and handle them as collections
20
+
21
+ *Cons*: Your reports are ultimately instances of some model - but most reports are not that at all! They're aggregate views over your models, and trying to cram them into existing instances forces you to conflate two very distinct concepts in the domain.
22
+
23
+ == Even Fuglier: Query straight from the connection, then use the raw result or OpenStruct it in order to achieve some measure of separation
24
+
25
+ *Example*:
26
+
27
+ results = ActiveRecord::Base.connection.execute <<-RUBY
28
+ SELECT MOFO_OBSCURE_SQL_FUNCTION(some_table.some_column) AS some_non_column_field FROM umpteen_thousand_tables
29
+ INNER JOIN god_knows_what ON some_computationally_difficult_calculation = 5
30
+ RUBY
31
+
32
+ results.each do |row|
33
+ #ARGGHH
34
+ end
35
+
36
+ *Pros*: Avoids the conflation of the report with models it doesn't represent
37
+ *Cons*: Pretty much everything else. Lots of boilerplate.
38
+
39
+ == Help Arrives!
40
+
41
+ Enter Bisque. Bisque provides a simple way of defining Report classes, each with its own query and designated parameters, as well as a way to dynamically define methods on its rows. Bisque will translate any returned datatypes into their AR analog, and in general help you keep your jazz organized and tight. Here's how to use it:
42
+
43
+ === Step 1: Define a report class
44
+
45
+ Reports inherit from Bisque::Report, and have the following API:
46
+
47
+ class FooReport < Bisque::Report
48
+ query <<-RUBY
49
+ SELECT
50
+ SUM(o.total) AS total,
51
+ MEAN(o.total) AS average
52
+ FROM
53
+ orders o
54
+ INNER JOIN
55
+ customers c ON c.id = o.customer_id
56
+ WHERE
57
+ o.store_type = :store_type
58
+ AND
59
+ c.id = :customer_id
60
+ RUBY
61
+
62
+ defaults :store_type => "digital"
63
+
64
+ rows do
65
+ def total_in_cents
66
+ total*100
67
+ end
68
+ end
69
+ end
70
+
71
+ ==== Bisque::Report.query
72
+ Every report *must* have a query. Define it here, using colon-prefixed words to designate parameters
73
+
74
+ ==== Bisque::Report.defaults
75
+ This method takes a hash of defaults, if any, for the parameters in your query. Each parameter must either have some default set for it, or be passed a value at runtime (see instantiatino below)
76
+
77
+ ==== Bisque::Report.rows
78
+ If you wish to define custom methods on each individual row item of the report, define them here, and they will be evaluated in the context of the dynamically generated row class (which will be namespaced the same as the report class and named <YourReportClassName>Row).
79
+
80
+ === Step 2: Instantiate the report
81
+ To use run a given report, just create a new instance of it. Each report instance is enumerable and so may be iterated over as usual.
82
+
83
+ report = FooReport.new :customer_id => 42
84
+
85
+ You must provide a hash of values for each parameter (that doesn't have a default) defined in the query, or a Bisque::MissingParameterException will be raised and the report will not be executed.
86
+
87
+ If you have defined any custom row methods (via the rows block discussed above), you may call them on each item of the iterator as one would expect:
88
+
89
+ puts report.first.total_in_cents
90
+
91
+ == In closing
92
+ For the time being, Bisque supports PostgreSQL exclusively (db-agnositicism is broken due to the necessities associated with typecasting dynamically selected values), because I virtually never use anything else and wanted this gem out there ASAP for my own purposes. I sincerely doubt I'll ever get around to extending it for support of other AR-supported dbs, but if one of you fine folks would like to do so, submit a pull request as described below and I'll happily include it. Of course, any other improvements, feature requests, etc. will be considered and taken into account - I would like to see this grow into a highly versatile tool, so suggest away!
93
+
94
+ == Contributing to bisque
95
+
96
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
97
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
98
+ * Fork the project.
99
+ * Start a feature/bugfix branch.
100
+ * Commit and push until you are happy with your contribution.
101
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
102
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
103
+
104
+ == Copyright
105
+
106
+ Copyright (c) 2012 Jeremy Holland. See LICENSE.txt for
107
+ further details.
108
+
data/Rakefile ADDED
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "bisque"
18
+ gem.homepage = "http://github.com/awebneck/bisque"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{A simple gem for ease of use in generating reports with ActiveRecord}
21
+ gem.description = %Q{Bisque exists to ease the organizational pain of having to attach reporting and ad-hoc queries to ActiveRecord models in order to get any class-like behavior out of them by providing a new class and DSL for that express purpose.}
22
+ gem.email = "jeremy@jeremypholland.com"
23
+ gem.authors = ["Jeremy Holland"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core'
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ spec.pattern = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
35
+ spec.pattern = 'spec/**/*_spec.rb'
36
+ spec.rcov = true
37
+ end
38
+
39
+ task :default => :spec
40
+
41
+ require 'rdoc/task'
42
+ Rake::RDocTask.new do |rdoc|
43
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
44
+
45
+ rdoc.rdoc_dir = 'rdoc'
46
+ rdoc.title = "bisque #{version}"
47
+ rdoc.rdoc_files.include('README*')
48
+ rdoc.rdoc_files.include('lib/**/*.rb')
49
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/bisque.gemspec ADDED
@@ -0,0 +1,84 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "bisque"
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Jeremy Holland"]
12
+ s.date = "2012-09-01"
13
+ s.description = "Bisque exists to ease the organizational pain of having to attach reporting and ad-hoc queries to ActiveRecord models in order to get any class-like behavior out of them by providing a new class and DSL for that express purpose."
14
+ s.email = "jeremy@jeremypholland.com"
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".rspec",
22
+ ".rvmrc",
23
+ "Gemfile",
24
+ "Gemfile.lock",
25
+ "LICENSE.txt",
26
+ "README.rdoc",
27
+ "Rakefile",
28
+ "VERSION",
29
+ "bisque.gemspec",
30
+ "lib/bisque.rb",
31
+ "lib/bisque/exceptions.rb",
32
+ "lib/bisque/report.rb",
33
+ "lib/bisque/report_row.rb",
34
+ "spec/bisque/exceptions_spec.rb",
35
+ "spec/bisque/report_row_spec.rb",
36
+ "spec/bisque/report_spec.rb",
37
+ "spec/bisque_spec.rb",
38
+ "spec/resources/models.rb",
39
+ "spec/resources/schema.rb",
40
+ "spec/spec_helper.rb"
41
+ ]
42
+ s.homepage = "http://github.com/awebneck/bisque"
43
+ s.licenses = ["MIT"]
44
+ s.require_paths = ["lib"]
45
+ s.rubygems_version = "1.8.15"
46
+ s.summary = "A simple gem for ease of use in generating reports with ActiveRecord"
47
+
48
+ if s.respond_to? :specification_version then
49
+ s.specification_version = 3
50
+
51
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
52
+ s.add_runtime_dependency(%q<activerecord>, [">= 3.0.0"])
53
+ s.add_runtime_dependency(%q<pg>, [">= 0"])
54
+ s.add_development_dependency(%q<rspec>, ["~> 2.8.0"])
55
+ s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
56
+ s.add_development_dependency(%q<bundler>, ["~> 1.1.0"])
57
+ s.add_development_dependency(%q<jeweler>, ["~> 1.8.4"])
58
+ s.add_development_dependency(%q<simplecov>, [">= 0"])
59
+ s.add_development_dependency(%q<spork>, [">= 0"])
60
+ s.add_development_dependency(%q<pry>, [">= 0"])
61
+ else
62
+ s.add_dependency(%q<activerecord>, [">= 3.0.0"])
63
+ s.add_dependency(%q<pg>, [">= 0"])
64
+ s.add_dependency(%q<rspec>, ["~> 2.8.0"])
65
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
66
+ s.add_dependency(%q<bundler>, ["~> 1.1.0"])
67
+ s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
68
+ s.add_dependency(%q<simplecov>, [">= 0"])
69
+ s.add_dependency(%q<spork>, [">= 0"])
70
+ s.add_dependency(%q<pry>, [">= 0"])
71
+ end
72
+ else
73
+ s.add_dependency(%q<activerecord>, [">= 3.0.0"])
74
+ s.add_dependency(%q<pg>, [">= 0"])
75
+ s.add_dependency(%q<rspec>, ["~> 2.8.0"])
76
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
77
+ s.add_dependency(%q<bundler>, ["~> 1.1.0"])
78
+ s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
79
+ s.add_dependency(%q<simplecov>, [">= 0"])
80
+ s.add_dependency(%q<spork>, [">= 0"])
81
+ s.add_dependency(%q<pry>, [">= 0"])
82
+ end
83
+ end
84
+
@@ -0,0 +1,4 @@
1
+ module Bisque
2
+ class MissingParameterException < Exception; end
3
+ class MissingQueryException < Exception; end
4
+ end
@@ -0,0 +1,146 @@
1
+ module Bisque
2
+ class Report
3
+ include Enumerable
4
+
5
+ attr_accessor :params, :sql
6
+
7
+ def initialize(params={})
8
+ @params = self.class.defaults.merge params
9
+ @sql = self.class.query.dup
10
+ self.class.params.each do |param|
11
+ value = @params[param]
12
+ raise Bisque::MissingParameterException, "Missing parameter :#{param} for construction of #{self.class} - please provide a value for this parameter to the constructor or define a default." if value.nil?
13
+ @sql.gsub!(/:#{param}/, sanitize_and_sqlize(value))
14
+ end
15
+ @results = ActiveRecord::Base.connection.execute @sql
16
+ extract_datatypes
17
+ construct_converted
18
+ end
19
+
20
+ def each
21
+ if block_given?
22
+ @converted.each do |r|
23
+ yield r
24
+ end
25
+ else
26
+ Enumerator.new self, :each
27
+ end
28
+ end
29
+
30
+ def [](index)
31
+ @converted[index]
32
+ end
33
+
34
+ def last
35
+ @converted.last
36
+ end
37
+
38
+ def to_s
39
+ "#{self.class}: #{count} result#{'s' if count != 1}"
40
+ end
41
+
42
+ protected
43
+ def sanitize_and_sqlize(value)
44
+ if value.is_a? Array
45
+ "(#{value.map { |v| sanitize_and_sqlize(v) }.join(",")})"
46
+ else
47
+ ActiveRecord::Base.connection.quote(value)
48
+ end
49
+ end
50
+
51
+ def extract_datatypes
52
+ sql = "SELECT"
53
+ @results.fields.each_with_index do |f, i|
54
+ sql << " UNION SELECT" if i > 0
55
+ sql << " format_type(#{@results.ftype(i)}, #{@results.fmod(i)}) AS type, #{ActiveRecord::Base.sanitize(f)} AS key"
56
+ end
57
+ typeresults = ActiveRecord::Base.connection.execute(sql)
58
+ @types = {}
59
+ typeresults.to_a.each do |typeresult|
60
+ @types[typeresult['key'].intern] = typeresult['type']
61
+ end
62
+ end
63
+
64
+ def construct_converted
65
+ @converted = []
66
+ @results.to_a.each do |r|
67
+ @converted << self.class.row_class.new( r.merge(r) { |k, v| convert_value(k,v) })
68
+ end
69
+ end
70
+
71
+ def convert_value(key, value)
72
+ return value if value.nil?
73
+ case @types[key.intern]
74
+ when /integer/
75
+ value.to_i
76
+ when /decimal/
77
+ value.to_f
78
+ when /float/
79
+ value.to_f
80
+ when /numeric/
81
+ value.to_f
82
+ when /precision/
83
+ value.to_f
84
+ when /boolean/
85
+ value == 't'
86
+ when /date/
87
+ Date.parse value
88
+ when /timestamp/
89
+ Time.parse value
90
+ when /bytea/
91
+ ActiveRecord::Base.connection.unescape_bytea(value)
92
+ else
93
+ value.to_s
94
+ end
95
+ end
96
+
97
+ class << self
98
+ def default(key, val)
99
+ @defaults ||= {}
100
+ @defaults[key] = val
101
+ end
102
+
103
+ def defaults(hash=nil)
104
+ if hash
105
+ @defaults ||= {}
106
+ @defaults.merge! hash
107
+ else
108
+ @defaults || {}
109
+ end
110
+ end
111
+
112
+ def query(qstr=nil)
113
+ if qstr
114
+ @qstr = qstr.strip
115
+ @params = qstr.scan(/:\w+/).map { |p| p.gsub(/:/,'').intern }
116
+ else
117
+ raise Bisque::MissingQueryException, "Bisque Report #{self} missing query definition." if @qstr.nil?
118
+ @qstr
119
+ end
120
+ end
121
+
122
+ def rows(&block)
123
+ if block_given?
124
+ carray = self.to_s.split(/::/)
125
+ carray.last << 'Row'
126
+ @row_class = carray.join('::')
127
+ c = Class.new(Bisque::ReportRow)
128
+ c.class_eval(&block)
129
+ Object.const_set @row_class, c
130
+ end
131
+ end
132
+
133
+ def row_class
134
+ return Bisque::ReportRow if @row_class.nil?
135
+ names = @row_class.split('::')
136
+ names.reduce(Object) do |mod, name|
137
+ mod.const_defined?(name) ? mod.const_get(name) : mod.const_missing(name)
138
+ end
139
+ end
140
+
141
+ def params
142
+ @params || []
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,12 @@
1
+ module Bisque
2
+ class ReportRow
3
+ def initialize(hash)
4
+ @fields = hash
5
+ hash.keys.each do |k|
6
+ unless self.respond_to? k
7
+ self.singleton_class.send(:define_method, k) { @fields[k] }
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
data/lib/bisque.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'bisque/exceptions'
2
+ require 'bisque/report_row'
3
+ require 'bisque/report'
4
+
5
+ module Bisque
6
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Bisque Exceptions" do
4
+ describe Bisque::MissingQueryException do
5
+ it "should be an Exception" do
6
+ Bisque::MissingQueryException.ancestors.should include Exception
7
+ end
8
+ end
9
+ describe Bisque::MissingParameterException do
10
+ it "should be an Exception" do
11
+ Bisque::MissingQueryException.ancestors.should include Exception
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,64 @@
1
+ require 'spec_helper'
2
+
3
+ describe Bisque::ReportRow do
4
+ before :all do
5
+ Frobnitz.create :name => 'Howdy',
6
+ :description => 'Here is some text',
7
+ :score => 12,
8
+ :cost => 48.22,
9
+ :numberish => Math::PI,
10
+ :timish => Time.now - (60*60*24*3),
11
+ :summed_at => Time.now + (60*60*24*3),
12
+ :created_on => Date.new(2012),
13
+ :boolish => true,
14
+ :binny => "\x00\x01\x02\x03"
15
+ Frobnitz.create :name => 'Slam',
16
+ :description => 'Here is some other text',
17
+ :score => 8,
18
+ :cost => 12.53,
19
+ :numberish => Math::E,
20
+ :timish => Time.now + (60*60*24*3),
21
+ :summed_at => Time.now - (60*60*24*3),
22
+ :created_on => Date.new(2011),
23
+ :boolish => false,
24
+ :binny => "\x03\x02\x01\x00"
25
+ end
26
+
27
+ describe "construction" do
28
+ it "should be an instance of Bisque::ReportRow if the rows block is undefined" do
29
+ f = FooReport.new
30
+ f.first.should be_a Bisque::ReportRow
31
+ end
32
+
33
+ it "should be an instance of the dynamically created Row class if the rows block is defined" do
34
+ f = FooeyReport.new :corn => 'hat'
35
+ f.first.should be_a FooeyReportRow
36
+ end
37
+ end
38
+
39
+ describe "instance methods" do
40
+ it "should provide an accessor method for each field in the report" do
41
+ f = FooReport.new
42
+ r = f.first
43
+ r.name.should == 'Howdy'
44
+ r.description.should == 'Here is some text'
45
+ r.score.should == 12
46
+ r.cost.should == 48.22
47
+ ((Math::PI - r.numberish).abs < 0.001).should == true
48
+ r.timish.should_not be_nil
49
+ r.summed_at.should_not be_nil
50
+ r.created_on.should_not be_nil
51
+ r.boolish.should == true
52
+ r.binny.should == "\x00\x01\x02\x03"
53
+ end
54
+
55
+ it "should respond to calls defined in the rows block" do
56
+ f = FooeyReport.new :corn => 'hat'
57
+ f.first.whoop.should == 'silly'
58
+ end
59
+ end
60
+
61
+ after(:all) do
62
+ Frobnitz.destroy_all
63
+ end
64
+ end
@@ -0,0 +1,181 @@
1
+ require 'spec_helper'
2
+
3
+ describe Bisque::Report do
4
+ describe "class methods" do
5
+ describe "query" do
6
+ it "should return the query specified in the class" do
7
+ FooReport.query.should == "SELECT * FROM frobnitzs"
8
+ BarReport.query.should == "SELECT * FROM frobnitzs WHERE name = :corn"
9
+ end
10
+
11
+ it "should raise a Bisque::MissingQueryException if no query is defined" do
12
+ lambda { NoQueryReport.query }.should raise_error Bisque::MissingQueryException
13
+ end
14
+ end
15
+
16
+ describe "params" do
17
+ it "should return an empty array if no parameters exist" do
18
+ FooReport.params.should be_a Array
19
+ FooReport.params.should be_empty
20
+ end
21
+
22
+ it "should return a list of the parameters extracted from the query if present" do
23
+ FooReport.params.should be_a Array
24
+ BarReport.params.length.should == 1
25
+ BarReport.params.should include :corn
26
+ end
27
+ end
28
+
29
+ describe "defaults" do
30
+ it "should return an empty hash if no defaults exist" do
31
+ BarReport.defaults.should be_a Hash
32
+ BarReport.defaults.should be_empty
33
+ end
34
+
35
+ it "should return an empty hash of default values specified by the defaults class method" do
36
+ BazReport.defaults.should be_a Hash
37
+ BazReport.defaults[:corn].should == 'hat'
38
+ end
39
+
40
+ it "should return an empty hash of default values specified by the default class method" do
41
+ QuuxReport.defaults.should be_a Hash
42
+ QuuxReport.defaults[:slip].should == 'paper'
43
+ end
44
+ end
45
+
46
+ describe "row_class" do
47
+ it "should return Bisque::ReportRow if no rows block is defined" do
48
+ FooReport.row_class.should == Bisque::ReportRow
49
+ end
50
+
51
+ it "should return the dynamic class defined by the report's class if a rows block is defined" do
52
+ FunReport.row_class.should == FunReportRow
53
+ end
54
+ end
55
+ end
56
+
57
+ describe "behaviors" do
58
+ it "should be Enumerable" do
59
+ FooReport.should include Enumerable
60
+ end
61
+ end
62
+
63
+ describe "instance methods" do
64
+ before :all do
65
+ Frobnitz.create :name => 'Howdy',
66
+ :description => 'Here is some text',
67
+ :score => 12,
68
+ :cost => 48.22,
69
+ :numberish => Math::PI,
70
+ :timish => Time.now - (60*60*24*3),
71
+ :summed_at => Time.now + (60*60*24*3),
72
+ :created_on => Date.new(2012),
73
+ :boolish => true,
74
+ :binny => "\x00\x01\x02\x03"
75
+ Frobnitz.create :name => 'Slam',
76
+ :description => 'Here is some other text',
77
+ :score => 8,
78
+ :cost => 12.53,
79
+ :numberish => Math::E,
80
+ :timish => Time.now + (60*60*24*3),
81
+ :summed_at => Time.now - (60*60*24*3),
82
+ :created_on => Date.new(2011),
83
+ :boolish => false,
84
+ :binny => "\x03\x02\x01\x00"
85
+ end
86
+
87
+ describe "construction" do
88
+ it "should be constructable with no arguments if the query designates no parameters" do
89
+ lambda { FooReport.new }.should_not raise_error
90
+ end
91
+
92
+ it "should be constructable with no arguments if the query designates parameters and defaults therefor" do
93
+ lambda { BazReport.new }.should_not raise_error
94
+ end
95
+
96
+ it "should raise a Bisque::MissingParameterException if the query designates parameters but no defaults, and it is constructed with no arguments" do
97
+ lambda { BarReport.new }.should raise_error Bisque::MissingParameterException
98
+ end
99
+
100
+ it "should accept a hash of parameters" do
101
+ lambda { BarReport.new :corn => 'cheese' }.should_not raise_error
102
+ end
103
+ end
104
+
105
+ describe "to_s" do
106
+ it "should return the name of the class and a count of resulting rows" do
107
+ f = FooReport.new
108
+ f.to_s.should == 'FooReport: 2 results'
109
+ end
110
+ end
111
+
112
+ describe "each" do
113
+ it "should return an enumerator of the results if called without a block" do
114
+ f = FooReport.new
115
+ e = f.each
116
+ e.should be_a Enumerator
117
+ e.count.should == 2
118
+ end
119
+
120
+ it "should loop over the results, passing each result to the block if called with a block" do
121
+ f = FooReport.new
122
+ i = 0
123
+ f.each do |r|
124
+ i += 1
125
+ end
126
+ i.should == 2
127
+ end
128
+ end
129
+
130
+ describe "[]" do
131
+ it "should allow array access to the results" do
132
+ f = FooReport.new
133
+ f[0].should be_a Bisque::ReportRow
134
+ f[1].should be_a Bisque::ReportRow
135
+ f[0].should_not == f[1]
136
+ end
137
+ end
138
+
139
+ describe "last" do
140
+ it "should return the last result" do
141
+ f = FooReport.new
142
+ f.last.should == f[1]
143
+ end
144
+ end
145
+
146
+ describe "params" do
147
+ it "should return the hash of specified parameters if there are no defaults" do
148
+ b = BarReport.new :corn => 'chili'
149
+ b.params.should == {:corn => 'chili'}
150
+ end
151
+
152
+ it "should return the hash of defaults overridden with specified parameters if defaults are defind" do
153
+ b = BazReport.new
154
+ b.params.should == {:corn => 'hat'}
155
+ b = BazReport.new :corn => 'powder'
156
+ b.params.should == {:corn => 'powder'}
157
+ end
158
+ end
159
+
160
+ describe "sql" do
161
+ it "should return the report's query with the parameters interpolated thereto" do
162
+ p = ParamReport.new :name => 'test',
163
+ :description => 'testagain',
164
+ :score => 12,
165
+ :cost => 34.12,
166
+ :numberish => 123.35583,
167
+ :created_at => Time.new(2012,1,1,5,0,0),
168
+ :timish => Time.new(2012,1,1,6,0,0),
169
+ :summed_at => Time.new(2012,1,1,7,0,0),
170
+ :created_on => Date.new(2012,2,1),
171
+ :boolish => true,
172
+ :binny => "\x00\x01\x02\x03"
173
+ p.sql.should == "SELECT * FROM frobnitzs WHERE name = 'test' AND description = 'testagain' AND score = 12 AND cost = 34.12 AND numberish = 123.35583 AND created_at = '2012-01-01 05:00:00.000000' AND timish = '2012-01-01 06:00:00.000000' AND summed_at = '2012-01-01 07:00:00.000000' AND created_on = '2012-02-01' AND boolish = 't' AND binny = ''"
174
+ end
175
+ end
176
+
177
+ after(:all) do
178
+ Frobnitz.destroy_all
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe Bisque do
4
+ it "should define the Bisque::Report class" do
5
+ lambda { Bisque::Report }.should_not raise_error
6
+ end
7
+ it "should define the Bisque::ReportRow class" do
8
+ lambda { Bisque::ReportRow }.should_not raise_error
9
+ end
10
+ it "should define the Bisque::MissingQueryException class" do
11
+ lambda { Bisque::MissingQueryException }.should_not raise_error
12
+ end
13
+ it "should define the Bisque::MissingParameterException class" do
14
+ lambda { Bisque::MissingParameterException }.should_not raise_error
15
+ end
16
+ end
@@ -0,0 +1,46 @@
1
+ class Frobnitz < ActiveRecord::Base
2
+ end
3
+
4
+ class NoQueryReport < Bisque::Report
5
+ end
6
+
7
+ class FooReport < Bisque::Report
8
+ query "SELECT * FROM frobnitzs"
9
+ end
10
+
11
+ class FooeyReport < Bisque::Report
12
+ query "SELECT * FROM frobnitzs"
13
+ rows do
14
+ def whoop
15
+ 'silly'
16
+ end
17
+ end
18
+ end
19
+
20
+ class BarReport < Bisque::Report
21
+ query "SELECT * FROM frobnitzs WHERE name = :corn"
22
+ end
23
+
24
+ class BazReport < Bisque::Report
25
+ query "SELECT * FROM frobnitzs WHERE name = :corn"
26
+ defaults :corn => 'hat'
27
+ end
28
+
29
+ class QuuxReport < Bisque::Report
30
+ query "SELECT * FROM frobnitzs WHERE name = :corn"
31
+ default :slip, 'paper'
32
+ end
33
+
34
+ class FunReport < Bisque::Report
35
+ query "SELECT * FROM frobnitzs WHERE name = :corn"
36
+ default :slip, 'paper'
37
+ rows do
38
+ def whoop
39
+ 'silly'
40
+ end
41
+ end
42
+ end
43
+
44
+ class ParamReport < Bisque::Report
45
+ query "SELECT * FROM frobnitzs WHERE name = :name AND description = :description AND score = :score AND cost = :cost AND numberish = :numberish AND created_at = :created_at AND timish = :timish AND summed_at = :summed_at AND created_on = :created_on AND boolish = :boolish AND binny = :binny"
46
+ end
@@ -0,0 +1,15 @@
1
+ ActiveRecord::Schema.define(:version => 0) do
2
+ create_table :frobnitzs, :force => true do |t|
3
+ t.column :name, :string
4
+ t.column :description, :text
5
+ t.column :score, :integer
6
+ t.column :cost, :decimal, :precision => 5, :scale => 2
7
+ t.column :numberish, :float
8
+ t.column :created_at, :datetime
9
+ t.column :timish, :timestamp
10
+ t.column :summed_at, :time
11
+ t.column :created_on, :date
12
+ t.column :boolish, :boolean
13
+ t.column :binny, :binary
14
+ end
15
+ end
@@ -0,0 +1,63 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'active_record'
5
+ require 'fileutils'
6
+ require 'bisque'
7
+ require 'pry'
8
+
9
+ DB_USER = 'bisque'
10
+ DB_PASS = 'bisquepass1234'
11
+
12
+ # Requires supporting files with custom matchers and macros, etc,
13
+ # in ./support/ and its subdirectories.
14
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
15
+
16
+ def stfu
17
+ begin
18
+ orig_stderr = $stderr.clone
19
+ orig_stdout = $stdout.clone
20
+ $stderr.reopen File.new('/dev/null', 'w')
21
+ $stdout.reopen File.new('/dev/null', 'w')
22
+ retval = yield
23
+ rescue Exception => e
24
+ $stdout.reopen orig_stdout
25
+ $stderr.reopen orig_stderr
26
+ raise e
27
+ ensure
28
+ $stdout.reopen orig_stdout
29
+ $stderr.reopen orig_stderr
30
+ end
31
+ retval
32
+ end
33
+
34
+ RSpec.configure do |config|
35
+ dir = File.dirname(__FILE__)
36
+ dbconfig = {
37
+ 'adapter' => 'postgresql',
38
+ 'encoding' => 'unicode',
39
+ 'database' => 'bisque_development',
40
+ 'pool' => 5,
41
+ 'username' => DB_USER,
42
+ 'password' => DB_PASS}
43
+
44
+ config.before(:all) do
45
+ ENV["RAILS_ENV"] ||= "test"
46
+ ActiveRecord::Base.establish_connection dbconfig.merge 'database' => 'postgres', 'schema_search_path' => 'public'
47
+ ActiveRecord::Base.connection.create_database dbconfig['database'], dbconfig
48
+ ActiveRecord::Base.remove_connection
49
+ ActiveRecord::Base.configurations = {'test' => dbconfig}
50
+ ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test'])
51
+ ActiveRecord::Migration.verbose = false
52
+ stfu do
53
+ load "#{dir}/resources/schema.rb"
54
+ load "#{dir}/resources/models.rb"
55
+ end
56
+ end
57
+
58
+ config.after(:all) do
59
+ ActiveRecord::Base.remove_connection
60
+ ActiveRecord::Base.establish_connection dbconfig.merge 'database' => 'postgres', 'schema_search_path' => 'public'
61
+ ActiveRecord::Base.connection.drop_database dbconfig['database']
62
+ end
63
+ end
metadata ADDED
@@ -0,0 +1,172 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bisque
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jeremy Holland
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-01 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activerecord
16
+ requirement: &11122500 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 3.0.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *11122500
25
+ - !ruby/object:Gem::Dependency
26
+ name: pg
27
+ requirement: &11120660 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *11120660
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec
38
+ requirement: &11118060 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 2.8.0
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *11118060
47
+ - !ruby/object:Gem::Dependency
48
+ name: rdoc
49
+ requirement: &11115660 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '3.12'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *11115660
58
+ - !ruby/object:Gem::Dependency
59
+ name: bundler
60
+ requirement: &11552740 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ~>
64
+ - !ruby/object:Gem::Version
65
+ version: 1.1.0
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *11552740
69
+ - !ruby/object:Gem::Dependency
70
+ name: jeweler
71
+ requirement: &11546320 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ~>
75
+ - !ruby/object:Gem::Version
76
+ version: 1.8.4
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *11546320
80
+ - !ruby/object:Gem::Dependency
81
+ name: simplecov
82
+ requirement: &11036180 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: *11036180
91
+ - !ruby/object:Gem::Dependency
92
+ name: spork
93
+ requirement: &11035140 !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ type: :development
100
+ prerelease: false
101
+ version_requirements: *11035140
102
+ - !ruby/object:Gem::Dependency
103
+ name: pry
104
+ requirement: &11032380 !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: *11032380
113
+ description: Bisque exists to ease the organizational pain of having to attach reporting
114
+ and ad-hoc queries to ActiveRecord models in order to get any class-like behavior
115
+ out of them by providing a new class and DSL for that express purpose.
116
+ email: jeremy@jeremypholland.com
117
+ executables: []
118
+ extensions: []
119
+ extra_rdoc_files:
120
+ - LICENSE.txt
121
+ - README.rdoc
122
+ files:
123
+ - .document
124
+ - .rspec
125
+ - .rvmrc
126
+ - Gemfile
127
+ - Gemfile.lock
128
+ - LICENSE.txt
129
+ - README.rdoc
130
+ - Rakefile
131
+ - VERSION
132
+ - bisque.gemspec
133
+ - lib/bisque.rb
134
+ - lib/bisque/exceptions.rb
135
+ - lib/bisque/report.rb
136
+ - lib/bisque/report_row.rb
137
+ - spec/bisque/exceptions_spec.rb
138
+ - spec/bisque/report_row_spec.rb
139
+ - spec/bisque/report_spec.rb
140
+ - spec/bisque_spec.rb
141
+ - spec/resources/models.rb
142
+ - spec/resources/schema.rb
143
+ - spec/spec_helper.rb
144
+ homepage: http://github.com/awebneck/bisque
145
+ licenses:
146
+ - MIT
147
+ post_install_message:
148
+ rdoc_options: []
149
+ require_paths:
150
+ - lib
151
+ required_ruby_version: !ruby/object:Gem::Requirement
152
+ none: false
153
+ requirements:
154
+ - - ! '>='
155
+ - !ruby/object:Gem::Version
156
+ version: '0'
157
+ segments:
158
+ - 0
159
+ hash: -468949534080404133
160
+ required_rubygems_version: !ruby/object:Gem::Requirement
161
+ none: false
162
+ requirements:
163
+ - - ! '>='
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ requirements: []
167
+ rubyforge_project:
168
+ rubygems_version: 1.8.15
169
+ signing_key:
170
+ specification_version: 3
171
+ summary: A simple gem for ease of use in generating reports with ActiveRecord
172
+ test_files: []