dossier 2.7.1 → 2.8.0

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 (75) hide show
  1. checksums.yaml +7 -0
  2. data/{README.markdown → README.md} +10 -5
  3. data/Rakefile +1 -0
  4. data/app/controllers/dossier/reports_controller.rb +9 -38
  5. data/app/helpers/dossier/application_helper.rb +7 -1
  6. data/app/views/dossier/layouts/application.html.haml +6 -0
  7. data/app/views/dossier/reports/multi.html.haml +8 -3
  8. data/app/views/dossier/reports/show.html.haml +4 -2
  9. data/config/routes.rb +2 -2
  10. data/lib/dossier.rb +4 -8
  11. data/lib/dossier/adapter/active_record.rb +1 -1
  12. data/lib/dossier/formatter.rb +20 -3
  13. data/lib/dossier/multi_report.rb +17 -9
  14. data/lib/dossier/naming.rb +51 -0
  15. data/lib/dossier/renderer.rb +63 -0
  16. data/lib/dossier/report.rb +22 -7
  17. data/lib/dossier/responder.rb +34 -0
  18. data/lib/dossier/result.rb +5 -7
  19. data/lib/dossier/stream_csv.rb +10 -3
  20. data/lib/dossier/version.rb +1 -1
  21. data/lib/dossier/view_context_with_report_formatter.rb +7 -0
  22. data/lib/dossier/xls.rb +2 -2
  23. data/lib/generators/dossier/views/templates/show.html.haml +5 -3
  24. data/spec/dossier/adapter/active_record_spec.rb +1 -1
  25. data/spec/dossier/formatter_spec.rb +38 -3
  26. data/spec/dossier/multi_report_spec.rb +18 -1
  27. data/spec/dossier/naming_spec.rb +29 -0
  28. data/spec/dossier/renderer_spec.rb +57 -0
  29. data/spec/dossier/report_spec.rb +23 -2
  30. data/spec/dossier/responder_spec.rb +59 -0
  31. data/spec/dossier/result_spec.rb +4 -0
  32. data/spec/dossier/stream_csv_spec.rb +75 -0
  33. data/spec/dossier_spec.rb +0 -13
  34. data/spec/dummy/app/controllers/site_controller.rb +0 -4
  35. data/spec/dummy/app/reports/cats/are/super_fun_report.rb +9 -0
  36. data/spec/dummy/app/reports/combination_report.rb +9 -0
  37. data/spec/{support → dummy/app}/reports/employee_report.rb +0 -0
  38. data/spec/{support → dummy/app}/reports/employee_with_custom_client_report.rb +0 -0
  39. data/spec/dummy/app/reports/employee_with_custom_view_report.rb +27 -0
  40. data/spec/dummy/app/reports/hello_my_friends_report.rb +2 -0
  41. data/spec/{support → dummy/app}/reports/test_report.rb +0 -0
  42. data/spec/dummy/app/views/dossier/reports/combination/_options.html.haml +4 -0
  43. data/spec/dummy/app/views/dossier/reports/employee_with_custom_view.html.haml +5 -1
  44. data/spec/dummy/app/views/dossier/reports/employee_with_custom_view/_options.html.haml +6 -0
  45. data/spec/dummy/config/database.yml.travis +5 -0
  46. data/spec/dummy/db/test.sqlite3 +0 -0
  47. data/spec/dummy/log/development.log +3865 -0
  48. data/spec/dummy/log/test.log +44084 -0
  49. data/spec/dummy/tmp/cache/assets/CEA/5A0/sprockets%2Fc0534884cbc43494a05d9e957ea1298d +0 -0
  50. data/spec/dummy/tmp/cache/assets/D40/0D0/sprockets%2F15a6bb0a1346b6d7fe859c14bf729a49 +0 -0
  51. data/spec/dummy/tmp/cache/assets/D4E/1B0/sprockets%2Ff7cbd26ba1d28d48de824f0e94586655 +0 -0
  52. data/spec/dummy/tmp/cache/assets/D51/510/sprockets%2Fca0353abc266080173bbc3c13efa935a +0 -0
  53. data/spec/dummy/tmp/cache/assets/D6C/400/sprockets%2F7fa180a6e05c7ca4346ef58c54bb30f8 +0 -0
  54. data/spec/dummy/tmp/cache/assets/DDC/400/sprockets%2Fcffd775d018f68ce5dba1ee0d951a994 +0 -0
  55. data/spec/dummy/tmp/cache/assets/E04/890/sprockets%2F2f5173deea6c795b8fdde723bb4b63af +0 -0
  56. data/spec/dummy/{dossier_test → tmp/restart.txt} +0 -0
  57. data/spec/features/combination_report_spec.rb +13 -10
  58. data/spec/features/employee_spec.rb +76 -0
  59. data/spec/{requests → features}/employee_with_custom_client_spec.rb +3 -2
  60. data/spec/{requests → features}/employee_with_custom_controller_spec.rb +3 -2
  61. data/spec/features/namespaced_report_spec.rb +15 -0
  62. data/spec/fixtures/db/mysql2.yml.travis +4 -0
  63. data/spec/fixtures/db/sqlite3.yml.travis +2 -0
  64. data/spec/helpers/dossier/application_helper_spec.rb +24 -0
  65. data/spec/spec_helper.rb +11 -0
  66. metadata +99 -92
  67. data/spec/fixtures/reports/employee.html +0 -54
  68. data/spec/fixtures/reports/employee_with_custom_client.html +0 -54
  69. data/spec/fixtures/reports/employee_with_custom_view.html +0 -15
  70. data/spec/fixtures/reports/employee_with_footer.html +0 -56
  71. data/spec/fixtures/reports/employee_with_parameters.html +0 -38
  72. data/spec/requests/employee_spec.rb +0 -61
  73. data/spec/support/reports/combination_report.rb +0 -8
  74. data/spec/support/reports/employee_with_custom_view_report.rb +0 -8
  75. data/spec/support/reports/hello_my_friends_report.rb +0 -6
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b964aa00e953df017cd8772cd0ed975301b841bd
4
+ data.tar.gz: c8faa33c28dec8b77a253918d089eea36d41c06d
5
+ SHA512:
6
+ metadata.gz: 4557b660e38d7b8a6b8aeb4bd7a7579ae0bc5eeea214bf3a6a02bd45330b95bd120bacb8cf9905cf5c4bf47922f6f37f7d57ba2ffff6bc8e660867556e02653a
7
+ data.tar.gz: 27c413936b6b22c1c4f977eb7546419b92f8804df1908e6346b82ce3f3f74e0bdcc42b182d7300a799b2f43c0658a25d7d12c8c869455036ef0b0ae6e292c1a4
@@ -5,7 +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)
8
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)
9
13
 
10
14
  ## Setup
11
15
 
@@ -75,10 +79,11 @@ By default, headers are generated by calling `titleize` on the column name from
75
79
  class ProductMarginReport < Dossier::Report
76
80
  # ...
77
81
  def format_header(column_name)
78
- {
79
- 'margin_percentage' => 'Margin %',
80
- 'absolute_margin' => 'Margin $'
81
- }[column_name.to_s] || super
82
+ custom_headers = {
83
+ margin_percentage: 'Margin %',
84
+ absolute_margin: 'Margin $'
85
+ }
86
+ custom_headers.fetch(column_name.to_sym) { super }
82
87
  end
83
88
  end
84
89
  ```
@@ -206,7 +211,7 @@ end
206
211
  ### Dossier for APIs
207
212
 
208
213
  ```ruby
209
- class API::ProjectsController < Api::ApplicationController
214
+ class Api::ProjectsController < Api::ApplicationController
210
215
 
211
216
  def snapshot
212
217
  render json: ProjectStatusReport.new(project: @project).results.hashes
data/Rakefile CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env rake
2
+
2
3
  begin
3
4
  require 'bundler/setup'
4
5
  rescue LoadError
@@ -1,51 +1,23 @@
1
1
  module Dossier
2
2
  class ReportsController < ApplicationController
3
+ include ViewContextWithReportFormatter
4
+
5
+ self.responder = Dossier::Responder
6
+
7
+ respond_to :html, :json, :csv, :xls, only: :show
8
+
3
9
  def show
4
- respond_to do |format|
5
- format.html do
6
- begin
7
- render template: "dossier/reports/#{report_class.report_name}", locals: {report: report}
8
- rescue ActionView::MissingTemplate => e
9
- render template: 'dossier/reports/show', locals: {report: report}
10
- end
11
- end
12
-
13
- format.json do
14
- render :json => report.results.hashes
15
- end
16
-
17
- format.csv do
18
- set_content_disposition!
19
- self.response_body = StreamCSV.new(report.raw_results.arrays)
20
- end
21
-
22
- format.xls do
23
- set_content_disposition!
24
- self.response_body = Xls.new(report.raw_results.arrays)
25
- end
26
- end
10
+ respond_with(report)
27
11
  end
28
12
 
29
13
  def multi
30
- respond_to do |format|
31
- format.html do
32
- begin
33
- render template: "dossier/reports/#{report_class.report_name}", locals: {multi: report}
34
- rescue ActionView::MissingTemplate => e
35
- render template: 'dossier/reports/multi', locals: {multi: report}
36
- end
37
- end
38
- end
14
+ render template: 'dossier/reports/multi', locals: {multi: report}
39
15
  end
40
16
 
41
17
  private
42
18
 
43
19
  def report_class
44
- Dossier.name_to_class(params[:report])
45
- end
46
-
47
- def set_content_disposition!
48
- headers["Content-Disposition"] = %[attachment;filename=#{params[:report]}-report_#{Time.now.strftime('%m-%d-%Y_%H-%M-%S')}.#{params[:format]}]
20
+ Dossier::Naming.name_to_class(params[:report])
49
21
  end
50
22
 
51
23
  def report
@@ -55,6 +27,5 @@ module Dossier
55
27
  def options_params
56
28
  params[:options].presence || {}
57
29
  end
58
-
59
30
  end
60
31
  end
@@ -2,7 +2,13 @@ module Dossier
2
2
  module ApplicationHelper
3
3
 
4
4
  def formatted_dossier_report_path(format, report)
5
- dossier_report_path(format: format, options: report.options, report: report.class.report_name)
5
+ dossier_report_path(format: format, options: report.options, report: report.report_name)
6
+ end
7
+
8
+ def render_options(report)
9
+ return if report.parent
10
+ render "dossier/reports/#{report.report_name}/options", report: report
11
+ rescue ActionView::MissingTemplate
6
12
  end
7
13
 
8
14
  end
@@ -0,0 +1,6 @@
1
+ !!!5
2
+ %html
3
+ %head
4
+ %title #{report.formatted_title}
5
+ %body
6
+ = yield
@@ -1,3 +1,8 @@
1
- %h1= multi.class.report_name
2
- - multi.reports.each do |report|
3
- = render template: 'dossier/reports/show', locals: {report: report}
1
+ %div{id: multi.dom_id}
2
+ %h1.dossier-multi-header
3
+ = multi.formatted_title
4
+
5
+ = render_options(multi)
6
+
7
+ - multi.reports.each do |report|
8
+ = report.render layout: false
@@ -1,6 +1,8 @@
1
- %h1= report.class.name.titleize
1
+ %h2= report.formatted_title
2
2
 
3
- = link_to 'Download CSV', dossier_report_path(format: 'csv', options: report.options, report: report.class.report_name), class: '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
@@ -1,6 +1,6 @@
1
1
  Rails.application.routes.draw do
2
2
 
3
- get "reports/:report", to: 'dossier/reports#show', as: :dossier_report
4
- get "multi/reports/:report", to: 'dossier/reports#multi', as: :dossier_multi_report
3
+ get "reports/*report", to: 'dossier/reports#show', as: :dossier_report
4
+ get "multi/reports/*report", to: 'dossier/reports#multi', as: :dossier_multi_report
5
5
 
6
6
  end
@@ -1,4 +1,6 @@
1
1
  require "dossier/engine"
2
+ require "dossier/naming"
3
+ require "dossier/view_context_with_report_formatter"
2
4
  require "dossier/version"
3
5
 
4
6
  module Dossier
@@ -18,14 +20,6 @@ module Dossier
18
20
  configuration.client
19
21
  end
20
22
 
21
- def class_to_name(klass)
22
- klass.name.underscore[0..-8]
23
- end
24
-
25
- def name_to_class(name)
26
- "#{name.split('_').map(&:capitalize).join}Report".constantize
27
- end
28
-
29
23
  class ExecuteError < StandardError; end
30
24
  end
31
25
 
@@ -36,7 +30,9 @@ require "dossier/configuration"
36
30
  require "dossier/formatter"
37
31
  require "dossier/multi_report"
38
32
  require "dossier/query"
33
+ require "dossier/renderer"
39
34
  require "dossier/report"
35
+ require "dossier/responder"
40
36
  require "dossier/result"
41
37
  require "dossier/stream_csv"
42
38
  require "dossier/xls"
@@ -15,7 +15,7 @@ module Dossier
15
15
 
16
16
  def execute(query, report_name = nil)
17
17
  # Ensure that SQL logs show name of report generating query
18
- Result.new(connection.exec_query(*[query, report_name].compact))
18
+ Result.new(connection.exec_query(*["\n#{query}", report_name].compact))
19
19
  rescue => e
20
20
  raise Dossier::ExecuteError.new "#{e.message}\n\n#{query}"
21
21
  end
@@ -1,18 +1,35 @@
1
1
  module Dossier
2
2
  module Formatter
3
+ include ActiveSupport::Inflector
4
+ include ActionView::Helpers::NumberHelper
3
5
  extend self
4
- extend ActiveSupport::Inflector
5
- extend ActionView::Helpers::NumberHelper
6
6
 
7
7
  def number_to_currency_from_cents(value)
8
8
  number_to_currency(value /= 100.0)
9
9
  end
10
10
 
11
+ def number_to_dollars(value)
12
+ "$#{commafy_number(value, 2)}"
13
+ end
14
+
15
+ def commafy_number(value, precision = nil)
16
+ whole, fraction = value.to_s.split('.')
17
+ fraction = "%.#{precision}d" % ("0.#{fraction}".to_f.round(precision) * 10**precision).to_i if precision
18
+ [whole.to_i.to_s.gsub(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1,"), fraction].compact.join('.')
19
+ end
20
+
11
21
  def url_formatter
12
22
  @url_formatter ||= UrlFormatter.new
13
23
  end
14
24
 
15
- delegate :url_for, :link_to, :url_helpers, to: :url_formatter
25
+ def report_name(report)
26
+ titleize("#{report.report_name.split('/').last} Report")
27
+ end
28
+
29
+ # TODO figure out how to handle this better
30
+ # reports rendered with a system layout use this link_to instead of the
31
+ # correct one
32
+ # delegate :url_for, :link_to, :url_helpers, to: :url_formatter
16
33
 
17
34
  class UrlFormatter
18
35
  include ActionView::Helpers::UrlHelper
@@ -1,4 +1,5 @@
1
1
  class Dossier::MultiReport
2
+ include Dossier::Naming
2
3
 
3
4
  attr_accessor :options
4
5
 
@@ -6,24 +7,31 @@ class Dossier::MultiReport
6
7
  attr_accessor :reports
7
8
  end
8
9
 
9
- def self.report_name
10
- Dossier.class_to_name(self)
11
- end
12
-
13
10
  def self.combine(*reports)
14
11
  self.reports = reports
15
12
  end
16
13
 
17
- def self.report=(value)
18
- value
19
- end
20
-
21
14
  def initialize(options = {})
22
15
  self.options = options.dup.with_indifferent_access
23
16
  end
24
17
 
25
18
  def reports
26
- @reports ||= self.class.reports.map(&:new)
19
+ @reports ||= self.class.reports.map { |report|
20
+ report.new(options).tap { |r|
21
+ r.parent = self
22
+ }
23
+ }
27
24
  end
28
25
 
26
+ def parent
27
+ nil
28
+ end
29
+
30
+ def formatter
31
+ Module.new
32
+ end
33
+
34
+ def dom_id
35
+ nil
36
+ end
29
37
  end
@@ -0,0 +1,51 @@
1
+ module Dossier
2
+ module Naming
3
+
4
+ # not using ActiveSupport::Concern because ClassMethods
5
+ # must be extended after ActiveModel::Naming
6
+ def self.included(base)
7
+ base.extend ActiveModel::Naming
8
+ base.extend ClassMethods
9
+ end
10
+
11
+ def self.class_to_name(klass)
12
+ (klass.name || anonymous_report).underscore[0..-8]
13
+ end
14
+
15
+ def self.name_to_class(name)
16
+ "#{name}_report".classify.constantize
17
+ end
18
+
19
+ def self.anonymous_report
20
+ 'AnonymousReport'
21
+ end
22
+
23
+ def to_key
24
+ [report_name]
25
+ end
26
+
27
+ def to_s
28
+ report_name
29
+ end
30
+
31
+ delegate :report_name, :formatted_title, to: "self.class"
32
+
33
+ module ClassMethods
34
+ def report_name
35
+ Dossier::Naming.class_to_name(self)
36
+ end
37
+
38
+ def formatted_title
39
+ Dossier::Formatter.report_name(self)
40
+ end
41
+
42
+ def model_name
43
+ @model_name ||= ActiveModel::Name.new(self, nil, superclass.name).tap do |name|
44
+ name.instance_variable_set(:@param_key, 'options')
45
+ end
46
+ end
47
+
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,63 @@
1
+ module Dossier
2
+ class Renderer
3
+ attr_reader :report
4
+ attr_writer :engine
5
+
6
+ def initialize(report)
7
+ @report = report
8
+ end
9
+
10
+ def render(options = {})
11
+ render_template :custom, options
12
+ rescue ActionView::MissingTemplate => e
13
+ render_template :default, options
14
+ end
15
+
16
+ def engine
17
+ @engine ||= Engine.new(report)
18
+ end
19
+
20
+ private
21
+
22
+ def render_template(template, options)
23
+ template = send("#{template}_template_path")
24
+ engine.render options.merge(template: template, locals: {report: report})
25
+ end
26
+
27
+ def template_path(template)
28
+ "dossier/reports/#{template}"
29
+ end
30
+
31
+ def custom_template_path
32
+ template_path(report.template)
33
+ end
34
+
35
+ def default_template_path
36
+ template_path('show')
37
+ end
38
+
39
+ class Engine < AbstractController::Base
40
+ include AbstractController::Layouts
41
+ include ViewContextWithReportFormatter
42
+ attr_reader :report
43
+
44
+ layout 'dossier/layouts/application'
45
+
46
+ def self._helpers
47
+ Module.new do
48
+ include Rails.application.helpers
49
+ include Rails.application.routes_url_helpers
50
+ end
51
+ end
52
+
53
+ def self._view_paths
54
+ ActionController::Base.view_paths
55
+ end
56
+
57
+ def initialize(report)
58
+ @report = report
59
+ super()
60
+ end
61
+ end
62
+ end
63
+ end
@@ -1,16 +1,29 @@
1
1
  module Dossier
2
2
  class Report
3
+ include Dossier::Naming
3
4
  include ActiveSupport::Callbacks
4
- extend ActiveModel::Naming
5
5
 
6
6
  define_callbacks :build_query, :execute
7
7
 
8
8
  attr_reader :options
9
+ attr_accessor :parent
9
10
 
10
- def self.report_name
11
- Dossier.class_to_name(self)
11
+ class_attribute :formatter
12
+ class_attribute :template
13
+
14
+ self.formatter = Dossier::Formatter
15
+
16
+ delegate :formatter, :template, to: "self.class"
17
+
18
+ def self.inherited(base)
19
+ super
20
+ base.template = base.report_name
12
21
  end
13
22
 
23
+ def self.filename
24
+ "#{report_name.parameterize}-report_#{Time.now.strftime('%m-%d-%Y_%H-%M-%S')}"
25
+ end
26
+
14
27
  def initialize(options = {})
15
28
  @options = options.dup.with_indifferent_access
16
29
  end
@@ -38,10 +51,6 @@ module Dossier
38
51
  tap { execute }
39
52
  end
40
53
 
41
- def formatter
42
- Dossier::Formatter
43
- end
44
-
45
54
  def format_header(header)
46
55
  formatter.titleize(header.to_s)
47
56
  end
@@ -50,6 +59,12 @@ module Dossier
50
59
  Dossier.client
51
60
  end
52
61
 
62
+ def renderer
63
+ @renderer ||= Renderer.new(self)
64
+ end
65
+
66
+ delegate :render, to: :renderer
67
+
53
68
  private
54
69
 
55
70
  def build_query