dossier 2.12.2 → 2.13.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +31 -6
- data/Rakefile +12 -5
- data/VERSION +1 -0
- data/app/controllers/dossier/reports_controller.rb +1 -1
- data/app/views/dossier/reports/show.html.haml +1 -1
- data/lib/dossier.rb +1 -1
- data/lib/dossier/client.rb +4 -1
- data/lib/dossier/configuration.rb +10 -5
- data/lib/dossier/{naming.rb → model.rb} +10 -2
- data/lib/dossier/multi_report.rb +1 -1
- data/lib/dossier/query.rb +1 -1
- data/lib/dossier/report.rb +13 -10
- data/lib/dossier/responder.rb +2 -0
- data/lib/dossier/result.rb +29 -12
- data/lib/dossier/version.rb +1 -1
- data/lib/generators/dossier/views/templates/show.html.haml +1 -1
- data/spec/dossier/adapter/active_record_spec.rb +4 -4
- data/spec/dossier/client_spec.rb +19 -19
- data/spec/dossier/configuration_spec.rb +29 -9
- data/spec/dossier/{naming_spec.rb → model_spec.rb} +6 -1
- data/spec/dossier/query_spec.rb +12 -12
- data/spec/dossier/report_spec.rb +7 -7
- data/spec/dossier/responder_spec.rb +5 -5
- data/spec/dossier/result_spec.rb +5 -5
- data/spec/dossier/stream_csv_spec.rb +5 -3
- data/spec/dossier/version_spec.rb +8 -0
- data/spec/dossier_spec.rb +4 -4
- data/spec/dummy/Rakefile +4 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/reports/cats/are/super_fun_report.rb +13 -1
- data/spec/dummy/app/reports/employee_report.rb +14 -1
- data/spec/dummy/app/reports/employee_with_custom_view_report.rb +5 -1
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config/application.rb +30 -0
- data/spec/dummy/config/database.yml +6 -5
- data/spec/dummy/db/{test.sqlite3 → dossier_test.sqlite3} +0 -0
- data/spec/dummy/log/development.log +0 -0
- data/spec/dummy/log/test.log +21983 -0
- data/spec/features/employee_spec.rb +13 -4
- data/spec/fixtures/db/postgresql.yml +4 -0
- data/spec/fixtures/db/postgresql.yml.example +5 -0
- data/spec/fixtures/db/postgresql.yml.travis +4 -0
- data/spec/fixtures/db/sqlite3.yml +1 -1
- data/spec/fixtures/reports/employee.csv +3 -3
- data/spec/fixtures/reports/employee.xls +3 -3
- data/spec/generators/dossier/views/views_spec.rb +17 -10
- data/spec/routing/dossier_routes_spec.rb +1 -1
- data/spec/spec_helper.rb +7 -10
- data/spec/support/factory.rb +38 -0
- metadata +57 -44
- data/spec/dummy/config/database.yml.example +0 -13
- data/spec/dummy/config/database.yml.travis +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ebb6c964daa51f25b6c3b0856cd7e9d1006ed2b0
|
4
|
+
data.tar.gz: 700abc7cba17e2218185f16bd51ace2711a493cb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
9
|
-
[![Code Climate](https://codeclimate.com/github/
|
10
|
-
[![Build Status](https://travis-ci.org/
|
11
|
-
[![Coverage Status](https://coveralls.io/repos/
|
12
|
-
[![Dependency Status](https://gemnasium.com/
|
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
|
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
|
-
|
7
|
+
require 'rdoc/task'
|
10
8
|
|
11
|
-
|
12
|
-
|
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
|
data/lib/dossier.rb
CHANGED
data/lib/dossier/client.rb
CHANGED
@@ -7,7 +7,10 @@ module Dossier
|
|
7
7
|
|
8
8
|
def initialize(options)
|
9
9
|
self.options = options.symbolize_keys
|
10
|
-
|
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(
|
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?
|
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
|
-
|
36
|
+
def raise_empty_conn_config
|
32
37
|
raise ConfigurationMissingError.new(
|
33
|
-
"
|
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
|
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::
|
43
|
+
Dossier::Model.class_to_name(self)
|
36
44
|
end
|
37
45
|
|
38
46
|
def formatted_title
|
data/lib/dossier/multi_report.rb
CHANGED
data/lib/dossier/query.rb
CHANGED
@@ -15,7 +15,7 @@ module Dossier
|
|
15
15
|
private
|
16
16
|
|
17
17
|
def compile
|
18
|
-
string.gsub(/\w
|
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)
|
data/lib/dossier/report.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Dossier
|
2
2
|
class Report
|
3
|
-
include Dossier::
|
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
|
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
|
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.
|
85
|
+
self.query_results = dossier_client.execute(query, self.class.name)
|
82
86
|
end
|
83
87
|
end
|
84
88
|
|
85
|
-
def
|
86
|
-
|
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
|
data/lib/dossier/responder.rb
CHANGED
data/lib/dossier/result.rb
CHANGED
@@ -52,7 +52,11 @@ module Dossier
|
|
52
52
|
class Formatted < Result
|
53
53
|
|
54
54
|
def headers
|
55
|
-
@formatted_headers ||= raw_headers.
|
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
|
-
|
79
|
+
def displayable_columns(row)
|
80
|
+
row.each_with_index.select { |value, i|
|
68
81
|
column = raw_headers.at(i)
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
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
|
data/lib/dossier/version.rb
CHANGED
@@ -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.
|
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.
|
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.
|
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.
|
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
|
|
data/spec/dossier/client_spec.rb
CHANGED
@@ -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(:
|
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
|
-
|
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
|
52
|
-
|
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
|
-
|
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{
|
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
|
-
|
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{
|
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.
|
93
|
+
allow(client).to receive(:adapter).and_return(adapter)
|
94
94
|
end
|
95
95
|
|
96
96
|
it "delegates `escape` to its adapter" do
|