adhoq 0.0.2 → 0.0.3

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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +45 -12
  3. data/app/assets/javascripts/adhoq/application.js +1 -0
  4. data/app/assets/javascripts/adhoq/current_tables.js.coffee +4 -0
  5. data/app/assets/stylesheets/adhoq/adhoq.css.sass +44 -0
  6. data/app/assets/stylesheets/adhoq/bootstrap.scss +1 -1
  7. data/app/controllers/adhoq/authorization_methods.rb +1 -1
  8. data/app/controllers/adhoq/current_tables_controller.rb +20 -0
  9. data/app/controllers/adhoq/executions_controller.rb +2 -2
  10. data/app/helpers/adhoq/application_helper.rb +13 -0
  11. data/app/models/adhoq/execution.rb +1 -1
  12. data/app/models/adhoq/report.rb +10 -17
  13. data/app/views/adhoq/application/_global_nav.html.slim +1 -0
  14. data/app/views/adhoq/current_tables/index.html.slim +32 -0
  15. data/app/views/adhoq/queries/_form.html.slim +29 -9
  16. data/app/views/adhoq/queries/_query.html.slim +1 -1
  17. data/config/routes.rb +2 -1
  18. data/lib/adhoq.rb +7 -6
  19. data/lib/adhoq/adhoc_execution.rb +11 -0
  20. data/lib/adhoq/executor.rb +12 -1
  21. data/lib/adhoq/global_variable.rb +8 -5
  22. data/lib/adhoq/reporter.rb +14 -0
  23. data/lib/adhoq/storage.rb +12 -1
  24. data/lib/adhoq/storage/fog_storage.rb +22 -0
  25. data/lib/adhoq/storage/local_file.rb +10 -29
  26. data/lib/adhoq/storage/s3.rb +24 -0
  27. data/lib/adhoq/version.rb +1 -1
  28. data/spec/adhoq/executor_spec.rb +13 -0
  29. data/spec/adhoq/global_variable_spec.rb +37 -0
  30. data/spec/adhoq/reporter/xlsx_spec.rb +18 -0
  31. data/spec/adhoq/storage_spec.rb +12 -2
  32. data/spec/spec_helper.rb +29 -2
  33. data/spec/support/codeclimate_reporter.rb +4 -0
  34. data/spec/support/feature_spec_helper.rb +5 -0
  35. data/spec/support/have_values_in_xlsx_sheet_matcher.rb +9 -3
  36. metadata +97 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cdf4e3ebbc61cde7c4ab96819f90274043380030
4
- data.tar.gz: ba15a928b583929ebf15ef137eb72feb416cfdbd
3
+ metadata.gz: a9fac13cfa25151dce3d5c579c39ba8098f07b6f
4
+ data.tar.gz: 23f822e233cc7977a2461dbe681a30d2426fffd5
5
5
  SHA512:
6
- metadata.gz: b3ba6a550268f3a7b899b240b720a15fb5d5000434cd2812bc43b001f0c9e86251d24112abd06400be1c952cca25f6188b795cfb8c9a8d4fbbf1c716a3874273
7
- data.tar.gz: 7cdc68ee8876b133c3bfde9830d66ca1f06cd7c0af4ebedca813afdc8ce9573123ea97220a7f810dec73ecd35a68c895c9c3a3f533bece43ac6ef5ee4acb7541
6
+ metadata.gz: f6d1804945abba5952f20f671f68e5fab9141d474dfdcc70aab8a6317cd3c975296e244b6d480a27a2e19066ec2c56eeaadde874ddd3617281275b23c08de6bb
7
+ data.tar.gz: 25dac12d074dff4dead41cb23271447737f237103aa494f4148b509b0a6f8cb30be2c1bd3aff9e63fe83763836c744be5249e48422d78d290afdd26685473904
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
- # Adhoq
1
+ Adhoq [![Build Status](https://travis-ci.org/esminc/adhoq.svg)](https://travis-ci.org/esminc/adhoq) [![Code Climate](https://codeclimate.com/github/esminc/adhoq/badges/gpa.svg)](https://codeclimate.com/github/esminc/adhoq)
2
+ ====
2
3
 
3
4
  Rails engine to generate instant reports from adhoc SQL query.
4
5
 
@@ -6,16 +7,17 @@ Rails engine to generate instant reports from adhoc SQL query.
6
7
 
7
8
  ## Features
8
9
 
9
- - [x] Rails 4.x support
10
- - [ ] Rails 3.2 support
10
+ - Export ad-hoc SQL result to .xlsx file
11
+ - Persist generated report as local file or in AWS S3
12
+ - Rails 4.x & 3.2 support
13
+ - Nice administration console with rails engine
14
+
15
+ ### Future planning
16
+
11
17
  - Export reports in some formats:
12
- - [x] .xlsx
13
18
  - [ ] .csv
14
19
  - [ ] .json
15
- - Report storage supports:
16
- - [x] as local file
17
- - [ ] S3 (via `Fog::Storage`)
18
- - [ ] In application export function helper
20
+ - [ ] Label data substitution
19
21
 
20
22
  ## Installation
21
23
 
@@ -58,22 +60,53 @@ Edit initialization file in `config/initializer/adhoq.rb`
58
60
 
59
61
  ```ruby
60
62
  Adhoq.configure do |config|
61
- config.storage = [:local_file, Rails.root + '/path/to/store/report/files']
63
+ config.storage = [:local_file, Rails.root + './path/to/store/report/files']
62
64
  config.authorization = ->(controller) { controller.signed_in? }
63
65
  end
64
66
  ```
65
67
 
66
- See configuration example in [dummy app](https://github.com/esminc/adhoq/commit/63cc31fe209baae7211c10f76e7b1c35f4c699ef).
68
+ See configuration example in [dummy app](https://github.com/esminc/adhoq/blob/master/spec/dummy/config/initializers/adhoq.rb).
67
69
 
68
70
  Then restart server and try it out.
69
71
 
70
72
  ### As Plain old library (application export helper)
71
73
 
72
- TODO: Write usage instructions here
74
+ Adhoq also provides report generation from SQL string, not from mounted rails engine.
75
+
76
+ ```ruby
77
+ execution = Adhoq::AdhocExecution.new(
78
+ 'xlsx',
79
+ 'SELECT "hello" AS name ,"English greeting message" AS description'
80
+ )
81
+
82
+ Adhoq::Reporter.generate(execution) #=> report data
83
+ ```
84
+
85
+ Persistence is also available without engine via `Adhoq::Storage::SomeClass#store`.
86
+ Below is example that generating report and persist to in Rails application report method.
87
+
88
+ ```ruby
89
+ execution = Adhoq::AdhocExecution.new(
90
+ 'xlsx',
91
+ 'SELECT "hello" AS name ,"English greeting message" AS description'
92
+ )
93
+
94
+ storage = Storage::S3.new(
95
+ 'my-adhoq-bucket',
96
+ aws_access_key_id: 'key_id',
97
+ aws_secret_access_key: 'access_key'
98
+ )
99
+
100
+ # generate report and store it to S3, returns `key` to get report data
101
+ key = storage.store('.xlsx') { Adhoq::Reporter.generate(execution) }
102
+
103
+ ...
104
+ storage.get(key) #=> report data
105
+ ```
73
106
 
74
107
  ## Contributing
75
108
 
76
- 1. Fork it ( https://github.com/[my-github-username]/adhoq/fork )
109
+ 1. Fork it ( https://github.com/esminc/adhoq/fork )
77
110
  2. Create your feature branch (`git checkout -b my-new-feature`)
78
111
  3. Commit your changes (`git commit -am 'Add some feature'`)
79
112
  4. Push to the branch (`git push origin my-new-feature`)
@@ -15,5 +15,6 @@
15
15
  //= require jquery_ujs
16
16
  //= require ./bootstrap
17
17
  //= require ./previewer
18
+ //= require ./current_tables
18
19
 
19
20
  window.Adhoq = {}
@@ -0,0 +1,4 @@
1
+ Adhoq.loadCurrentTableTabOnce = ($el)->
2
+ pane = $("#{$el.attr('href')}:has(.loading)")
3
+
4
+ pane.load(pane.find('a.loading').attr('href'))
@@ -55,3 +55,47 @@ form.query-form
55
55
 
56
56
  .fa-pad-l
57
57
  padding-left: $short-span / 2
58
+
59
+ #current-tables
60
+ font-size: $font-size-base * 0.9
61
+
62
+ table
63
+ width: 800px
64
+
65
+ caption
66
+ text-align: left
67
+ margin-bottom: $short-span
68
+ font-size: $font-size-base * 1.5
69
+
70
+ span.name
71
+ font-family: monospace
72
+ small.count
73
+ font-size: $font-size-base * 1
74
+ color: gray
75
+ margin-left: $font-size-base
76
+
77
+ th.pk, td.pk
78
+ width: $font-size-base * 1.5
79
+ th, td.icon
80
+ text-align: center
81
+ th.number, td.number
82
+ text-align: right
83
+ th.monospace, td.monospace
84
+ font-family: monospace
85
+ th.data
86
+ width: $font-size-base * 10
87
+
88
+ .tab-content
89
+ margin-top: $font-size-base
90
+ padding-left: 1em
91
+ padding-right: 1em
92
+
93
+ max-height: 600px
94
+ overflow: scroll
95
+
96
+ .tab-pane > h3
97
+ font-size: $font-size-base * 1.5
98
+
99
+ small
100
+ font-size: $font-size-base
101
+ margin-left: $font-size-base
@@ -5,7 +5,7 @@
5
5
  // Reset and dependencies
6
6
  @import "bootstrap/normalize";
7
7
  @import "bootstrap/print";
8
- @import "bootstrap/glyphicons";
8
+ // @import "bootstrap/glyphicons";
9
9
 
10
10
  // Core CSS
11
11
  @import "bootstrap/scaffolding";
@@ -3,7 +3,7 @@ module Adhoq
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do |controller|
6
- controller.before_action Authorizer.new
6
+ controller.before_filter Authorizer.new
7
7
 
8
8
  helper_method :adhoq_current_user
9
9
  hide_action :adhoq_current_user
@@ -0,0 +1,20 @@
1
+ module Adhoq
2
+ class CurrentTablesController < Adhoq::ApplicationController
3
+ before_filter :eager_load_models
4
+
5
+ def index
6
+ @ar_classes = ActiveRecord::Base.subclasses.
7
+ reject {|klass| klass.name == 'ActiveRecord::SchemaMigration' }.
8
+ sort_by(&:name)
9
+
10
+ render layout: false
11
+ end
12
+
13
+ private
14
+
15
+ def eager_load_models
16
+ return unless Rails.env.development?
17
+ [Rails.application, Adhoq::Engine].each(&:eager_load!)
18
+ end
19
+ end
20
+ end
@@ -1,5 +1,5 @@
1
1
  module Adhoq
2
- class ExecutionsController < ApplicationController
2
+ class ExecutionsController < Adhoq::ApplicationController
3
3
  def show
4
4
  @execution = current_query.executions.where(id: params[:id], report_format: params[:format]).first!
5
5
 
@@ -19,7 +19,7 @@ module Adhoq
19
19
  end
20
20
 
21
21
  def respond_report(report)
22
- send_data report.data.read, type: report.mime_type, filename: report.name, disposition: 'attachment'
22
+ send_data report.data, type: report.mime_type, filename: report.name, disposition: 'attachment'
23
23
  end
24
24
  end
25
25
  end
@@ -7,5 +7,18 @@ module Adhoq
7
7
  klass.model_name.humanize
8
8
  end
9
9
  end
10
+
11
+ def icon_fa(name, additional_classes = [])
12
+ tag('i', class: ['fa', "fa-#{name}", *additional_classes])
13
+ end
14
+
15
+ def schema_version
16
+ if defined?(ActiveRecord::SchemaMigration)
17
+ ActiveRecord::SchemaMigration.maximum(:version)
18
+ else
19
+ result = Adhoq::Executor.select("SELECT MAX(version) AS current_version FROM #{ActiveRecord::Migrator.schema_migrations_table_name}")
20
+ result.rows.first
21
+ end
22
+ end
10
23
  end
11
24
  end
@@ -18,7 +18,7 @@ module Adhoq
18
18
  end
19
19
 
20
20
  def success?
21
- report.try(:success?)
21
+ report.try(:available?)
22
22
  end
23
23
 
24
24
  # TODO go decorator or view model or so
@@ -1,11 +1,8 @@
1
1
  module Adhoq
2
2
  class Report < ActiveRecord::Base
3
- BUFSIZE = 10.kilobytes.to_i
4
-
5
3
  belongs_to :execution
6
4
 
7
- delegate :name, to: 'execution'
8
- delegate :mime_type, to: :reporter
5
+ delegate :name, to: 'execution'
9
6
 
10
7
  def generate!(storage = Adhoq.current_storage)
11
8
  self.identifier = generate_and_persist_report!(storage)
@@ -15,28 +12,24 @@ module Adhoq
15
12
  save!
16
13
  end
17
14
 
18
- def success?
19
- data.present?
15
+ def available?
16
+ identifier.present? && (storage == Adhoq.current_storage.identifier)
20
17
  end
21
18
 
22
19
  def data(storage = Adhoq.current_storage)
23
20
  storage.get(identifier)
24
21
  end
25
22
 
26
- private
27
-
28
- def reporter
29
- {'xlsx' => Adhoq::Reporter::Xlsx}[execution.report_format]
23
+ def mime_type
24
+ Adhoq::Reporter.lookup(execution.report_format).mime_type
30
25
  end
31
26
 
32
- def generate_and_persist_report!(storage)
33
- storage.store(".#{execution.report_format}") do |file, *|
34
- executor = Executor.new(execution.raw_sql)
27
+ private
35
28
 
36
- reporter.new(executor.execute).build_report.each(BUFSIZE) do |chunk|
37
- file.write chunk
38
- end
39
- end
29
+ def generate_and_persist_report!(storage)
30
+ storage.store(".#{execution.report_format}") {
31
+ Adhoq::Reporter.generate(execution)
32
+ }
40
33
  end
41
34
  end
42
35
  end
@@ -9,3 +9,4 @@ nav#global-nav.navbar.navbar-default(role='navigation')
9
9
  = link_to main_app.root_path do
10
10
  i.fa.fa-arrow-circle-right.fa-pad-r
11
11
  | Main app
12
+
@@ -0,0 +1,32 @@
1
+ h3
2
+ | Current database schema
3
+ small= "Version #{schema_version}"
4
+
5
+ ul.list-unstyled.tables
6
+ - @ar_classes.each do |ar_class|
7
+ - first_record = ar_class.unscoped.order(:id).first
8
+
9
+ li.ar_class data-table-name=ar_class.table_name
10
+ table.table.table-striped.table-hover.table-bordered
11
+ caption
12
+ span.name= ar_class.table_name
13
+ small.count #{ar_class.unscoped.count} rows
14
+ thead
15
+ tr
16
+ th.col-sm-1.pk PK
17
+ th.col-sm-2.name Name
18
+ th.col-sm-1.type Type
19
+ th.col-sm-1.null Non-Null
20
+ th.col-sm-1.limit Limit
21
+ th.col-sm-2.default Default
22
+ th.col-sm-4.data unscoped.first
23
+ tbody
24
+ - ar_class.columns.each do |column|
25
+ tr
26
+ td.pk.icon= column.primary ? icon_fa('check-circle') : ''
27
+ td.monospace= column.name
28
+ td= column.type
29
+ td.null.icon= column.null ? '' : icon_fa('check')
30
+ td.limit.number= column.limit
31
+ td= column.default
32
+ td.monospace= first_record.try {|r| truncate(r.read_attribute_before_type_cast(column.name).to_s, length: 50) }
@@ -24,15 +24,35 @@
24
24
  .col-sm-8
25
25
  = f.text_area :query, class: 'form-control', rows: 10, required: true
26
26
 
27
- .preview
28
- h2
29
- | Preview
30
- .btn-in-header
31
- = link_to preview_path, class: 'btn btn-default btn-sm js-preview-button', data: {source: '#query_query', result: '.js-preview-result', remote: true, method: 'POST'} do
32
- i.fa.fa-refresh[data-title='Refresh preview']
27
+ ul.nav.nav-tabs[role='tablist']
28
+ li.active
29
+ a[role='tab' data-toggle='tab' href='#preview' ]
30
+ i.fa.fa-eye.fa-pad-r
31
+ | Preview
32
+ li
33
+ a[role='tab' data-toggle='tab' href='#current-tables']
34
+ i.fa.fa-database.fa-pad-r
35
+ | Tables
33
36
 
34
- .js-preview-result
35
- .alert Preview is shown here
37
+ .tab-content
38
+ #preview.tab-pane.active
39
+ h3
40
+ | Query preview
41
+ small
42
+ = link_to preview_path, class: 'js-preview-button', data: {source: '#query_query', result: '.js-preview-result', remote: true, method: 'POST'} do
43
+ i.fa.fa-refresh.fa-pad-r[data-title='Refresh preview']
44
+ | Reflesh
45
+
46
+ .js-preview-result
47
+ .alert.alert-info Preview is shown here
48
+
49
+ #current-tables.tab-pane
50
+ a.loading[href=current_tables_path]
36
51
 
37
52
  javascript:
38
- $(function() { Adhoq.enablePreview($('.preview a.js-preview-button')) })
53
+ $(function() {
54
+ Adhoq.enablePreview($('#preview a.js-preview-button'));
55
+
56
+ $('a[data-toggle="tab"]').on('show.bs.tab', function(ev) { Adhoq.loadCurrentTableTabOnce($(ev.target)) });
57
+ });
58
+
@@ -38,6 +38,6 @@ section.query
38
38
  span.label[class=(exec.success? ? 'label-success' : 'label-danger')]= exec.status_label
39
39
  td.report
40
40
  - if exec.success?
41
- = link_to([query, exec, format: exec.report_format], class: 'btn btn-sm btn-default') do
41
+ = link_to(adhoq.query_execution_path(query, exec, format: exec.report_format), class: 'btn btn-sm btn-default') do
42
42
  i.fa.fa-download.fa-pad-r
43
43
  = exec.report_format
data/config/routes.rb CHANGED
@@ -5,5 +5,6 @@ Adhoq::Engine.routes.draw do
5
5
  resources :executions, only: %w(create show)
6
6
  end
7
7
 
8
- resource :preview, only: 'create'
8
+ resource :preview, only: 'create'
9
+ resources :current_tables, only: 'index'
9
10
  end
data/lib/adhoq.rb CHANGED
@@ -2,12 +2,13 @@ require 'adhoq/engine'
2
2
  require 'adhoq/global_variable'
3
3
 
4
4
  module Adhoq
5
- autoload 'Configuration', 'adhoq/configuration'
6
- autoload 'Error', 'adhoq/error'
7
- autoload 'Executor', 'adhoq/executor'
8
- autoload 'Reporter', 'adhoq/reporter'
9
- autoload 'Result', 'adhoq/result'
10
- autoload 'Storage', 'adhoq/storage'
5
+ autoload 'AdhocExecution', 'adhoq/adhoc_execution'
6
+ autoload 'Configuration', 'adhoq/configuration'
7
+ autoload 'Error', 'adhoq/error'
8
+ autoload 'Executor', 'adhoq/executor'
9
+ autoload 'Reporter', 'adhoq/reporter'
10
+ autoload 'Result', 'adhoq/result'
11
+ autoload 'Storage', 'adhoq/storage'
11
12
 
12
13
  extend Adhoq::GlobalVariable
13
14
  end
@@ -0,0 +1,11 @@
1
+ # Provides polymorphic API with Adhoc::Execution AR Model
2
+ module Adhoq
3
+ class AdhocExecution
4
+ attr_reader :report_format, :raw_sql
5
+
6
+ def initialize(report_format, raw_sql)
7
+ @report_format = report_format
8
+ @raw_sql = raw_sql
9
+ end
10
+ end
11
+ end
@@ -2,12 +2,23 @@ module Adhoq
2
2
  class Executor
3
3
  class << self
4
4
  def select(query)
5
- current_connection.send(:select, query)
5
+ with_sandbox do
6
+ current_connection.exec_query(query)
7
+ end
6
8
  end
7
9
 
8
10
  def current_connection
9
11
  ActiveRecord::Base.connection
10
12
  end
13
+
14
+ def with_sandbox
15
+ result = nil
16
+ ActiveRecord::Base.transaction do
17
+ result = yield
18
+ raise ActiveRecord::Rollback
19
+ end
20
+ result
21
+ end
11
22
  end
12
23
 
13
24
  def initialize(query)
@@ -22,13 +22,16 @@ module Adhoq
22
22
 
23
23
  private
24
24
 
25
- # TODO Implement S3
26
25
  def setup_storage(type, *args)
27
- unless type == :local_file
28
- raise NotImplementedError
29
- end
26
+ klass =
27
+ case type
28
+ when :local_file then Adhoq::Storage::LocalFile
29
+ when :s3 then Adhoq::Storage::S3
30
+ else
31
+ raise NotImplementedError
32
+ end
30
33
 
31
- Adhoq::Storage::LocalFile.new(*args)
34
+ klass.new(*args)
32
35
  end
33
36
  end
34
37
  end
@@ -1,5 +1,19 @@
1
1
  module Adhoq
2
2
  module Reporter
3
3
  autoload 'Xlsx', 'adhoq/reporter/xlsx'
4
+
5
+ class << self
6
+ def generate(execution)
7
+ executor = Executor.new(execution.raw_sql)
8
+ reporter = lookup(execution.report_format).new(executor.execute)
9
+
10
+ reporter.build_report
11
+ end
12
+
13
+ def lookup(format)
14
+ @map ||= {'xlsx' => Adhoq::Reporter::Xlsx}
15
+ @map[format]
16
+ end
17
+ end
4
18
  end
5
19
  end
data/lib/adhoq/storage.rb CHANGED
@@ -1,5 +1,16 @@
1
1
  module Adhoq
2
2
  module Storage
3
- autoload 'LocalFile', 'adhoq/storage/local_file'
3
+ autoload 'FogStorage', 'adhoq/storage/fog_storage'
4
+ autoload 'LocalFile', 'adhoq/storage/local_file'
5
+ autoload 'S3', 'adhoq/storage/s3'
6
+
7
+ def with_new_identifier(suffix = nil, seed = Time.now)
8
+ dirname, fname_seed = ['%Y-%m-%d', '%H%M%S.%L'].map {|f| seed.strftime(f) }
9
+
10
+ basename = "%s_%05d%s" % [fname_seed, Process.pid, suffix]
11
+
12
+ [dirname, basename].join('/').tap {|id| yield id }
13
+ end
14
+ module_function :with_new_identifier
4
15
  end
5
16
  end
@@ -0,0 +1,22 @@
1
+ module Adhoq
2
+ module Storage
3
+ class FogStorage
4
+ def store(suffix = nil, seed = Time.now, &block)
5
+ Adhoq::Storage.with_new_identifier(suffix, seed) do |identifier|
6
+ io = yield
7
+ io.rewind
8
+
9
+ directory.files.create(key: identifier, body: io, public: false)
10
+ end
11
+ end
12
+
13
+ def get(identifier)
14
+ get_raw(identifier).body
15
+ end
16
+
17
+ def get_raw(identifier)
18
+ directory.files.head(identifier)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,44 +1,25 @@
1
+ require 'fog'
2
+
1
3
  module Adhoq
2
4
  module Storage
3
- class LocalFile
5
+ class LocalFile < FogStorage
4
6
  attr_reader :root
5
7
 
6
8
  def initialize(root_path)
7
- @root = Pathname.new(root_path)
8
- end
9
-
10
- def identifier
11
- "file://#{@root.realpath}"
12
- end
13
-
14
- def store(suffix = nil, seed = Time.now, &block)
15
- calculate_identifier(suffix, seed).tap do |identifier|
16
- mkpath!(identifier)
9
+ path = Pathname.new(root_path)
17
10
 
18
- (@root + identifier).open('w:BINARY') do |file|
19
- yield file, identifier
20
- file.flush
21
- end
22
- end
11
+ @fog = Fog::Storage.new(provider: 'Local', local_root: path.parent)
12
+ @dir = path.basename.to_s
23
13
  end
24
14
 
25
- def get(identifier)
26
- (@root + identifier).open('r:BINARY')
15
+ def identifier
16
+ "file://#{[@fog.local_root, @dir].join('/')}"
27
17
  end
28
18
 
29
19
  private
30
20
 
31
- def calculate_identifier(suffix, seed)
32
- dirname, fname_seed = ['%Y-%m-%d', '%H%M%S.%L'].map {|f| seed.strftime(f) }
33
-
34
- basename = "%s_%05d%s" % [fname_seed, Process.pid, suffix]
35
-
36
- identifier = [dirname, basename].join('/')
37
- end
38
-
39
- def mkpath!(identifier)
40
- dir = identifier.split('/').first
41
- (@root + dir).mkpath
21
+ def directory
22
+ @fog.directories.get(@dir) || @fog.directories.create(key: @dir)
42
23
  end
43
24
  end
44
25
  end
@@ -0,0 +1,24 @@
1
+ require 'fog'
2
+
3
+ module Adhoq
4
+ module Storage
5
+ class S3 < FogStorage
6
+ def initialize(bucket, s3_options = {})
7
+ @bucket = bucket
8
+ @s3 = Fog::Storage.new({provider: 'AWS'}.merge(s3_options))
9
+ end
10
+
11
+ def identifier
12
+ "s3://#{@bucket}"
13
+ end
14
+
15
+ private
16
+
17
+ def directory
18
+ return @directory if @directory
19
+
20
+ @directory = @s3.directories.get(@bucket) || @s3.directories.create(key: @bucket, public: false)
21
+ end
22
+ end
23
+ end
24
+ end
data/lib/adhoq/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Adhoq
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
@@ -7,5 +7,18 @@ module Adhoq
7
7
 
8
8
  specify { expect(executor.execute).to eq Adhoq::Result.new(%w[answer], [[42]]) }
9
9
  end
10
+
11
+ describe '.select' do
12
+ specify 'Do not reflect write access' do
13
+ expect {
14
+ Executor.select(<<-INSERT_SQL.strip_heredoc)
15
+ INSERT INTO "adhoq_queries"
16
+ ("description", "name", "query", "updated_at", "created_at")
17
+ VALUES
18
+ ("description", "name", "SELECT 1", "NOW", "NOW")
19
+ INSERT_SQL
20
+ }.not_to change(Adhoq::Query, :count)
21
+ end
22
+ end
10
23
  end
11
24
  end
@@ -0,0 +1,37 @@
1
+ module Adhoq
2
+ RSpec.describe GlobalVariable, type: :model do
3
+ def reset_storage_config(*storage_config)
4
+ Adhoq.instance_variable_set('@current_storage', nil)
5
+ Adhoq.config.storage = storage_config
6
+ end
7
+
8
+ around(:each) do |example|
9
+ begin
10
+ original_config = Adhoq.config.storage
11
+ example.run
12
+ ensure
13
+ reset_storage_config(*original_config)
14
+ end
15
+ end
16
+
17
+ context 'config.storage = [:local_file, ....]' do
18
+ before do
19
+ reset_storage_config(:local_file, Rails.root + "/tmp/adhoq/#{Rails.env}")
20
+ end
21
+
22
+ specify do
23
+ expect(Adhoq.current_storage).to be_instance_of Adhoq::Storage::LocalFile
24
+ end
25
+ end
26
+
27
+ context 'config.storage = [:s3, ....]' do
28
+ before do
29
+ reset_storage_config(:s3, 'my-bucket-name', aws_access_key_id: 'key-id', aws_secret_access_key: 'secret', region: 'paris-01')
30
+ end
31
+
32
+ specify do
33
+ expect(Adhoq.current_storage).to be_instance_of Adhoq::Storage::S3
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,18 @@
1
+ module Adhoq
2
+ RSpec.describe Reporter::Xlsx, type: :model do
3
+ context 'create xlsx report' do
4
+ let(:report_data) do
5
+ Adhoq::Reporter.generate(Adhoq::AdhocExecution.new('xlsx', <<-SQL.strip_heredoc))
6
+ SELECT "hello" AS name ,"English greeting message" AS description
7
+ SQL
8
+ end
9
+
10
+ specify do
11
+ expect(report_data.read).to have_values_in_xlsx_sheet([
12
+ %w[name description],
13
+ ['hello', 'English greeting message']
14
+ ])
15
+ end
16
+ end
17
+ end
18
+ end
@@ -10,10 +10,20 @@ module Adhoq
10
10
  let(:storage) { Storage::LocalFile.new(tempdir) }
11
11
 
12
12
  let(:identifier) do
13
- storage.store('.txt') {|file, ident| file.puts 'Hello adhoq!' }
13
+ storage.store('.txt') { StringIO.new("Hello adhoq!\n") }
14
14
  end
15
15
 
16
- specify { expect(storage.get(identifier).read).to eq "Hello adhoq!\n" }
16
+ specify { expect(storage.get(identifier)).to eq "Hello adhoq!\n" }
17
+ end
18
+
19
+ describe Storage::S3, :fog_mock do
20
+ let(:storage) { Storage::S3.new('my-adhoq-bucket', aws_access_key_id: 'key_id', aws_secret_access_key: 'access_key') }
21
+
22
+ let(:identifier) do
23
+ storage.store('.txt') { StringIO.new("Hello adhoq!\n") }
24
+ end
25
+
26
+ specify { expect(storage.get(identifier)).to eq "Hello adhoq!\n" }
17
27
  end
18
28
  end
19
29
  end
data/spec/spec_helper.rb CHANGED
@@ -2,13 +2,18 @@ ENV['RAILS_ENV'] ||= 'test'
2
2
  require_relative 'dummy/config/environment'
3
3
 
4
4
  require 'rspec/rails'
5
- require 'factory_girl_rails'
6
5
 
6
+ require 'capybara/rspec'
7
+ require 'capybara/poltergeist'
8
+ require 'database_cleaner'
9
+ require 'factory_girl_rails'
7
10
  require 'pry-byebug'
8
11
 
9
12
  Rails.backtrace_cleaner.remove_silencers!
10
13
  Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f }
11
14
 
15
+ Capybara.default_driver = :poltergeist
16
+
12
17
  RSpec.configure do |config|
13
18
  config.expect_with :rspec do |expectations|
14
19
  expectations.include_chain_clauses_in_custom_matcher_descriptions = true
@@ -18,7 +23,7 @@ RSpec.configure do |config|
18
23
  mocks.verify_partial_doubles = true
19
24
  end
20
25
 
21
- config.use_transactional_fixtures = true
26
+ config.use_transactional_fixtures = false
22
27
  config.filter_run :focus
23
28
  config.run_all_when_everything_filtered = true
24
29
 
@@ -33,4 +38,26 @@ RSpec.configure do |config|
33
38
 
34
39
  config.include FactoryGirl::Syntax::Methods
35
40
  Kernel.srand config.seed
41
+
42
+ config.around(:each, :fog_mock) do |example|
43
+ begin
44
+ Fog.mock!
45
+ Fog::Mock.reset
46
+
47
+ example.run
48
+ ensure
49
+ Fog.unmock!
50
+ end
51
+ end
52
+
53
+ config.before(:suite) do
54
+ DatabaseCleaner.strategy = :truncation
55
+ DatabaseCleaner.clean_with :truncation
56
+ end
57
+
58
+ config.around(:each) do |example|
59
+ DatabaseCleaner.cleaning do
60
+ example.run
61
+ end
62
+ end
36
63
  end
@@ -0,0 +1,4 @@
1
+ if ENV['CODECLIMATE_REPO_TOKEN'] && RUBY_VERSION >= '2.1'
2
+ require 'codeclimate-test-reporter'
3
+ CodeClimate::TestReporter.start
4
+ end
@@ -0,0 +1,5 @@
1
+ module FeatureSpecHelper
2
+ def table_contant(table)
3
+ first(table).all('tr').map {|row| row.all('th, td').map(&:text) }
4
+ end
5
+ end
@@ -1,16 +1,22 @@
1
1
  require 'rspec/matchers'
2
2
  require 'simple_xlsx_reader'
3
3
 
4
- RSpec::Matchers.define :have_values_in_xlsx_sheet do |values|
4
+ RSpec::Matchers.define :have_values_in_xlsx_sheet do |expect_values|
5
5
  match do |data|
6
- expect(extract_values(data)).to eq values
6
+ @actual_values = extract_values(data)
7
+ expect(@actual_values).to eq expect_values
8
+ end
9
+
10
+
11
+ failure_message do
12
+ RSpec::Expectations.differ.diff_as_object(@actual_values, expect_values)
7
13
  end
8
14
 
9
15
  private
10
16
 
11
17
  def extract_values(data)
12
18
  Tempfile.open(%w[actual .xlsx], Dir.tmpdir, encoding: 'BINARY') do |f|
13
- f.write data.read
19
+ f.write data
14
20
  f.flush
15
21
 
16
22
  sheet = SimpleXlsxReader::Document.new(f.path).sheets.first
metadata CHANGED
@@ -1,31 +1,45 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: adhoq
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kyosuke MOROHASHI
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-10-08 00:00:00.000000000 Z
11
+ date: 2014-10-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '3.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '3.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: axlsx
15
29
  requirement: !ruby/object:Gem::Requirement
16
30
  requirements:
17
31
  - - "~>"
18
32
  - !ruby/object:Gem::Version
19
- version: '4.0'
33
+ version: '2.0'
20
34
  type: :runtime
21
35
  prerelease: false
22
36
  version_requirements: !ruby/object:Gem::Requirement
23
37
  requirements:
24
38
  - - "~>"
25
39
  - !ruby/object:Gem::Version
26
- version: '4.0'
40
+ version: '2.0'
27
41
  - !ruby/object:Gem::Dependency
28
- name: axlsx
42
+ name: coffee-rails
29
43
  requirement: !ruby/object:Gem::Requirement
30
44
  requirements:
31
45
  - - ">="
@@ -39,7 +53,35 @@ dependencies:
39
53
  - !ruby/object:Gem::Version
40
54
  version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
- name: coffee-rails
56
+ name: fog
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.23'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.23'
69
+ - !ruby/object:Gem::Dependency
70
+ name: font-awesome-sass
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 4.2.0
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 4.2.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: jquery-rails
43
85
  requirement: !ruby/object:Gem::Requirement
44
86
  requirements:
45
87
  - - ">="
@@ -53,7 +95,7 @@ dependencies:
53
95
  - !ruby/object:Gem::Version
54
96
  version: '0'
55
97
  - !ruby/object:Gem::Dependency
56
- name: font-awesome-sass
98
+ name: sass-rails
57
99
  requirement: !ruby/object:Gem::Requirement
58
100
  requirements:
59
101
  - - ">="
@@ -67,7 +109,7 @@ dependencies:
67
109
  - !ruby/object:Gem::Version
68
110
  version: '0'
69
111
  - !ruby/object:Gem::Dependency
70
- name: jquery-rails
112
+ name: slim-rails
71
113
  requirement: !ruby/object:Gem::Requirement
72
114
  requirements:
73
115
  - - ">="
@@ -81,13 +123,27 @@ dependencies:
81
123
  - !ruby/object:Gem::Version
82
124
  version: '0'
83
125
  - !ruby/object:Gem::Dependency
84
- name: sass-rails
126
+ name: capybara
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 2.4.3
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 2.4.3
139
+ - !ruby/object:Gem::Dependency
140
+ name: database_cleaner
85
141
  requirement: !ruby/object:Gem::Requirement
86
142
  requirements:
87
143
  - - ">="
88
144
  - !ruby/object:Gem::Version
89
145
  version: '0'
90
- type: :runtime
146
+ type: :development
91
147
  prerelease: false
92
148
  version_requirements: !ruby/object:Gem::Requirement
93
149
  requirements:
@@ -95,13 +151,13 @@ dependencies:
95
151
  - !ruby/object:Gem::Version
96
152
  version: '0'
97
153
  - !ruby/object:Gem::Dependency
98
- name: slim-rails
154
+ name: factory_girl_rails
99
155
  requirement: !ruby/object:Gem::Requirement
100
156
  requirements:
101
157
  - - ">="
102
158
  - !ruby/object:Gem::Version
103
159
  version: '0'
104
- type: :runtime
160
+ type: :development
105
161
  prerelease: false
106
162
  version_requirements: !ruby/object:Gem::Requirement
107
163
  requirements:
@@ -109,7 +165,7 @@ dependencies:
109
165
  - !ruby/object:Gem::Version
110
166
  version: '0'
111
167
  - !ruby/object:Gem::Dependency
112
- name: factory_girl_rails
168
+ name: launchy
113
169
  requirement: !ruby/object:Gem::Requirement
114
170
  requirements:
115
171
  - - ">="
@@ -122,6 +178,20 @@ dependencies:
122
178
  - - ">="
123
179
  - !ruby/object:Gem::Version
124
180
  version: '0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: poltergeist
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: 1.5.1
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: 1.5.1
125
195
  - !ruby/object:Gem::Dependency
126
196
  name: pry-byebug
127
197
  requirement: !ruby/object:Gem::Requirement
@@ -217,6 +287,7 @@ files:
217
287
  - app/assets/javascripts/adhoq/bootstrap/tab.js
218
288
  - app/assets/javascripts/adhoq/bootstrap/tooltip.js
219
289
  - app/assets/javascripts/adhoq/bootstrap/transition.js
290
+ - app/assets/javascripts/adhoq/current_tables.js.coffee
220
291
  - app/assets/javascripts/adhoq/previewer.js.coffee
221
292
  - app/assets/stylesheets/adhoq/_bootstrap-compass.scss
222
293
  - app/assets/stylesheets/adhoq/_bootstrap-mincer.scss
@@ -296,6 +367,7 @@ files:
296
367
  - app/assets/stylesheets/adhoq/bootstrap/mixins/_vendor-prefixes.scss
297
368
  - app/controllers/adhoq/application_controller.rb
298
369
  - app/controllers/adhoq/authorization_methods.rb
370
+ - app/controllers/adhoq/current_tables_controller.rb
299
371
  - app/controllers/adhoq/executions_controller.rb
300
372
  - app/controllers/adhoq/previews_controller.rb
301
373
  - app/controllers/adhoq/queries_controller.rb
@@ -306,6 +378,7 @@ files:
306
378
  - app/models/adhoq/time_based_orders.rb
307
379
  - app/views/adhoq/application/_global_nav.html.slim
308
380
  - app/views/adhoq/application/_sidebar_queries_index.html.slim
381
+ - app/views/adhoq/current_tables/index.html.slim
309
382
  - app/views/adhoq/previews/create.html.slim
310
383
  - app/views/adhoq/previews/statement_invalid.html.slim
311
384
  - app/views/adhoq/queries/_form.html.slim
@@ -320,6 +393,7 @@ files:
320
393
  - db/migrate/20141006014750_create_adhoq_executions.rb
321
394
  - db/migrate/20141007052308_create_adhoq_reports.rb
322
395
  - lib/adhoq.rb
396
+ - lib/adhoq/adhoc_execution.rb
323
397
  - lib/adhoq/configuration.rb
324
398
  - lib/adhoq/engine.rb
325
399
  - lib/adhoq/error.rb
@@ -329,16 +403,22 @@ files:
329
403
  - lib/adhoq/reporter/xlsx.rb
330
404
  - lib/adhoq/result.rb
331
405
  - lib/adhoq/storage.rb
406
+ - lib/adhoq/storage/fog_storage.rb
332
407
  - lib/adhoq/storage/local_file.rb
408
+ - lib/adhoq/storage/s3.rb
333
409
  - lib/adhoq/version.rb
334
410
  - lib/tasks/adhoq_tasks.rake
335
411
  - spec/adhoq/executor_spec.rb
412
+ - spec/adhoq/global_variable_spec.rb
413
+ - spec/adhoq/reporter/xlsx_spec.rb
336
414
  - spec/adhoq/storage_spec.rb
337
415
  - spec/factories/adhoq_queries.rb
338
416
  - spec/models/adhoq/execution_spec.rb
339
417
  - spec/models/adhoq/query_spec.rb
340
418
  - spec/models/adhoq/report_spec.rb
341
419
  - spec/spec_helper.rb
420
+ - spec/support/codeclimate_reporter.rb
421
+ - spec/support/feature_spec_helper.rb
342
422
  - spec/support/have_values_in_xlsx_sheet_matcher.rb
343
423
  homepage: https://github.com/esminc/adhoq
344
424
  licenses:
@@ -366,10 +446,14 @@ specification_version: 4
366
446
  summary: DB management console in the wild.
367
447
  test_files:
368
448
  - spec/adhoq/executor_spec.rb
449
+ - spec/adhoq/global_variable_spec.rb
450
+ - spec/adhoq/reporter/xlsx_spec.rb
369
451
  - spec/adhoq/storage_spec.rb
370
452
  - spec/factories/adhoq_queries.rb
371
453
  - spec/models/adhoq/execution_spec.rb
372
454
  - spec/models/adhoq/query_spec.rb
373
455
  - spec/models/adhoq/report_spec.rb
456
+ - spec/support/codeclimate_reporter.rb
457
+ - spec/support/feature_spec_helper.rb
374
458
  - spec/support/have_values_in_xlsx_sheet_matcher.rb
375
459
  - spec/spec_helper.rb