dossier 2.12.2 → 2.13.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.
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