combinaut_rehearsal 0.1.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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +59 -0
- data/lib/combinaut_rehearsal.rb +1 -0
- data/lib/rehearsal.rb +3 -0
- data/lib/rehearsal/configuration.rb +14 -0
- data/lib/rehearsal/controller_extensions.rb +12 -0
- data/lib/rehearsal/engine.rb +11 -0
- data/lib/rehearsal/middleware.rb +110 -0
- data/lib/rehearsal/railtie.rb +9 -0
- data/lib/rehearsal/version.rb +3 -0
- metadata +112 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c1a4bc4a5f3d48de6ee54ae530bab070de5fe26ba587d9fee5aa68ba1233cf66
|
4
|
+
data.tar.gz: 949c60a9477008abe1f43fcce62eafd6a81825d4dd48919c60d0310e762a7a75
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0deadea53bcd5b1842294c04ea5a3add8b48b22b3e7360941f0f1aeba523a4eb8df3f1d7a3c7a13762671e317ebdb713efcb161283eeb8e6c14da6aebe04388f
|
7
|
+
data.tar.gz: 1bfa5bfecc56881d9587bcaa93590126f287f2c23806b138a68e987d8fed5d966be0fa5fc8624aa7ee30ee7803e56fc92eaf0803fd334fbbeea7947df59ab118
|
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.md
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# Rehearsal
|
2
|
+
|
3
|
+
Rehearsal is a Rack Middleware gem that allows model changes to be previewed without persisting them to the database.
|
4
|
+
It achieves this by intercepting the original update request and spawning a second request to Rails for a preview,
|
5
|
+
wrapping both in a single database transaction that is rolled back after the preview is generated.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Simply include the gem in your bundle, or `require` it by hand.
|
10
|
+
|
11
|
+
## Setup
|
12
|
+
|
13
|
+
### trigger
|
14
|
+
|
15
|
+
The trigger defines a proc that enables the rehearsal mechanism for the request. Keep in mind the request has not yet
|
16
|
+
reached Rails itself, and won't have data from `ApplicationController`.
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
# This is the default trigger, but you can override it with any proc you want
|
20
|
+
Rehearsal::Configuration.trigger = ->(request) {
|
21
|
+
request.params['rehearsal'] == 'true'
|
22
|
+
}
|
23
|
+
```
|
24
|
+
|
25
|
+
### preview_url
|
26
|
+
|
27
|
+
The preview url is the path of the second request which will show the preview of the changes made in the first request.
|
28
|
+
The preview url may be defined in the following ways:
|
29
|
+
|
30
|
+
- as a param, i.e. `params[:preview_url]` This method overrides all other methods of setting the url
|
31
|
+
- as a proc, e.g. `Rehearsal::Configuration.preview_url = ->(controller) { controller.my_preview_url }`
|
32
|
+
- as a symbol, e.g. `Rehearsal::Configuration.preview_url = :some_method` The method will be called on the controller the update request is sent to
|
33
|
+
- as a value, e.g. `Rehearsal::Configuration.preview_url = '/my_previews'`
|
34
|
+
|
35
|
+
NOTE: If left blank, the preview url will be determined from the response of the update request. If that request returns
|
36
|
+
a redirect, the redirect chain will be followed until the response is not a redirect.
|
37
|
+
|
38
|
+
### redirect_limit
|
39
|
+
|
40
|
+
The number of allowable redirects can be configured in order to defend against an infinite redirecting bug. This is
|
41
|
+
defaulted to a sane value, but can be adjusted as necessary.
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
Rehearsal::Configuration.redirect_limit = 3
|
45
|
+
```
|
46
|
+
|
47
|
+
## Response Headers
|
48
|
+
|
49
|
+
Since the request is redirected internally to the preview url, the browser will show the original request url.
|
50
|
+
Rehearsal injects a `rehearsal.preview_url` header into the response, setting it to the url of the final
|
51
|
+
preview url after redirection. A potential use case for this data is to rewrite the
|
52
|
+
browser url history so that when the response returns, it shows the url of the preview instead of the original request.
|
53
|
+
|
54
|
+
```erb
|
55
|
+
<%= tag(:meta, property: 'preview_url', content: request['rehearsal.preview_url']) %>
|
56
|
+
<script>
|
57
|
+
window.history.replaceState(null, null, $('meta[property=preview_url]').attr('content'));
|
58
|
+
</script>
|
59
|
+
```
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'rehearsal'
|
data/lib/rehearsal.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module Rehearsal
|
4
|
+
module Configuration
|
5
|
+
mattr_accessor :preview_url
|
6
|
+
mattr_accessor :trigger
|
7
|
+
mattr_accessor :redirect_limit
|
8
|
+
|
9
|
+
self.trigger = ->(request) {
|
10
|
+
request.params['rehearsal'] == 'true'
|
11
|
+
}
|
12
|
+
self.redirect_limit = 15
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'rehearsal/controller_extensions'
|
2
|
+
|
3
|
+
module Rehearsal
|
4
|
+
class Engine < Rails::Engine
|
5
|
+
initializer 'rehearsal.load_controller_extensions' do |app|
|
6
|
+
ActiveSupport.on_load(:action_controller) do
|
7
|
+
include ControllerExtensions::InstanceMethods
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module Rehearsal
|
4
|
+
class Middleware
|
5
|
+
def initialize(app)
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
@original_env = env.dup
|
11
|
+
@request_env = env
|
12
|
+
@redirects_followed = []
|
13
|
+
request = Rack::Request.new(@request_env)
|
14
|
+
|
15
|
+
if rehearsal?(request)
|
16
|
+
rehearse(request) { process_request(request) }
|
17
|
+
else
|
18
|
+
process_request(request)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def rehearse(request)
|
25
|
+
ActiveRecord::Base.logger.info "Rehearsal beginning"
|
26
|
+
response = nil
|
27
|
+
ActiveRecord::Base.transaction do
|
28
|
+
response = objectify_response(yield)
|
29
|
+
if response.successful? || response.redirect?
|
30
|
+
preview_url = preview_url(request)
|
31
|
+
response = request_url(preview_url) if preview_url
|
32
|
+
response = follow_redirect(response) while response.redirect?
|
33
|
+
end
|
34
|
+
|
35
|
+
raise ActiveRecord::Rollback
|
36
|
+
end
|
37
|
+
ActiveRecord::Base.logger.info "Rehearsal ending"
|
38
|
+
|
39
|
+
return [response.status, response.header, response.body]
|
40
|
+
end
|
41
|
+
|
42
|
+
def rehearsal?(request)
|
43
|
+
case Configuration.trigger
|
44
|
+
when Proc
|
45
|
+
Configuration.trigger.call(request)
|
46
|
+
else
|
47
|
+
Configuration.trigger
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def preview_url(request)
|
52
|
+
return request.params['preview_url'] if request.params.key?('preview_url')
|
53
|
+
case Configuration.preview_url
|
54
|
+
when Symbol
|
55
|
+
action_controller.send(Configuration.preview_url)
|
56
|
+
when Proc
|
57
|
+
Configuration.preview_url.call(action_controller)
|
58
|
+
else
|
59
|
+
Configuration.preview_url
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def follow_redirect(response)
|
64
|
+
@redirects_followed << response.location
|
65
|
+
if @redirects_followed.count > Configuration.redirect_limit
|
66
|
+
raise TooManyRedirectsError, "Exceeded redirect limit of #{Configuration.redirect_limit}: #{redirect_history}"
|
67
|
+
elsif @redirects_followed.count(response.location) > 1
|
68
|
+
raise RedirectLoopError, "Redirect loop detected: #{redirect_history}"
|
69
|
+
end
|
70
|
+
request_url(response.location)
|
71
|
+
end
|
72
|
+
|
73
|
+
def request_url(url)
|
74
|
+
uri = URI(url)
|
75
|
+
params = Rack::Utils.parse_query(uri.query)
|
76
|
+
request = ActionDispatch::Request.new @original_env.dup.merge({
|
77
|
+
'rehearsal.preview_url' => url,
|
78
|
+
'REQUEST_METHOD' => 'GET',
|
79
|
+
'REQUEST_URI' => url,
|
80
|
+
'REQUEST_PATH' => uri.path,
|
81
|
+
'PATH_INFO' => uri.path,
|
82
|
+
'QUERY_STRING' => uri.query,
|
83
|
+
'rack.request.form_hash' => {}
|
84
|
+
})
|
85
|
+
|
86
|
+
return objectify_response(process_request(request))
|
87
|
+
end
|
88
|
+
|
89
|
+
def process_request(request)
|
90
|
+
@app.call(request.env)
|
91
|
+
end
|
92
|
+
|
93
|
+
def objectify_response(response)
|
94
|
+
Rack::Response.new(response[2], response[0], response[1])
|
95
|
+
end
|
96
|
+
|
97
|
+
def action_controller
|
98
|
+
@request_env.fetch('action_controller.instance')
|
99
|
+
end
|
100
|
+
|
101
|
+
def redirect_history
|
102
|
+
@redirects_followed.join(' -> ')
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# EXCEPTIONS
|
107
|
+
class RehearsalError < StandardError; end
|
108
|
+
class RedirectLoopError < RehearsalError; end
|
109
|
+
class TooManyRedirectsError < RehearsalError; end
|
110
|
+
end
|
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: combinaut_rehearsal
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ryan Wallace
|
8
|
+
- Nicholas Jakobsen
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2019-10-26 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rails
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '4.2'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '4.2'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: combustion
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 0.7.0
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: 0.7.0
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: rspec-rails
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '3.6'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '3.6'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: sqlite3
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
description: Rack Middleware that handles directs incoming requests to their aliased
|
71
|
+
path targets
|
72
|
+
email:
|
73
|
+
- hello@combinaut.ca
|
74
|
+
executables: []
|
75
|
+
extensions: []
|
76
|
+
extra_rdoc_files: []
|
77
|
+
files:
|
78
|
+
- MIT-LICENSE
|
79
|
+
- README.md
|
80
|
+
- lib/combinaut_rehearsal.rb
|
81
|
+
- lib/rehearsal.rb
|
82
|
+
- lib/rehearsal/configuration.rb
|
83
|
+
- lib/rehearsal/controller_extensions.rb
|
84
|
+
- lib/rehearsal/engine.rb
|
85
|
+
- lib/rehearsal/middleware.rb
|
86
|
+
- lib/rehearsal/railtie.rb
|
87
|
+
- lib/rehearsal/version.rb
|
88
|
+
homepage: http://github.com/combinaut/rehearsal
|
89
|
+
licenses: []
|
90
|
+
metadata: {}
|
91
|
+
post_install_message:
|
92
|
+
rdoc_options: []
|
93
|
+
require_paths:
|
94
|
+
- lib
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
requirements: []
|
106
|
+
rubyforge_project:
|
107
|
+
rubygems_version: 2.7.9
|
108
|
+
signing_key:
|
109
|
+
specification_version: 4
|
110
|
+
summary: Rack Middleware that handles directs incoming requests to their aliased path
|
111
|
+
targets
|
112
|
+
test_files: []
|