dossier 2.12.2 → 2.13.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +31 -6
  3. data/Rakefile +12 -5
  4. data/VERSION +1 -0
  5. data/app/controllers/dossier/reports_controller.rb +1 -1
  6. data/app/views/dossier/reports/show.html.haml +1 -1
  7. data/lib/dossier.rb +1 -1
  8. data/lib/dossier/client.rb +4 -1
  9. data/lib/dossier/configuration.rb +10 -5
  10. data/lib/dossier/{naming.rb → model.rb} +10 -2
  11. data/lib/dossier/multi_report.rb +1 -1
  12. data/lib/dossier/query.rb +1 -1
  13. data/lib/dossier/report.rb +13 -10
  14. data/lib/dossier/responder.rb +2 -0
  15. data/lib/dossier/result.rb +29 -12
  16. data/lib/dossier/version.rb +1 -1
  17. data/lib/generators/dossier/views/templates/show.html.haml +1 -1
  18. data/spec/dossier/adapter/active_record_spec.rb +4 -4
  19. data/spec/dossier/client_spec.rb +19 -19
  20. data/spec/dossier/configuration_spec.rb +29 -9
  21. data/spec/dossier/{naming_spec.rb → model_spec.rb} +6 -1
  22. data/spec/dossier/query_spec.rb +12 -12
  23. data/spec/dossier/report_spec.rb +7 -7
  24. data/spec/dossier/responder_spec.rb +5 -5
  25. data/spec/dossier/result_spec.rb +5 -5
  26. data/spec/dossier/stream_csv_spec.rb +5 -3
  27. data/spec/dossier/version_spec.rb +8 -0
  28. data/spec/dossier_spec.rb +4 -4
  29. data/spec/dummy/Rakefile +4 -0
  30. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  31. data/spec/dummy/app/reports/cats/are/super_fun_report.rb +13 -1
  32. data/spec/dummy/app/reports/employee_report.rb +14 -1
  33. data/spec/dummy/app/reports/employee_with_custom_view_report.rb +5 -1
  34. data/spec/dummy/bin/rails +4 -0
  35. data/spec/dummy/bin/rake +4 -0
  36. data/spec/dummy/config/application.rb +30 -0
  37. data/spec/dummy/config/database.yml +6 -5
  38. data/spec/dummy/db/{test.sqlite3 → dossier_test.sqlite3} +0 -0
  39. data/spec/dummy/log/development.log +0 -0
  40. data/spec/dummy/log/test.log +21983 -0
  41. data/spec/features/employee_spec.rb +13 -4
  42. data/spec/fixtures/db/postgresql.yml +4 -0
  43. data/spec/fixtures/db/postgresql.yml.example +5 -0
  44. data/spec/fixtures/db/postgresql.yml.travis +4 -0
  45. data/spec/fixtures/db/sqlite3.yml +1 -1
  46. data/spec/fixtures/reports/employee.csv +3 -3
  47. data/spec/fixtures/reports/employee.xls +3 -3
  48. data/spec/generators/dossier/views/views_spec.rb +17 -10
  49. data/spec/routing/dossier_routes_spec.rb +1 -1
  50. data/spec/spec_helper.rb +7 -10
  51. data/spec/support/factory.rb +38 -0
  52. metadata +57 -44
  53. data/spec/dummy/config/database.yml.example +0 -13
  54. data/spec/dummy/config/database.yml.travis +0 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2e46d866c27e0bcc065d6fd0e0912ed6b1ecbb72
4
- data.tar.gz: a76bd7e9487ae6de1114b5b9aa2ac32d616d11cd
3
+ metadata.gz: ebb6c964daa51f25b6c3b0856cd7e9d1006ed2b0
4
+ data.tar.gz: 700abc7cba17e2218185f16bd51ace2711a493cb
5
5
  SHA512:
6
- metadata.gz: 1a333bec0fa762f56180e6d2e6916dbd2043ee8d0dbc634d114a74a178702558e8d30afb22e22292fa46cdd3ec97fa98e25f3d7915487d6fb9e5216b0695fef4
7
- data.tar.gz: 7ac2295c362089079c2010b20dea42c600f0c23dc8208a1d7040fd0d9d2308bc6646b17dc8e0340228054314a2639d6d98498fd7d129000e5cc90c947ae7fba0
6
+ metadata.gz: f31cf8a46a495fc85aa2dbd89e467a463044e8d1fdb1712602c497d66c1162be21133ef4362a8679a991aa588f6c3319a8aa8925c249fa9698eaff70c57ba791
7
+ data.tar.gz: f7e0953538b0091ba7c2c40c4494e91a65032a01d189e7d3366ffeef5dd2a631c12c4a45d759eb0765a29c9bedbf2443076b49bf272b656049981c37430c02c3
data/README.md CHANGED
@@ -5,11 +5,11 @@ Dossier is a Rails engine that turns SQL into reports. Reports can be easily ren
5
5
  - If you **hate** SQL, you can use whatever tool you like to generate it; for example, ActiveRecord's `to_sql`.
6
6
  - If you **love** SQL, you can use every feature your database supports.
7
7
 
8
- [![Gem Version](https://badge.fury.io/rb/dossier.png)](https://rubygems.org/gems/dossier)
9
- [![Code Climate](https://codeclimate.com/github/adamhunter/dossier.png)](https://codeclimate.com/github/adamhunter/dossier)
10
- [![Build Status](https://travis-ci.org/adamhunter/dossier.png?branch=master)](https://travis-ci.org/adamhunter/dossier)
11
- [![Coverage Status](https://coveralls.io/repos/adamhunter/dossier/badge.png?branch=master)](https://coveralls.io/r/adamhunter/dossier?branch=master)
12
- [![Dependency Status](https://gemnasium.com/adamhunter/dossier.png)](https://gemnasium.com/adamhunter/dossier)
8
+ [![Gem Version](https://badge.fury.io/rb/dossier.svg)](http://badge.fury.io/rb/dossier)
9
+ [![Code Climate](https://codeclimate.com/github/tma1/dossier/badges/gpa.svg)](https://codeclimate.com/github/tma1/dossier)
10
+ [![Build Status](https://travis-ci.org/tma1/dossier.svg?branch=master)](https://travis-ci.org/tma1/dossier)
11
+ [![Coverage Status](https://coveralls.io/repos/tma1/dossier/badge.svg?branch=master&service=github)](https://coveralls.io/github/tma1/dossier?branch=master)
12
+ [![Dependency Status](https://gemnasium.com/tma1/dossier.svg)](https://gemnasium.com/tma1/dossier)
13
13
 
14
14
  ## Setup
15
15
 
@@ -137,6 +137,27 @@ class MoneyLaunderingReport < Dossier::Report
137
137
  end
138
138
  ```
139
139
 
140
+ ## Hidden Columns
141
+
142
+ You may override `display_column?` in your report class in order to hide columns from the formatted results. For instance, you might select an employee's ID and name in order to generate a link from their name to their profile page, without actually displaying the ID value itself:
143
+
144
+ ```ruby
145
+ class EmployeeReport < Dossier::Report
146
+ # ...
147
+
148
+ def display_column?(name)
149
+ name != 'id'
150
+ end
151
+
152
+ def format_name(value, row)
153
+ url = formatter.url_formatter.url_helpers.employee_path(row['id'])
154
+ formatter.url_formatter.link_to(value, url)
155
+ end
156
+ end
157
+ ```
158
+
159
+ By default, all selected columns are displayed.
160
+
140
161
  ## Report Options and Footers
141
162
 
142
163
  You may want to specify parameters for a report: which columns to show, a range of dates, etc. Dossier supports this via URL parameters, anything in `params[:options]` will be passed into your report's `initialize` method and made available via the `options` reader.
@@ -160,9 +181,13 @@ It's up to you to use these options in generating your SQL query.
160
181
 
161
182
  However, Dossier does support one URL parameter natively: if you supply a `footer` parameter with an integer value, the last N rows will be accesible via `report.results.footers` instead of `report.results.body`. The built-in `show` view renders those rows inside an HTML footer. This is an easy way to display a totals row or something similar.
162
183
 
184
+ ## Styling
185
+
186
+ The default report views use a `<table class="dossier report">` for easy CSS styling.
187
+
163
188
  ## Additional View Customization
164
189
 
165
- To further customize your results view, run the the generator provided. The default will provide 'app/views/dossier/reports/show'.
190
+ To further customize your results view, run the generator provided. The default will provide 'app/views/dossier/reports/show'.
166
191
 
167
192
  ```ruby
168
193
  rails generate dossier:views
data/Rakefile CHANGED
@@ -1,13 +1,20 @@
1
- #!/usr/bin/env rake
2
-
3
1
  begin
4
2
  require 'bundler/setup'
5
3
  rescue LoadError
6
4
  puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
5
  end
8
6
 
9
- Bundler::GemHelper.install_tasks
7
+ require 'rdoc/task'
10
8
 
11
- require 'rails/all'
12
- require 'dummy/application/tasks'
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Dossier'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
13
16
 
17
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+ Bundler::GemHelper.install_tasks
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 2.13.1
@@ -17,7 +17,7 @@ module Dossier
17
17
  private
18
18
 
19
19
  def report_class
20
- Dossier::Naming.name_to_class(params[:report])
20
+ Dossier::Model.name_to_class(params[:report])
21
21
  end
22
22
 
23
23
  def report
@@ -4,7 +4,7 @@
4
4
 
5
5
  = render_options(report)
6
6
 
7
- %table
7
+ %table.dossier.report
8
8
  %thead
9
9
  %tr
10
10
  - report.results.headers.each do |header|
@@ -1,5 +1,5 @@
1
1
  require "dossier/engine"
2
- require "dossier/naming"
2
+ require "dossier/model"
3
3
  require "dossier/view_context_with_report_formatter"
4
4
  require "dossier/version"
5
5
 
@@ -7,7 +7,10 @@ module Dossier
7
7
 
8
8
  def initialize(options)
9
9
  self.options = options.symbolize_keys
10
- self.adapter = dossier_adapter.new(self.options.except(:dossier_adapter))
10
+ end
11
+
12
+ def adapter
13
+ @adapter ||= dossier_adapter.new(self.options.except(:dossier_adapter))
11
14
  end
12
15
 
13
16
  def dossier_adapter
@@ -4,6 +4,8 @@ require 'yaml'
4
4
  module Dossier
5
5
  class Configuration
6
6
 
7
+ DB_KEY = 'DATABASE_URL'.freeze
8
+
7
9
  attr_accessor :config_path, :client
8
10
 
9
11
  def initialize
@@ -12,25 +14,28 @@ module Dossier
12
14
  end
13
15
 
14
16
  def connection_options
15
- yaml_config.merge(dburl_config || {})
17
+ yaml_config.merge(dburl_config || {}).presence || raise_empty_conn_config
16
18
  end
17
19
 
18
20
  def yaml_config
19
- YAML.load(ERB.new(File.read(@config_path)).result)[Rails.env].symbolize_keys
21
+ YAML.load(ERB.new(File.read(config_path)).result)[Rails.env].symbolize_keys
22
+ rescue Errno::ENOENT
23
+ {}
20
24
  end
21
25
 
22
26
  def dburl_config
23
- Dossier::ConnectionUrl.new.to_hash if ENV.has_key? "DATABASE_URL"
27
+ Dossier::ConnectionUrl.new.to_hash if ENV.has_key? DB_KEY
24
28
  end
25
29
 
26
30
  private
27
31
 
28
32
  def setup_client!
29
33
  @client = Dossier::Client.new(connection_options)
34
+ end
30
35
 
31
- rescue Errno::ENOENT => e
36
+ def raise_empty_conn_config
32
37
  raise ConfigurationMissingError.new(
33
- "#{e.message}. #{@config_path} must exist for Dossier to connect to the database."
38
+ "Your connection options are blank, you are missing both #{config_path} and ENV['#{DB_KEY}']"
34
39
  )
35
40
  end
36
41
 
@@ -1,5 +1,5 @@
1
1
  module Dossier
2
- module Naming
2
+ module Model
3
3
 
4
4
  # not using ActiveSupport::Concern because ClassMethods
5
5
  # must be extended after ActiveModel::Naming
@@ -28,11 +28,19 @@ module Dossier
28
28
  report_name
29
29
  end
30
30
 
31
+ def to_model
32
+ self
33
+ end
34
+
35
+ def persisted?
36
+ true
37
+ end
38
+
31
39
  delegate :report_name, :formatted_title, to: "self.class"
32
40
 
33
41
  module ClassMethods
34
42
  def report_name
35
- Dossier::Naming.class_to_name(self)
43
+ Dossier::Model.class_to_name(self)
36
44
  end
37
45
 
38
46
  def formatted_title
@@ -1,5 +1,5 @@
1
1
  class Dossier::MultiReport
2
- include Dossier::Naming
2
+ include Dossier::Model
3
3
 
4
4
  attr_accessor :options
5
5
 
@@ -15,7 +15,7 @@ module Dossier
15
15
  private
16
16
 
17
17
  def compile
18
- string.gsub(/\w*\:[a-z]{1}\w*/) { |match| escape(report.public_send(match[1..-1])) }
18
+ string.gsub(/\w*(?<!:):(?!:)[a-z]{1}\w*/) { |match| escape(report.public_send(match[1..-1])) }
19
19
  end
20
20
 
21
21
  def escape(value)
@@ -1,6 +1,6 @@
1
1
  module Dossier
2
2
  class Report
3
- include Dossier::Naming
3
+ include Dossier::Model
4
4
  include ActiveSupport::Callbacks
5
5
 
6
6
  define_callbacks :build_query, :execute
@@ -38,13 +38,13 @@ module Dossier
38
38
  end
39
39
 
40
40
  def results
41
- execute unless defined?(@results)
42
- @results
41
+ execute unless query_results
42
+ @results ||= Result::Formatted.new(query_results, self)
43
43
  end
44
44
 
45
45
  def raw_results
46
- execute unless defined?(@raw_results)
47
- @raw_results
46
+ execute unless query_results
47
+ @raw_results ||= Result::Unformatted.new(query_results, self)
48
48
  end
49
49
 
50
50
  def run
@@ -59,6 +59,10 @@ module Dossier
59
59
  value
60
60
  end
61
61
 
62
+ def display_column?(column)
63
+ true
64
+ end
65
+
62
66
  def dossier_client
63
67
  Dossier.client
64
68
  end
@@ -78,15 +82,14 @@ module Dossier
78
82
  def execute
79
83
  build_query
80
84
  run_callbacks :execute do
81
- self.results = dossier_client.execute(query, self.class.name)
85
+ self.query_results = dossier_client.execute(query, self.class.name)
82
86
  end
83
87
  end
84
88
 
85
- def results=(results)
86
- results.freeze
87
- @raw_results = Result::Unformatted.new(results, self)
88
- @results = Result::Formatted.new(results, self)
89
+ def query_results=(query_results)
90
+ @query_results = query_results.freeze
89
91
  end
92
+ attr_reader :query_results
90
93
 
91
94
  end
92
95
  end
@@ -1,3 +1,5 @@
1
+ require 'responders' unless defined? ::ActionController::Responder
2
+
1
3
  module Dossier
2
4
  class Responder < ::ActionController::Responder
3
5
  alias :report :resource
@@ -52,7 +52,11 @@ module Dossier
52
52
  class Formatted < Result
53
53
 
54
54
  def headers
55
- @formatted_headers ||= raw_headers.map { |h| report.format_header(h) }
55
+ @formatted_headers ||= raw_headers.select { |h|
56
+ report.display_column?(h)
57
+ }.map { |h|
58
+ report.format_header(h)
59
+ }
56
60
  end
57
61
 
58
62
  def each
@@ -63,19 +67,32 @@ module Dossier
63
67
  unless row.kind_of?(Enumerable)
64
68
  raise ArgumentError.new("#{row.inspect} must be a kind of Enumerable")
65
69
  end
70
+
71
+ displayable_columns(row).map { |value, i|
72
+ column = raw_headers.at(i)
73
+ apply_formatter(row, column, value)
74
+ }
75
+ end
76
+
77
+ private
66
78
 
67
- row.each_with_index.map do |value, i|
79
+ def displayable_columns(row)
80
+ row.each_with_index.select { |value, i|
68
81
  column = raw_headers.at(i)
69
- method = "format_#{column}"
70
-
71
- if report.respond_to?(method)
72
- args = [method, value]
73
- # Provide the row as context if the formatter takes two arguments
74
- args << row_hash(row) if report.method(method).arity == 2
75
- report.public_send(*args)
76
- else
77
- report.format_column(column, value)
78
- end
82
+ report.display_column?(column)
83
+ }
84
+ end
85
+
86
+ def apply_formatter(row, column, value)
87
+ method = "format_#{column}"
88
+
89
+ if report.respond_to?(method)
90
+ args = [method, value]
91
+ # Provide the row as context if the formatter takes two arguments
92
+ args << row_hash(row) if report.method(method).arity == 2
93
+ report.public_send(*args)
94
+ else
95
+ report.format_column(column, value)
79
96
  end
80
97
  end
81
98
  end
@@ -1,3 +1,3 @@
1
1
  module Dossier
2
- VERSION = "2.12.2"
2
+ VERSION = File.read(File.expand_path '../../../VERSION', __FILE__).chomp
3
3
  end
@@ -4,7 +4,7 @@
4
4
 
5
5
  = render_options(report)
6
6
 
7
- %table
7
+ %table.dossier.report
8
8
  %thead
9
9
  %tr
10
10
  - report.results.headers.each do |header|
@@ -16,7 +16,7 @@ describe Dossier::Adapter::ActiveRecord do
16
16
  end
17
17
 
18
18
  it "returns the connection's escaped value" do
19
- ar_connection.stub(:quote).and_return(clean_value)
19
+ allow(ar_connection).to receive(:quote).and_return(clean_value)
20
20
  expect(adapter.escape(dirty_value)).to eq(clean_value)
21
21
  end
22
22
 
@@ -34,18 +34,18 @@ describe Dossier::Adapter::ActiveRecord do
34
34
  end
35
35
 
36
36
  it "builds an adapter result" do
37
- ar_connection.stub(:exec_query).and_return(connection_results)
37
+ allow(ar_connection).to receive(:exec_query).and_return(connection_results)
38
38
  expect(adapter_result_class).to receive(:new).with(connection_results)
39
39
  adapter.execute(:query)
40
40
  end
41
41
 
42
42
  it "returns the adapter result" do
43
- ar_connection.stub(:exec_query).and_return(connection_results)
43
+ allow(ar_connection).to receive(:exec_query).and_return(connection_results)
44
44
  expect(adapter.execute(:query)).to be_a(adapter_result_class)
45
45
  end
46
46
 
47
47
  it "rescues any errors and raises a Dossier::ExecuteError" do
48
- ar_connection.stub(:exec_query).and_raise(StandardError.new('wat'))
48
+ allow(ar_connection).to receive(:exec_query).and_raise(StandardError.new('wat'))
49
49
  expect{ adapter.execute(:query) }.to raise_error(Dossier::ExecuteError)
50
50
  end
51
51
 
@@ -32,48 +32,48 @@ describe Dossier::Client do
32
32
 
33
33
  it "uses an adapter by that name" do
34
34
  expect(Dossier::Adapter::SpecAdapter).to receive(:new).with(username: 'Timmy')
35
- described_class.new(dossier_adapter: 'spec_adapter', username: 'Timmy')
35
+ described_class.new(dossier_adapter: 'spec_adapter', username: 'Timmy').adapter
36
36
  end
37
37
 
38
38
  end
39
39
 
40
40
  context "when not given a connection or a dossier_adapter option" do
41
41
 
42
- let(:client) { described_class.new(username: 'Jimmy') }
42
+ let(:loaded_orms) { raise 'implement in nested describe' }
43
+ let(:client) {
44
+ described_class.new(username: 'Jimmy').tap { |c|
45
+ allow(c).to receive(:loaded_orms).and_return(loaded_orms)
46
+ }
47
+ }
43
48
 
44
49
  describe "if there is one known ORM loaded" do
45
-
46
- before :each do
47
- described_class.any_instance.stub(:loaded_orms).and_return([double(:class, name: 'ActiveRecord::Base')])
48
- end
50
+
51
+ let(:loaded_orms) { [double(:class, name: 'ActiveRecord::Base')] }
49
52
 
50
53
  it "uses that ORM's adapter" do
51
- expect(Dossier::Adapter::ActiveRecord).to receive(:new).with(username: 'Jimmy')
52
- described_class.new(username: 'Jimmy')
54
+ expect(Dossier::Adapter::ActiveRecord).to(
55
+ receive(:new).with(username: 'Jimmy'))
56
+ client.adapter
53
57
  end
54
58
 
55
59
  end
56
60
 
57
61
  context "if there are no known ORMs loaded" do
58
-
59
- before :each do
60
- described_class.any_instance.stub(:loaded_orms).and_return([])
61
- end
62
+
63
+ let(:loaded_orms) { [] }
62
64
 
63
65
  it "raises an error" do
64
- expect{described_class.new(username: 'Jimmy')}.to raise_error(Dossier::Client::IndeterminableAdapter)
66
+ expect{ client.adapter }.to raise_error(Dossier::Client::IndeterminableAdapter)
65
67
  end
66
68
 
67
69
  end
68
70
 
69
71
  describe "if there are multiple known ORMs loaded" do
70
-
71
- before :each do
72
- described_class.any_instance.stub(:loaded_orms).and_return([:orm1, :orm2])
73
- end
72
+
73
+ let(:loaded_orms) { [:orm1, :orm2] }
74
74
 
75
75
  it "raises an error" do
76
- expect{described_class.new(username: 'Jimmy')}.to raise_error(Dossier::Client::IndeterminableAdapter)
76
+ expect{ client.adapter }.to raise_error(Dossier::Client::IndeterminableAdapter)
77
77
  end
78
78
 
79
79
  end
@@ -90,7 +90,7 @@ describe Dossier::Client do
90
90
  let(:adapter) { double(:adapter) }
91
91
 
92
92
  before :each do
93
- client.stub(:adapter).and_return(adapter)
93
+ allow(client).to receive(:adapter).and_return(adapter)
94
94
  end
95
95
 
96
96
  it "delegates `escape` to its adapter" do