dossier 2.7.1 → 2.8.0

Sign up to get free protection for your applications and to get access to all the features.
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