dossier 2.7.1 → 2.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/{README.markdown → README.md} +10 -5
- data/Rakefile +1 -0
- data/app/controllers/dossier/reports_controller.rb +9 -38
- data/app/helpers/dossier/application_helper.rb +7 -1
- data/app/views/dossier/layouts/application.html.haml +6 -0
- data/app/views/dossier/reports/multi.html.haml +8 -3
- data/app/views/dossier/reports/show.html.haml +4 -2
- data/config/routes.rb +2 -2
- data/lib/dossier.rb +4 -8
- data/lib/dossier/adapter/active_record.rb +1 -1
- data/lib/dossier/formatter.rb +20 -3
- data/lib/dossier/multi_report.rb +17 -9
- data/lib/dossier/naming.rb +51 -0
- data/lib/dossier/renderer.rb +63 -0
- data/lib/dossier/report.rb +22 -7
- data/lib/dossier/responder.rb +34 -0
- data/lib/dossier/result.rb +5 -7
- data/lib/dossier/stream_csv.rb +10 -3
- data/lib/dossier/version.rb +1 -1
- data/lib/dossier/view_context_with_report_formatter.rb +7 -0
- data/lib/dossier/xls.rb +2 -2
- data/lib/generators/dossier/views/templates/show.html.haml +5 -3
- data/spec/dossier/adapter/active_record_spec.rb +1 -1
- data/spec/dossier/formatter_spec.rb +38 -3
- data/spec/dossier/multi_report_spec.rb +18 -1
- data/spec/dossier/naming_spec.rb +29 -0
- data/spec/dossier/renderer_spec.rb +57 -0
- data/spec/dossier/report_spec.rb +23 -2
- data/spec/dossier/responder_spec.rb +59 -0
- data/spec/dossier/result_spec.rb +4 -0
- data/spec/dossier/stream_csv_spec.rb +75 -0
- data/spec/dossier_spec.rb +0 -13
- data/spec/dummy/app/controllers/site_controller.rb +0 -4
- data/spec/dummy/app/reports/cats/are/super_fun_report.rb +9 -0
- data/spec/dummy/app/reports/combination_report.rb +9 -0
- data/spec/{support → dummy/app}/reports/employee_report.rb +0 -0
- data/spec/{support → dummy/app}/reports/employee_with_custom_client_report.rb +0 -0
- data/spec/dummy/app/reports/employee_with_custom_view_report.rb +27 -0
- data/spec/dummy/app/reports/hello_my_friends_report.rb +2 -0
- data/spec/{support → dummy/app}/reports/test_report.rb +0 -0
- data/spec/dummy/app/views/dossier/reports/combination/_options.html.haml +4 -0
- data/spec/dummy/app/views/dossier/reports/employee_with_custom_view.html.haml +5 -1
- data/spec/dummy/app/views/dossier/reports/employee_with_custom_view/_options.html.haml +6 -0
- data/spec/dummy/config/database.yml.travis +5 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +3865 -0
- data/spec/dummy/log/test.log +44084 -0
- data/spec/dummy/tmp/cache/assets/CEA/5A0/sprockets%2Fc0534884cbc43494a05d9e957ea1298d +0 -0
- data/spec/dummy/tmp/cache/assets/D40/0D0/sprockets%2F15a6bb0a1346b6d7fe859c14bf729a49 +0 -0
- data/spec/dummy/tmp/cache/assets/D4E/1B0/sprockets%2Ff7cbd26ba1d28d48de824f0e94586655 +0 -0
- data/spec/dummy/tmp/cache/assets/D51/510/sprockets%2Fca0353abc266080173bbc3c13efa935a +0 -0
- data/spec/dummy/tmp/cache/assets/D6C/400/sprockets%2F7fa180a6e05c7ca4346ef58c54bb30f8 +0 -0
- data/spec/dummy/tmp/cache/assets/DDC/400/sprockets%2Fcffd775d018f68ce5dba1ee0d951a994 +0 -0
- data/spec/dummy/tmp/cache/assets/E04/890/sprockets%2F2f5173deea6c795b8fdde723bb4b63af +0 -0
- data/spec/dummy/{dossier_test → tmp/restart.txt} +0 -0
- data/spec/features/combination_report_spec.rb +13 -10
- data/spec/features/employee_spec.rb +76 -0
- data/spec/{requests → features}/employee_with_custom_client_spec.rb +3 -2
- data/spec/{requests → features}/employee_with_custom_controller_spec.rb +3 -2
- data/spec/features/namespaced_report_spec.rb +15 -0
- data/spec/fixtures/db/mysql2.yml.travis +4 -0
- data/spec/fixtures/db/sqlite3.yml.travis +2 -0
- data/spec/helpers/dossier/application_helper_spec.rb +24 -0
- data/spec/spec_helper.rb +11 -0
- metadata +99 -92
- data/spec/fixtures/reports/employee.html +0 -54
- data/spec/fixtures/reports/employee_with_custom_client.html +0 -54
- data/spec/fixtures/reports/employee_with_custom_view.html +0 -15
- data/spec/fixtures/reports/employee_with_footer.html +0 -56
- data/spec/fixtures/reports/employee_with_parameters.html +0 -38
- data/spec/requests/employee_spec.rb +0 -61
- data/spec/support/reports/combination_report.rb +0 -8
- data/spec/support/reports/employee_with_custom_view_report.rb +0 -8
- data/spec/support/reports/hello_my_friends_report.rb +0 -6
@@ -0,0 +1,34 @@
|
|
1
|
+
module Dossier
|
2
|
+
class Responder < ::ActionController::Responder
|
3
|
+
alias :report :resource
|
4
|
+
|
5
|
+
def to_html
|
6
|
+
report.renderer.engine = controller
|
7
|
+
controller.response_body = report.render
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_json
|
11
|
+
controller.render json: report.results.hashes
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_csv
|
15
|
+
set_content_disposition!
|
16
|
+
controller.response_body = StreamCSV.new(report.raw_results.arrays)
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_xls
|
20
|
+
set_content_disposition!
|
21
|
+
controller.response_body = Xls.new(report.raw_results.arrays)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def set_content_disposition!
|
27
|
+
controller.headers["Content-Disposition"] = %[attachment;filename=#{filename}]
|
28
|
+
end
|
29
|
+
|
30
|
+
def filename
|
31
|
+
"#{report.class.filename}.#{format}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/dossier/result.rb
CHANGED
@@ -10,15 +10,15 @@ module Dossier
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def headers
|
13
|
-
adapter_results.headers
|
13
|
+
@headers ||= adapter_results.headers
|
14
14
|
end
|
15
15
|
|
16
16
|
def body
|
17
|
-
rows.first(rows.length - report.options[:footer].to_i)
|
17
|
+
@body ||= rows.first(rows.length - report.options[:footer].to_i)
|
18
18
|
end
|
19
19
|
|
20
20
|
def footers
|
21
|
-
rows.last(report.options[:footer].to_i)
|
21
|
+
@footer ||= rows.last(report.options[:footer].to_i)
|
22
22
|
end
|
23
23
|
|
24
24
|
def rows
|
@@ -39,14 +39,12 @@ module Dossier
|
|
39
39
|
end
|
40
40
|
|
41
41
|
def each
|
42
|
-
raise NotImplementedError, "
|
42
|
+
raise NotImplementedError, "#{self.class.name} must define `each`"
|
43
43
|
end
|
44
44
|
|
45
45
|
class Formatted < Result
|
46
46
|
def each
|
47
|
-
adapter_results.rows.each
|
48
|
-
yield format(row)
|
49
|
-
end
|
47
|
+
adapter_results.rows.each { |row| yield format(row) }
|
50
48
|
end
|
51
49
|
|
52
50
|
def format(row)
|
data/lib/dossier/stream_csv.rb
CHANGED
@@ -2,15 +2,16 @@ require 'csv'
|
|
2
2
|
|
3
3
|
module Dossier
|
4
4
|
class StreamCSV
|
5
|
+
attr_reader :headers, :collection
|
5
6
|
|
6
7
|
def initialize(collection, headers = nil)
|
7
|
-
@headers = headers || collection.shift
|
8
|
+
@headers = headers || collection.shift unless false === headers
|
8
9
|
@collection = collection
|
9
10
|
end
|
10
11
|
|
11
12
|
def each
|
12
|
-
yield
|
13
|
-
|
13
|
+
yield headers.map { |header| Dossier::Formatter.titleize(header) }.to_csv if headers?
|
14
|
+
collection.each do |record|
|
14
15
|
yield record.to_csv
|
15
16
|
end
|
16
17
|
rescue => e
|
@@ -24,5 +25,11 @@ module Dossier
|
|
24
25
|
end
|
25
26
|
end
|
26
27
|
|
28
|
+
private
|
29
|
+
|
30
|
+
def headers?
|
31
|
+
headers.present?
|
32
|
+
end
|
33
|
+
|
27
34
|
end
|
28
35
|
end
|
data/lib/dossier/version.rb
CHANGED
data/lib/dossier/xls.rb
CHANGED
@@ -18,12 +18,12 @@ module Dossier
|
|
18
18
|
|
19
19
|
private
|
20
20
|
|
21
|
-
def
|
21
|
+
def as_cell(el)
|
22
22
|
%{<Cell><Data ss:Type="String">#{el}</Data></Cell>}
|
23
23
|
end
|
24
24
|
|
25
25
|
def as_row(array)
|
26
|
-
my_array = array.map{|a|
|
26
|
+
my_array = array.map{|a| as_cell(a)}.join("\n")
|
27
27
|
"<Row>\n" + my_array + "\n</Row>\n"
|
28
28
|
end
|
29
29
|
|
@@ -1,12 +1,14 @@
|
|
1
|
-
%
|
1
|
+
%h2= report.formatted_title
|
2
2
|
|
3
|
-
= link_to 'Download CSV',
|
3
|
+
= link_to 'Download CSV', formatted_dossier_report_path('csv', report), class: 'download-csv'
|
4
|
+
|
5
|
+
= render_options(report)
|
4
6
|
|
5
7
|
%table
|
6
8
|
%thead
|
7
9
|
%tr
|
8
10
|
- report.results.headers.each do |header|
|
9
|
-
%th=
|
11
|
+
%th= report.format_header(header)
|
10
12
|
%tbody
|
11
13
|
- report.results.body.each do |row|
|
12
14
|
%tr
|
@@ -29,7 +29,7 @@ describe Dossier::Adapter::ActiveRecord do
|
|
29
29
|
let(:adapter_result_class) { Dossier::Adapter::ActiveRecord::Result}
|
30
30
|
|
31
31
|
it "delegates to the connection" do
|
32
|
-
ar_connection.should_receive(:exec_query).with(query)
|
32
|
+
ar_connection.should_receive(:exec_query).with("\n#{query}")
|
33
33
|
adapter.execute(query)
|
34
34
|
end
|
35
35
|
|
@@ -24,15 +24,50 @@ describe Dossier::Formatter do
|
|
24
24
|
let(:route) { {controller: :site, action: :index} }
|
25
25
|
|
26
26
|
it "allows URL generation" do
|
27
|
-
expect(formatter.url_for(route)).to eq('/woo')
|
27
|
+
expect(formatter.url_formatter.url_for(route)).to eq('/woo')
|
28
28
|
end
|
29
29
|
|
30
30
|
it "allows link generation" do
|
31
|
-
expect(formatter.link_to('Woo!', route)).to eq('<a href="/woo">Woo!</a>')
|
31
|
+
expect(formatter.url_formatter.link_to('Woo!', route)).to eq('<a href="/woo">Woo!</a>')
|
32
32
|
end
|
33
33
|
|
34
34
|
it "allows usage of url helpers" do
|
35
|
-
expect(formatter.url_helpers.woo_path).to eq('/woo')
|
35
|
+
expect(formatter.url_formatter.url_helpers.woo_path).to eq('/woo')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "custom formatters" do
|
40
|
+
describe "commafy_number" do
|
41
|
+
formats = {
|
42
|
+
10_000 => '10,000',
|
43
|
+
10_000.01 => '10,000.01',
|
44
|
+
1_000_000_000.001 => '1,000,000,000.001',
|
45
|
+
'12345.6789' => '12,345.6789'
|
46
|
+
}.each { |base, formatted|
|
47
|
+
it "formats #{base} as #{formatted}" do
|
48
|
+
expect(formatter.commafy_number(base)).to eq formatted
|
49
|
+
end
|
50
|
+
}
|
51
|
+
it "will return the expected precision if too large" do
|
52
|
+
expect(formatter.commafy_number(1_000.23523563, 2)).to eq '1,000.24'
|
53
|
+
end
|
54
|
+
|
55
|
+
it "will return the expected precision if too small" do
|
56
|
+
expect(formatter.commafy_number(1_000, 5)).to eq '1,000.00000'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "number_to_dollars" do
|
61
|
+
formats = {
|
62
|
+
10_000 => '$10,000.00',
|
63
|
+
10_000.00 => '$10,000.00',
|
64
|
+
1_000_000_000.000 => '$1,000,000,000.00',
|
65
|
+
'12345.6788' => '$12,345.68'
|
66
|
+
}.each { |base, formatted|
|
67
|
+
it "formats #{base} as #{formatted}" do
|
68
|
+
expect(formatter.number_to_dollars(base)).to eq formatted
|
69
|
+
end
|
70
|
+
}
|
36
71
|
end
|
37
72
|
end
|
38
73
|
|
@@ -2,10 +2,27 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Dossier::MultiReport do
|
4
4
|
|
5
|
-
let(:
|
5
|
+
let(:options) { {'foo' => 'bar'} }
|
6
|
+
let(:combined_report) { CombinationReport }
|
7
|
+
let(:report) { combined_report.new(options) }
|
6
8
|
|
7
9
|
it 'knows its sub reports' do
|
8
10
|
expect(combined_report.reports).to eq([EmployeeReport, EmployeeWithCustomViewReport])
|
9
11
|
end
|
10
12
|
|
13
|
+
it "passes options to the sub reports" do
|
14
|
+
combined_report.reports.each do |report|
|
15
|
+
report.should_receive(:new).with(options).and_call_original
|
16
|
+
end
|
17
|
+
|
18
|
+
report.reports
|
19
|
+
end
|
20
|
+
|
21
|
+
it "sets the multi property on its child reports" do
|
22
|
+
expect(report.reports.first.parent).to eq(report)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "never has a parent" do
|
26
|
+
expect(report.parent).to be_nil
|
27
|
+
end
|
11
28
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Dossier::Naming do
|
4
|
+
describe "report naming" do
|
5
|
+
let(:klass) { HelloMyFriendsReport }
|
6
|
+
let(:name) { 'hello_my_friends' }
|
7
|
+
|
8
|
+
it "converts a report class to a report name" do
|
9
|
+
expect(described_class.class_to_name(klass)).to eq(name)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "converting a report name to a report class" do
|
13
|
+
expect(described_class.name_to_class(name)).to eq(klass)
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "with namespaces" do
|
17
|
+
let(:klass) { Cats::Are::SuperFunReport }
|
18
|
+
let(:name) { 'cats/are/super_fun' }
|
19
|
+
|
20
|
+
it "converts a report class to a report name" do
|
21
|
+
expect(described_class.class_to_name klass).to eq name
|
22
|
+
end
|
23
|
+
|
24
|
+
it "converts a report name to a report class" do
|
25
|
+
expect(described_class.name_to_class name).to eq klass
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Dossier::Renderer do
|
4
|
+
|
5
|
+
let(:report) { EmployeeReport.new }
|
6
|
+
let(:renderer) { described_class.new(report) }
|
7
|
+
let(:engine) { renderer.engine }
|
8
|
+
|
9
|
+
describe "rendering" do
|
10
|
+
let(:options) { {template: "dossier/reports/#{template}", locals: {report: report}} }
|
11
|
+
|
12
|
+
describe "with custom view" do
|
13
|
+
let(:report) { EmployeeWithCustomViewReport.new }
|
14
|
+
let(:template) { report.report_name }
|
15
|
+
|
16
|
+
it "renders the custom view" do
|
17
|
+
engine.should_receive(:render).with(options)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "without custom view" do
|
22
|
+
let(:template) { 'show' }
|
23
|
+
|
24
|
+
it "renders show" do
|
25
|
+
engine.should_receive(:render).with(options.merge(template: 'dossier/reports/employee')).and_call_original
|
26
|
+
engine.should_receive(:render).with(options)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
after(:each) { renderer.render }
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "engine" do
|
34
|
+
describe "view_context" do
|
35
|
+
it "mixes in the dossier/application_helper to that view context" do
|
36
|
+
expect(engine.view_context.class.ancestors).to include(Dossier::ApplicationHelper)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "view path" do
|
41
|
+
it "has the same view paths the application would have" do
|
42
|
+
extractor = ->(vp) { vp.paths }
|
43
|
+
expect(extractor.call engine.view_paths).to eq(extractor.call ActionController::Base.view_paths)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "layouts" do
|
48
|
+
it "uses a layout" do
|
49
|
+
expect(report.render).to match('<html>')
|
50
|
+
end
|
51
|
+
|
52
|
+
it "makes the report available to the layout" do
|
53
|
+
expect(report.render).to match('<title>Employee Report</title>')
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/spec/dossier/report_spec.rb
CHANGED
@@ -8,6 +8,15 @@ describe Dossier::Report do
|
|
8
8
|
TestReport.report_name.should eq('test')
|
9
9
|
end
|
10
10
|
|
11
|
+
it "has a template name that is the report name" do
|
12
|
+
expect(report.template).to eq(report.report_name)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "allows overriding the template" do
|
16
|
+
report = Class.new(described_class) { def template; 'fooo'; end }
|
17
|
+
expect(report.new.template).to eq 'fooo'
|
18
|
+
end
|
19
|
+
|
11
20
|
describe "report instances" do
|
12
21
|
let(:report_with_custom_header) do
|
13
22
|
Class.new(Dossier::Report) do
|
@@ -20,18 +29,20 @@ describe Dossier::Report do
|
|
20
29
|
end
|
21
30
|
|
22
31
|
it "takes options when initializing" do
|
23
|
-
report = TestReport.new(:foo => 'bar')
|
24
32
|
report.options.should eq('foo' => 'bar')
|
25
33
|
end
|
26
34
|
|
27
35
|
it 'generates column headers' do
|
28
|
-
report = TestReport.new(:foo => 'bar')
|
29
36
|
report.format_header('Foo').should eq 'Foo'
|
30
37
|
end
|
31
38
|
|
32
39
|
it 'allows for column header customization' do
|
33
40
|
report_with_custom_header.format_header(:generic).should eq 'customized'
|
34
41
|
end
|
42
|
+
|
43
|
+
it "has a formatted title" do
|
44
|
+
expect(report.formatted_title).to eq 'Test Report'
|
45
|
+
end
|
35
46
|
end
|
36
47
|
|
37
48
|
describe "callbacks" do
|
@@ -81,4 +92,14 @@ describe Dossier::Report do
|
|
81
92
|
|
82
93
|
end
|
83
94
|
|
95
|
+
describe "rendering" do
|
96
|
+
it "has a renderer" do
|
97
|
+
expect(report.renderer).to be_a(Dossier::Renderer)
|
98
|
+
end
|
99
|
+
|
100
|
+
it "delegates render to the renderer" do
|
101
|
+
report.renderer.should_receive(:render)
|
102
|
+
report.render
|
103
|
+
end
|
104
|
+
end
|
84
105
|
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Dossier::Responder do
|
4
|
+
|
5
|
+
def stub_out_report_results(report)
|
6
|
+
report.tap { |r|
|
7
|
+
r.stub(:results).and_return(results)
|
8
|
+
r.stub(:raw_results).and_return(results)
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:results) { mock(arrays: [[]], hashes: [{}]) }
|
13
|
+
let(:report) { EmployeeReport.new }
|
14
|
+
let(:reports) { [stub_out_report_results(report)] }
|
15
|
+
let(:controller) {
|
16
|
+
ActionController::Base.new.tap { |controller| controller.stub(:headers).and_return({}) }
|
17
|
+
}
|
18
|
+
let(:responder) { described_class.new(controller, reports, {}) }
|
19
|
+
|
20
|
+
describe "to_html" do
|
21
|
+
it "calls render on the report" do
|
22
|
+
report.should_receive(:render)
|
23
|
+
responder.to_html
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "to_json" do
|
28
|
+
it "renders the report as json" do
|
29
|
+
controller.should_receive(:render).with(json: results.hashes)
|
30
|
+
responder.to_json
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "to_csv" do
|
35
|
+
it "sets the content disposition" do
|
36
|
+
responder.should_receive(:set_content_disposition!)
|
37
|
+
responder.to_csv
|
38
|
+
end
|
39
|
+
|
40
|
+
it "sets the response body to a new csv streamer instance" do
|
41
|
+
responder.to_csv
|
42
|
+
expect(responder.controller.response_body).to be_a(Dossier::StreamCSV)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "to_xls" do
|
47
|
+
it "sets the content disposition" do
|
48
|
+
responder.should_receive(:set_content_disposition!)
|
49
|
+
responder.to_xls
|
50
|
+
end
|
51
|
+
|
52
|
+
it "sets the response body to a new xls instance" do
|
53
|
+
responder.to_xls
|
54
|
+
expect(responder.controller.response_body).to be_a(Dossier::Xls)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|