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.
- 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
|
-
[](http://badge.fury.io/rb/dossier)
|
9
|
+
[](https://codeclimate.com/github/tma1/dossier)
|
10
|
+
[](https://travis-ci.org/tma1/dossier)
|
11
|
+
[](https://coveralls.io/github/tma1/dossier?branch=master)
|
12
|
+
[](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
|