dossier 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.markdown +181 -0
- data/Rakefile +27 -0
- data/app/assets/javascripts/dossier/application.js +15 -0
- data/app/assets/stylesheets/dossier/application.css +13 -0
- data/app/controllers/dossier/application_controller.rb +4 -0
- data/app/controllers/dossier/reports_controller.rb +34 -0
- data/app/helpers/dossier/application_helper.rb +4 -0
- data/app/views/dossier/reports/show.html.haml +21 -0
- data/config/routes.rb +5 -0
- data/lib/dossier.rb +31 -0
- data/lib/dossier/adapter/active_record.rb +40 -0
- data/lib/dossier/adapter/active_record/result.rb +24 -0
- data/lib/dossier/client.rb +52 -0
- data/lib/dossier/configuration.rb +28 -0
- data/lib/dossier/engine.rb +7 -0
- data/lib/dossier/formatter.rb +33 -0
- data/lib/dossier/query.rb +30 -0
- data/lib/dossier/report.rb +69 -0
- data/lib/dossier/result.rb +67 -0
- data/lib/dossier/stream_csv.rb +24 -0
- data/lib/dossier/version.rb +3 -0
- data/lib/tasks/dossier_tasks.rake +4 -0
- data/spec/dossier/adapter/active_record/result_spec.rb +31 -0
- data/spec/dossier/adapter/active_record_spec.rb +54 -0
- data/spec/dossier/client_spec.rb +109 -0
- data/spec/dossier/configuration_spec.rb +35 -0
- data/spec/dossier/formatter_spec.rb +39 -0
- data/spec/dossier/query_spec.rb +59 -0
- data/spec/dossier/report_spec.rb +67 -0
- data/spec/dossier/result_spec.rb +119 -0
- data/spec/dossier_spec.rb +31 -0
- data/spec/dummy/README.rdoc +261 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/javascripts/application.js +15 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/controllers/site_controller.rb +5 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/views/dossier/reports/suspended_employee.html.haml +1 -0
- data/spec/dummy/app/views/dossier/reports/total.html.haml +11 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +56 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml.example +13 -0
- data/spec/dummy/config/environment.rb +9 -0
- data/spec/dummy/config/environments/development.rb +37 -0
- data/spec/dummy/config/environments/production.rb +67 -0
- data/spec/dummy/config/environments/test.rb +37 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +5 -0
- data/spec/dummy/config/setup_load_paths.rb +15 -0
- data/spec/dummy/db/schema.rb +24 -0
- data/spec/dummy/dossier_test +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/fixtures/customized_employee_report.html +38 -0
- data/spec/fixtures/db/mysql2.yml.example +4 -0
- data/spec/fixtures/db/sqlite3.yml.example +2 -0
- data/spec/fixtures/employee_report.csv +4 -0
- data/spec/fixtures/employee_report.html +54 -0
- data/spec/fixtures/employee_report_with_footer.html +56 -0
- data/spec/fixtures/employee_with_custom_client.html +54 -0
- data/spec/requests/employee_report_spec.rb +52 -0
- data/spec/requests/employee_with_custom_client_spec.rb +13 -0
- data/spec/routing/dossier_routes_spec.rb +11 -0
- data/spec/spec_helper.rb +36 -0
- data/spec/support/factory.rb +86 -0
- data/spec/support/reports/employee_report.rb +78 -0
- data/spec/support/reports/employee_with_custom_client.rb +10 -0
- data/spec/support/reports/sqlite_employee_report.rb +15 -0
- data/spec/support/reports/supended_employee_report.rb +7 -0
- data/spec/support/reports/test_report.rb +4 -0
- data/spec/support/reports/total_report.rb +35 -0
- metadata +361 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2012 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
# Dossier
|
2
|
+
|
3
|
+
Dossier is a Rails engine that turns SQL into reports. Reports can be easily rendered in various formats, like HTML, CSV, and JSON. The SQL can be written by hand or generated by another tool, like ActiveRecord.
|
4
|
+
|
5
|
+
## Setup
|
6
|
+
|
7
|
+
Install the Dossier gem and create `config/dossier.yml`. This has the same format as Rails' `database.yml`, and can actually just be a symlink: `ln -s config/{database,dossier}.yml`.
|
8
|
+
|
9
|
+
## Routing
|
10
|
+
|
11
|
+
Dossier will add a route to your app so that `reports/fancy_ketchup` will instantiate and run a `FancyKetchupReport`. It will respond with whatever format was requested; for example `reports/fancy_ketchup.csv` will render the results as CSV.
|
12
|
+
|
13
|
+
## Basic Reports
|
14
|
+
|
15
|
+
In your app, create report classes under `app/reports`, with `Report` as the end of the class name. Define a `sql` method that returns the sql string to be sent to the database.
|
16
|
+
|
17
|
+
For example:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
# app/reports/fancy_ketchup_report.rb
|
21
|
+
class FancyKetchupReport < Dossier::Report
|
22
|
+
def sql
|
23
|
+
'SELECT * FROM ketchups WHERE fancy = true'
|
24
|
+
end
|
25
|
+
|
26
|
+
# Or, if you're using ActiveRecord and hate writing SQL:
|
27
|
+
def sql
|
28
|
+
Ketchup.where(fancy: true).to_sql
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
```
|
33
|
+
|
34
|
+
If you need dynamic values that may be influenced by the user, [do not interpolate them directly](http://xkcd.com/327/). Dossier provides a safer way to add them: any symbols in the query will be replaced by calling methods of the same name in the report. Return values other than numerics will be coerced to strings and **escaped by the database**.
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
# app/reports/fancy_ketchup_report.rb
|
38
|
+
class FancyKetchupReport < Dossier::Report
|
39
|
+
def sql
|
40
|
+
'SELECT * FROM ketchups WHERE brand = :brand'
|
41
|
+
end
|
42
|
+
|
43
|
+
def brand
|
44
|
+
'Acme'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
## Column Formatting
|
50
|
+
|
51
|
+
You can format any values in your results by defining a `format_` method for that column on your report class. For instance, to reverse the names of your employees:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
class EmployeeReport < Dossier::Report
|
55
|
+
# ...
|
56
|
+
def format_name(value)
|
57
|
+
value.reverse
|
58
|
+
end
|
59
|
+
end
|
60
|
+
```
|
61
|
+
|
62
|
+
Dossier also provides a `formatter` with access to all the standard Rails formatters. So to format all values in the `payment` column as currency, you could do:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
class MoneyLaunderingReport < Dossier::Report
|
66
|
+
#...
|
67
|
+
def format_payment(value)
|
68
|
+
formatter.number_to_currency
|
69
|
+
end
|
70
|
+
end
|
71
|
+
```
|
72
|
+
|
73
|
+
In addition, the formatter provides Rails' URL helpers for use in your reports. For example, in a report of your least profitable accounts, you might want to add a link to change the salesperson assigned to that account.
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
formatter.url_helpers.edit_accounts_path(3)
|
77
|
+
```
|
78
|
+
|
79
|
+
The built-in `ReportsController` uses this formatting when rendering the HTML and JSON representations, but not when rendering the CSV.
|
80
|
+
|
81
|
+
## Report Options and Footers
|
82
|
+
|
83
|
+
You may want to specify parameters for a report: which columns to show, a range of dates, etc. Dossier supports this via URL parameters, which will be passed into your report's `initialize` method and made available via the `options` reader.
|
84
|
+
|
85
|
+
You can pass these options by hardcoding them into a link, or you can allow users to customize a report with a form. For example:
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
# app/views/dossier/reports/employee.html.haml
|
89
|
+
|
90
|
+
= form_for report, as: :options, url: url_for, html: {method: :get} do |f|
|
91
|
+
|
92
|
+
= f.label "Salary greater than:"
|
93
|
+
= f.text_field :salary_greater_than
|
94
|
+
= f.label "In Division:"
|
95
|
+
= f.select_tag :in_division, divisions_collection
|
96
|
+
= f.button "Submit"
|
97
|
+
|
98
|
+
= render template: 'dossier/dossier/reports/show', locals: {report: report}
|
99
|
+
```
|
100
|
+
|
101
|
+
It's up to you to use these options in generating your SQL query.
|
102
|
+
|
103
|
+
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.
|
104
|
+
|
105
|
+
## Additional View Customization
|
106
|
+
|
107
|
+
To further customize your results view, provide your own `app/views/dossier/reports/show`.
|
108
|
+
|
109
|
+
## Callbacks
|
110
|
+
|
111
|
+
To produce report results, Dossier builds your query and executes it in separate steps. It uses [ActiveSupport::Callbacks](http://api.rubyonrails.org/classes/ActiveSupport/Callbacks.html) to define callbacks for `build_query` and `execute`. Therefore, you may provide callbacks similar to these:
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
set_callback :build_query, :before, :run_my_stored_procedure
|
115
|
+
set_callback :execute, :after do
|
116
|
+
mangle_results
|
117
|
+
end
|
118
|
+
```
|
119
|
+
|
120
|
+
## Using Reports Outside of Dossier::ReportsController
|
121
|
+
|
122
|
+
### With Other Controllers
|
123
|
+
|
124
|
+
You can use Dossier reports in your own controllers and views. For example, if you wanted to render two reports on a page with other information, you might do this in a controller:
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
class ProjectsController < ApplicationController
|
128
|
+
|
129
|
+
def show
|
130
|
+
@project = Project.find(params[:id])
|
131
|
+
@project_status_report = ProjectStatusReport.new(project: @project)
|
132
|
+
@project_revenue_report = ProjectRevenueReport.new(project: @project, grouped: 'monthly')
|
133
|
+
end
|
134
|
+
end
|
135
|
+
```
|
136
|
+
|
137
|
+
```haml
|
138
|
+
.span6
|
139
|
+
= render template: 'dossier/reports/show', locals: {report: @project_status_report.run}
|
140
|
+
.span6
|
141
|
+
= render template: 'dossier/reports/show', locals: {report: @project_revenue_report.run}
|
142
|
+
```
|
143
|
+
|
144
|
+
### Dossier for APIs
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
class API::ProjectsController < Api::ApplicationController
|
148
|
+
|
149
|
+
def snapshot
|
150
|
+
render json: ProjectStatusReport.new(project: @project).results.hashes
|
151
|
+
end
|
152
|
+
end
|
153
|
+
```
|
154
|
+
|
155
|
+
## Advanced Usage
|
156
|
+
|
157
|
+
To see a report with all the bells and whistles, check out `spec/support/reports/employee_report.rb`.
|
158
|
+
|
159
|
+
## Running the Tests
|
160
|
+
|
161
|
+
Note: when you run the tests, Dossier will **make and/or truncate** some tables in the `dossier_test` database.
|
162
|
+
|
163
|
+
- Run `bundle`
|
164
|
+
- `cp spec/dummy/config/database.yml{.example,}` and edit it so that it can connect to the test database.
|
165
|
+
- `rspec spec`
|
166
|
+
|
167
|
+
## TODO
|
168
|
+
|
169
|
+
### Features
|
170
|
+
|
171
|
+
- Support more databases
|
172
|
+
|
173
|
+
### Moar Dokumentationz pleaze
|
174
|
+
|
175
|
+
- Document using hooks and what methods are available in them
|
176
|
+
- callbacks
|
177
|
+
- stored procedures
|
178
|
+
- reformat results
|
179
|
+
- linking to reports
|
180
|
+
- linking to formats
|
181
|
+
- Extending the formatter
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
begin
|
8
|
+
require 'rdoc/task'
|
9
|
+
rescue LoadError
|
10
|
+
require 'rdoc/rdoc'
|
11
|
+
require 'rake/rdoctask'
|
12
|
+
RDoc::Task = Rake::RDocTask
|
13
|
+
end
|
14
|
+
|
15
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
16
|
+
rdoc.rdoc_dir = 'rdoc'
|
17
|
+
rdoc.title = 'Dossier'
|
18
|
+
rdoc.options << '--line-numbers'
|
19
|
+
rdoc.rdoc_files.include('README.rdoc')
|
20
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
21
|
+
end
|
22
|
+
|
23
|
+
APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
|
24
|
+
load 'rails/tasks/engine.rake'
|
25
|
+
|
26
|
+
Bundler::GemHelper.install_tasks
|
27
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// the compiled file.
|
9
|
+
//
|
10
|
+
// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
|
11
|
+
// GO AFTER THE REQUIRES BELOW.
|
12
|
+
//
|
13
|
+
//= require jquery
|
14
|
+
//= require jquery_ujs
|
15
|
+
//= require_tree .
|
@@ -0,0 +1,13 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the top of the
|
9
|
+
* compiled file, but it's generally better to create a new file per style scope.
|
10
|
+
*
|
11
|
+
*= require_self
|
12
|
+
*= require_tree .
|
13
|
+
*/
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Dossier
|
2
|
+
class ReportsController < ApplicationController
|
3
|
+
def show
|
4
|
+
report = report_class.new(params[:options] || {})
|
5
|
+
report.run
|
6
|
+
|
7
|
+
respond_to do |format|
|
8
|
+
format.html do
|
9
|
+
begin
|
10
|
+
render template: "dossier/reports/#{report.view}", locals: {report: report}
|
11
|
+
rescue ActionView::MissingTemplate => e
|
12
|
+
render template: 'dossier/reports/show', locals: {report: report}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
format.json do
|
17
|
+
render :json => report.results.hashes
|
18
|
+
end
|
19
|
+
|
20
|
+
format.csv do
|
21
|
+
headers["Content-Disposition"] = %[attachment;filename=#{params[:report]}-report_#{Time.now.strftime('%m-%d-%Y-%H%M%S')}.csv]
|
22
|
+
self.response_body = StreamCSV.new(report.raw_results.arrays)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def report_class
|
30
|
+
"#{params[:report].classify}Report".constantize
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
%h1= report.class.name.titleize
|
2
|
+
|
3
|
+
%table
|
4
|
+
%thead
|
5
|
+
%tr
|
6
|
+
- report.results.headers.each do |header|
|
7
|
+
%th= Dossier::Formatter.titleize(header)
|
8
|
+
%tbody
|
9
|
+
- report.results.body.each do |row|
|
10
|
+
%tr
|
11
|
+
- row.each do |value|
|
12
|
+
%td= value
|
13
|
+
|
14
|
+
- if report.results.footers.any?
|
15
|
+
%tfoot
|
16
|
+
- report.results.footers.each do |row|
|
17
|
+
%tr
|
18
|
+
- row.each do |value|
|
19
|
+
%th= value
|
20
|
+
|
21
|
+
= link_to 'Download CSV', dossier_report_path(:format => 'csv', :options => params[:options]), :class => 'download-csv'
|
data/config/routes.rb
ADDED
data/lib/dossier.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require "dossier/engine"
|
2
|
+
require "dossier/version"
|
3
|
+
|
4
|
+
module Dossier
|
5
|
+
|
6
|
+
def self.configuration
|
7
|
+
@configuration || configure
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.configure
|
11
|
+
@configuration = Configuration.new
|
12
|
+
yield(@configuration) if block_given?
|
13
|
+
@configuration
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.client
|
17
|
+
configuration.client
|
18
|
+
end
|
19
|
+
|
20
|
+
class ExecuteError < StandardError; end
|
21
|
+
end
|
22
|
+
|
23
|
+
require "dossier/adapter/active_record"
|
24
|
+
require "dossier/adapter/active_record/result"
|
25
|
+
require "dossier/client"
|
26
|
+
require "dossier/configuration"
|
27
|
+
require "dossier/formatter"
|
28
|
+
require "dossier/query"
|
29
|
+
require "dossier/report"
|
30
|
+
require "dossier/result"
|
31
|
+
require "dossier/stream_csv"
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Dossier
|
2
|
+
module Adapter
|
3
|
+
class ActiveRecord
|
4
|
+
|
5
|
+
attr_accessor :options, :connection
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
self.options = options
|
9
|
+
self.connection = options.delete(:connection) || active_record_connection
|
10
|
+
end
|
11
|
+
|
12
|
+
def escape(value)
|
13
|
+
connection.quote(value)
|
14
|
+
end
|
15
|
+
|
16
|
+
def execute(query, report_name = nil)
|
17
|
+
Result.new(connection.exec_query(*[query, report_name].compact))
|
18
|
+
rescue => e
|
19
|
+
raise Dossier::ExecuteError.new "#{e.message}\n\n#{query}"
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def active_record_connection
|
25
|
+
@abstract_class = Class.new(::ActiveRecord::Base) do
|
26
|
+
self.abstract_class = true
|
27
|
+
|
28
|
+
# Needs a unique name for ActiveRecord's connection pool
|
29
|
+
def self.name
|
30
|
+
"Dossier::Adapter::ActiveRecord::Connection_#{object_id}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
@abstract_class.establish_connection(options)
|
34
|
+
@abstract_class.connection
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Dossier
|
2
|
+
module Adapter
|
3
|
+
class ActiveRecord
|
4
|
+
class Result
|
5
|
+
|
6
|
+
attr_accessor :result
|
7
|
+
|
8
|
+
def initialize(activerecord_result)
|
9
|
+
self.result = activerecord_result
|
10
|
+
end
|
11
|
+
|
12
|
+
def headers
|
13
|
+
result.columns
|
14
|
+
end
|
15
|
+
|
16
|
+
def rows
|
17
|
+
result.rows
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Dossier
|
2
|
+
class Client
|
3
|
+
|
4
|
+
attr_accessor :adapter, :options
|
5
|
+
|
6
|
+
delegate :escape, :execute, to: :adapter
|
7
|
+
|
8
|
+
def initialize(options)
|
9
|
+
self.options = options.symbolize_keys
|
10
|
+
self.adapter = dossier_adapter.new(self.options.except(:dossier_adapter))
|
11
|
+
end
|
12
|
+
|
13
|
+
def dossier_adapter
|
14
|
+
adapter_name = options.fetch(:dossier_adapter) { determine_adapter_name }
|
15
|
+
"Dossier::Adapter::#{adapter_name.classify}".constantize
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def determine_adapter_name
|
21
|
+
if options.has_key?(:connection)
|
22
|
+
namespace_for(options[:connection].class)
|
23
|
+
else
|
24
|
+
guess_adapter_name
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def namespace_for(klass)
|
29
|
+
klass.name.split('::').first.underscore
|
30
|
+
end
|
31
|
+
|
32
|
+
def guess_adapter_name
|
33
|
+
return namespace_for(loaded_orms.first) if loaded_orms.length == 1
|
34
|
+
|
35
|
+
message = <<-Must_be_one_of_them_newfangled_ones.strip_heredoc
|
36
|
+
You didn't specify a dossier_adapter. If you had exactly one
|
37
|
+
ORM loaded that Dossier knew about, it would try to choose an
|
38
|
+
appropriate adapter, but you have #{loaded_orms.length}.
|
39
|
+
Must_be_one_of_them_newfangled_ones
|
40
|
+
message << "Specifically, Dossier found #{loaded_orms.join(', ')}" if loaded_orms.any?
|
41
|
+
raise IndeterminableAdapter.new(message)
|
42
|
+
end
|
43
|
+
|
44
|
+
def loaded_orms
|
45
|
+
[].tap do |loaded_orms|
|
46
|
+
loaded_orms << ActiveRecord::Base if defined?(ActiveRecord)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class IndeterminableAdapter < StandardError; end
|
51
|
+
end
|
52
|
+
end
|