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