combinaut_director 1.2.2
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 +139 -0
- data/app/models/director/alias.rb +137 -0
- data/lib/combinaut_director.rb +1 -0
- data/lib/director.rb +6 -0
- data/lib/director/configuration.rb +18 -0
- data/lib/director/engine.rb +15 -0
- data/lib/director/handler.rb +8 -0
- data/lib/director/handler/base.rb +34 -0
- data/lib/director/handler/passthrough.rb +9 -0
- data/lib/director/handler/proxy.rb +12 -0
- data/lib/director/handler/redirect.rb +14 -0
- data/lib/director/helpers.rb +39 -0
- data/lib/director/middleware.rb +56 -0
- data/lib/director/model_extensions.rb +71 -0
- data/lib/director/railtie.rb +9 -0
- data/lib/director/version.rb +3 -0
- metadata +119 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 558b04f0ba15457cf86c1b03adfd5a33250f0b8683870b53414936d900a07539
|
4
|
+
data.tar.gz: 505c7e703bc500c2abe5ef51f88c38131a3fac34d4972084036b3fc0910a8fcb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ccb12a09877d0f43f8ef8644efa8e30dca67344f41624905b4f6f3812ac65a8aa8490ee0f823881743a540e7b3f3e85c1b688d6bb4296fdade4313231b8afd56
|
7
|
+
data.tar.gz: c4b9a652ce4a4bdba5963361f427e7550171244bfd57566f6815831aa7827d7bf9a6f7b8b231d039f444105c1ff3c8bdf7a7b40f39d7275c6c8d75ddf0170cbd
|
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,139 @@
|
|
1
|
+
# Director
|
2
|
+
|
3
|
+
Director is a Rack Middleware gem that directs incoming requests to their aliased target paths based on a customizable
|
4
|
+
alias list stored in the database. It has two basic handlers, but can be extended with your own.
|
5
|
+
|
6
|
+
## Usage
|
7
|
+
|
8
|
+
### Proxy
|
9
|
+
If you want to create a vanity path, where the contents of one page appears at a new path, use a `proxy` handler. The user agent will show the vanity path in the url bar, but the contents of the target path will appear on the page.
|
10
|
+
```ruby
|
11
|
+
Director::Alias.create(source_path: '/vanity/path', target_path: 'real/path', handler: :proxy)
|
12
|
+
```
|
13
|
+
|
14
|
+
### Redirect
|
15
|
+
If you want to redirect a deprecated or non-canonical path to the canonical path, use a `redirect` handler. The user agent will show the target path in the url bar and the contents of the target path will appear on the page.
|
16
|
+
```ruby
|
17
|
+
Director::Alias.create(source_path: '/deprecated/path', target_path: 'new/path', handler: :redirect)
|
18
|
+
```
|
19
|
+
|
20
|
+
### Custom Alias Handlers
|
21
|
+
You can create handlers for your own custom aliases. Handlers must be namespaced under `Director::Handler` and need only
|
22
|
+
implement a single `response` method. For those who have played with Rack, you will find the `response` method is very
|
23
|
+
similar to Rack's `MiddleWare#call` method in that it must return a Rack-compatible response. See https://rack.github.io/
|
24
|
+
for more information on valid return values, and the `lib/direct/handlers` folder basic for handler examples.
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
class Director::Handler::MyCustomHandler < Base
|
28
|
+
def response(app, env)
|
29
|
+
# do some amazing stuff, then...
|
30
|
+
# return a valid Rack response
|
31
|
+
end
|
32
|
+
end
|
33
|
+
```
|
34
|
+
|
35
|
+
### Source/Target Record
|
36
|
+
An alias can also be linked to a record at both the source and target end of the alias. Linked records allow for automatic updating of the corresponding path. When the record is saved, it runs a method that updates all incoming and outgoing aliases.
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
class MyModel < ActiveRecord::Base
|
40
|
+
has_aliased_paths canonical_path: :path # Accepts a symbol, proc, or object that responds to `#canonical_path`
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def path
|
45
|
+
# Return the path that routes to this record
|
46
|
+
end
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
### Url params
|
51
|
+
Url "params" or "query" from the request will be passed on and merged with target path. Any url params in the target path
|
52
|
+
will be preserved.
|
53
|
+
|
54
|
+
### Chaining
|
55
|
+
Alias lookups will chain if an alias `target_path` points to `source_path` of another. A `Director::AliasChainLoop`
|
56
|
+
exception is raised if a cycle is detected in the alias chain in order to avoid infinite lookups.
|
57
|
+
A chain ends when there is no alias with a `source_path` matching the `target_path` of the last
|
58
|
+
alias found, or if the last alias found is has a `redirect` handler. A redirect alias ends the chain in order to force the browser to update its url, thus continuing the alias chain lookup in a second request.
|
59
|
+
|
60
|
+
## Constraints
|
61
|
+
There are several constraints that can be applied to limit which requests are handled. Each constraint consists of a
|
62
|
+
whitelist and a blacklist that can be independently configured.
|
63
|
+
|
64
|
+
### Format
|
65
|
+
The format constraint looks at the request extension. This can be used to ignore asset requests or only apply aliasing
|
66
|
+
to HTML requests.
|
67
|
+
```ruby
|
68
|
+
Director::Configuration.constraints.format.only = [:html, :xml]
|
69
|
+
# or
|
70
|
+
Director::Configuration.constraints.format.except = :jpg
|
71
|
+
```
|
72
|
+
|
73
|
+
### Source Path
|
74
|
+
The source constraint limits what can be entered as a source path in an `Alias`. This can be useful if you want to
|
75
|
+
prevent aliasing of certain routes, like an admin namespace for example. `only` and `except` are passed to `validates_format_of`
|
76
|
+
validations on the Alias model, and accept any patterns the `:with` and `:without` options of that validator.
|
77
|
+
```ruby
|
78
|
+
Director::Configuration.constraints.source_path.only = %r{\A/pages/}
|
79
|
+
# or
|
80
|
+
Director::Configuration.constraints.source_path.except = %r{\A/admin/}
|
81
|
+
```
|
82
|
+
NOTE: This constraint will also limit what requests perform an alias lookup. If a constraint is added, it will effectively
|
83
|
+
disable existing aliases that do not match the new constraint.
|
84
|
+
|
85
|
+
### Target Path
|
86
|
+
The target constraint limits what can be entered as a target path in an `Alias`. This can be useful if you want to
|
87
|
+
prevent aliasing of certain routes, like an admin namespace for example. `only` and `except` are passed to `validates_format_of`
|
88
|
+
validations on the Alias model, and accept any patterns the `:with` and `:without` options of that validator.
|
89
|
+
```ruby
|
90
|
+
Director::Configuration.constraints.target_path.only = %r{\A/pages/}
|
91
|
+
# or
|
92
|
+
Director::Configuration.constraints.target_path.except = %r{\A/admin/}
|
93
|
+
```
|
94
|
+
|
95
|
+
### Request
|
96
|
+
The request constraint yields the request itself to a given proc. This can be used to ignore asset requests or only
|
97
|
+
apply aliasing based on any aspect of the request, for example, params, host name, etc.<sup id="footnote_ref_1">[1](#footnote_1)</sup>
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
Director::Configuration.constraints.request.only = ->(request) { request.params['my_param'] == 'false' }
|
101
|
+
# or
|
102
|
+
Director::Configuration.constraints.request.except = ->(request) { request.env['HTTP_HOST'] == 'testing.test' }
|
103
|
+
```
|
104
|
+
|
105
|
+
### Lookup Scope
|
106
|
+
The lookup scope constraint is applied using the `ActiveRecord::Base.merge` method to inject the scope into alias the
|
107
|
+
lookup query. The constraint should be a callable object, and is passed a `Rack::Request` object for the current request.
|
108
|
+
This can be used to scope alias lookups based on the request subdomain, or other request criteria.
|
109
|
+
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
# Assuming you have added a `domain` column to the aliases table...
|
113
|
+
Director::Configuration.constraints.lookup_scope = lambda do |request|
|
114
|
+
Director::Alias.where(domain: Domain.id_from_host(request.host))
|
115
|
+
end
|
116
|
+
|
117
|
+
# or returning a callable object for merging
|
118
|
+
Director::Configuration.constraints.lookup_scope = proc do
|
119
|
+
-> { where(client: Client.current) }
|
120
|
+
end
|
121
|
+
```
|
122
|
+
|
123
|
+
## Request Information
|
124
|
+
Sometimes it may be useful to know what the request url was before Director modified it, or know if Director handled the
|
125
|
+
request or ignored it due to constraints.
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
Director::Middleware.original_request_url(request) # => Original request url before Director handled it
|
129
|
+
Director::Middleware.handled_request?(request) # => Boolean indicating whether or not Director handled the request
|
130
|
+
```
|
131
|
+
|
132
|
+
## Upgrading from an older version
|
133
|
+
|
134
|
+
### 1.1.x to 1.2.0
|
135
|
+
|
136
|
+
Alias resolution is now case insensitive. All incoming paths are downcased before saving or resolving. If you have existing aliases, you should call `#save` on each one to trigger the path sanitizer which will downcase the saved paths so the the downcased paths coming in to the `#resolve` method.
|
137
|
+
|
138
|
+
## Footnotes
|
139
|
+
<span id="footnote_1">1.</span> By default, Director only handles GET and HEAD requests because the `redirect` handler cannot tell the browser to redirect a POST. The `proxy` handler could internally redirect a POST to an aliased target path, but this dichotomy adds too much complexity for it to be enabled out of the box. The constraints configuration gives you full control over which requests are handled by Director in your application. [↩](#footnote_ref_1)
|
@@ -0,0 +1,137 @@
|
|
1
|
+
module Director
|
2
|
+
class Alias < ActiveRecord::Base
|
3
|
+
belongs_to :source, polymorphic: true
|
4
|
+
belongs_to :target, polymorphic: true
|
5
|
+
|
6
|
+
before_validation :sanitize_path
|
7
|
+
validates_presence_of :source_path, unless: :source
|
8
|
+
validates_presence_of :target_path, unless: :target
|
9
|
+
validates_format_of :source_path, with: Configuration.constraints.source_path.only
|
10
|
+
validates_format_of :target_path, with: Configuration.constraints.target_path.only
|
11
|
+
validates_format_of :source_path, without: Configuration.constraints.source_path.except
|
12
|
+
validates_format_of :target_path, without: Configuration.constraints.target_path.except
|
13
|
+
validates_format_of :source_path, with: %r{\A/}, if: :source_path_relative?, message: 'is a relative path so it must start with a slash'
|
14
|
+
validates_format_of :target_path, with: %r{\A/}, if: :target_path_relative?, message: 'is a relative path so it must start with a slash'
|
15
|
+
validate :valid_paths
|
16
|
+
validate :valid_handler
|
17
|
+
|
18
|
+
scope :with_source_path, -> { where.not(source_path: nil) }
|
19
|
+
scope :with_target_path, -> { where.not(target_path: nil) }
|
20
|
+
|
21
|
+
before_save :set_source_path, if: :source_changed?
|
22
|
+
before_save :set_target_path, if: :target_changed?
|
23
|
+
|
24
|
+
def self.resolve_with_constraint(source_path, request)
|
25
|
+
merge(Configuration.constraints.lookup_scope.call(request)).resolve(source_path)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns the alias matching the source_path, traversing any chained aliases and returning the last one
|
29
|
+
def self.resolve(source_path)
|
30
|
+
source_path = sanitize_path(source_path)
|
31
|
+
found = {}
|
32
|
+
|
33
|
+
# Traverse a chain of aliases
|
34
|
+
while alias_entry = find_by_source_path(source_path) do
|
35
|
+
raise AliasChainLoop, [*found.keys, source_path].join(' -> ') if found.key?(alias_entry.target_path)
|
36
|
+
break if alias_entry.passthrough? # Stop if we reach a passthrough since the app will handle this
|
37
|
+
found[source_path] = alias_entry
|
38
|
+
break if alias_entry.redirect? # Stop if we reach a redirect since the browser will need to change url at that point
|
39
|
+
source_path = alias_entry.target_path
|
40
|
+
end
|
41
|
+
|
42
|
+
return found.values.last
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.valid_uri?(url)
|
46
|
+
!!URI(url)
|
47
|
+
rescue URI::InvalidURIError
|
48
|
+
false
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.relative?(url)
|
52
|
+
URI(url).relative?
|
53
|
+
rescue URI::InvalidURIError
|
54
|
+
false
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.sanitize_path(path = nil, &block)
|
58
|
+
path = block.call if block_given?
|
59
|
+
path = path.to_s
|
60
|
+
path = path.strip
|
61
|
+
path = path.downcase
|
62
|
+
path = path.remove(%r{/$}) if path.length > 1
|
63
|
+
return path
|
64
|
+
end
|
65
|
+
|
66
|
+
def redirect?
|
67
|
+
handler_class <= Handler::Redirect
|
68
|
+
end
|
69
|
+
|
70
|
+
def passthrough?
|
71
|
+
handler_class <= Handler::Passthrough
|
72
|
+
end
|
73
|
+
|
74
|
+
def handler_class
|
75
|
+
handler_name = "Director::Handler::#{handler.classify}"
|
76
|
+
handler_name.constantize
|
77
|
+
rescue NameError
|
78
|
+
raise MissingAliasHandler, "Handler not found '#{handler_name}'"
|
79
|
+
end
|
80
|
+
|
81
|
+
def blank?
|
82
|
+
!(source_path? || target_path? || source || target)
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def valid_paths
|
88
|
+
source_path = source_changed? && source ? source.generate_canonical_path : self.source_path
|
89
|
+
target_path = target_changed? && target ? target.generate_canonical_path : self.target_path
|
90
|
+
|
91
|
+
errors.add(:source_path, 'is not a valid') unless self.class.valid_uri?(source_path)
|
92
|
+
errors.add(:target_path, 'is not a valid') unless self.class.valid_uri?(target_path)
|
93
|
+
errors.add(:target_path, 'cannot be the same as the source path') if source_path == target_path
|
94
|
+
end
|
95
|
+
|
96
|
+
def sanitize_path
|
97
|
+
self.source_path = self.class.sanitize_path(source_path)
|
98
|
+
self.target_path = self.class.sanitize_path(target_path)
|
99
|
+
end
|
100
|
+
|
101
|
+
def set_source_path
|
102
|
+
self.source_path = source.generate_canonical_path if source
|
103
|
+
end
|
104
|
+
|
105
|
+
def set_target_path
|
106
|
+
self.target_path = target.generate_canonical_path if target
|
107
|
+
end
|
108
|
+
|
109
|
+
def source_changed?
|
110
|
+
source_path_changed? || source_id_changed? || source_type_changed?
|
111
|
+
end
|
112
|
+
|
113
|
+
def target_changed?
|
114
|
+
target_path_changed? || target_id_changed? || target_type_changed?
|
115
|
+
end
|
116
|
+
|
117
|
+
def source_path_relative?
|
118
|
+
self.class.relative?(source_path) if source_path?
|
119
|
+
end
|
120
|
+
|
121
|
+
def target_path_relative?
|
122
|
+
self.class.relative?(target_path) if target_path?
|
123
|
+
end
|
124
|
+
|
125
|
+
def valid_handler
|
126
|
+
handler_class
|
127
|
+
rescue MissingAliasHandler
|
128
|
+
errors.add(:handler, 'not defined')
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# EXCEPTIONS
|
133
|
+
|
134
|
+
class DirectorException < StandardError; end
|
135
|
+
class MissingAliasHandler < DirectorException; end
|
136
|
+
class AliasChainLoop < DirectorException; end
|
137
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require "director"
|
data/lib/director.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module Director
|
4
|
+
module Configuration
|
5
|
+
mattr_reader :constraints
|
6
|
+
|
7
|
+
Constraint = Struct.new(:only, :except)
|
8
|
+
|
9
|
+
# Defaults
|
10
|
+
@@constraints = OpenStruct.new({
|
11
|
+
source_path: Constraint.new,
|
12
|
+
target_path: Constraint.new,
|
13
|
+
request: Constraint.new(->(request) { request.get? || request.head? }),
|
14
|
+
format: Constraint.new,
|
15
|
+
lookup_scope: ->(request) { {} }
|
16
|
+
})
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'director/handler/base'
|
2
|
+
require 'director/handler/passthrough'
|
3
|
+
require 'director/handler/proxy'
|
4
|
+
require 'director/handler/redirect'
|
5
|
+
require 'director/model_extensions'
|
6
|
+
|
7
|
+
module Director
|
8
|
+
class Engine < Rails::Engine
|
9
|
+
initializer 'director.load_model_extensions' do |app|
|
10
|
+
ActiveSupport.on_load(:active_record) do
|
11
|
+
ActiveRecord::Base.extend Director::ModelExtensions::ActMethod
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# TEST: individual handlers work when a target_path is present
|
2
|
+
# TEST: individual handlers work when only a target is present
|
3
|
+
# TEST: individual handlers can redirect to "" without looping indefinitely
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
module Director
|
7
|
+
module Handler
|
8
|
+
class Base
|
9
|
+
attr_reader :alias_entry
|
10
|
+
|
11
|
+
def initialize(alias_entry)
|
12
|
+
@alias_entry = alias_entry
|
13
|
+
end
|
14
|
+
|
15
|
+
def response(app, env)
|
16
|
+
raise NotImplementedError
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def request_uri(env)
|
22
|
+
URI(Rack::Request.new(env).url).freeze
|
23
|
+
end
|
24
|
+
|
25
|
+
def target_uri
|
26
|
+
URI(alias_entry.target_path).freeze
|
27
|
+
end
|
28
|
+
|
29
|
+
def merge_query(query1, query2)
|
30
|
+
[query1, query2].select(&:present?).join('&').presence
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Director
|
2
|
+
module Handler
|
3
|
+
class Redirect < Base
|
4
|
+
def response(app, env)
|
5
|
+
res = Rack::Response.new
|
6
|
+
redirect_uri = target_uri.dup
|
7
|
+
redirect_uri.query = merge_query(target_uri.query, request_uri(env).query)
|
8
|
+
|
9
|
+
res.redirect(redirect_uri.to_s || '/')
|
10
|
+
res.finish
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Director
|
2
|
+
module Helpers
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def matches_constraint?(constraint, value, coerce: :itself)
|
6
|
+
value = value.send(coerce)
|
7
|
+
only = Array(constraint.only).map(&coerce)
|
8
|
+
except = Array(constraint.except).map(&coerce)
|
9
|
+
|
10
|
+
return false if only.present? && only.none? {|matcher| matches?(matcher, value) }
|
11
|
+
return false if except.present? && except.any? {|matcher| matches?(matcher, value) }
|
12
|
+
return true
|
13
|
+
end
|
14
|
+
|
15
|
+
def matches?(matcher, value)
|
16
|
+
case matcher
|
17
|
+
when Regexp
|
18
|
+
matches_regexp?(matcher, value)
|
19
|
+
when Proc
|
20
|
+
matches_proc?(matcher, value)
|
21
|
+
else
|
22
|
+
matcher == value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def matches_regexp?(matcher, value)
|
27
|
+
case value
|
28
|
+
when matcher
|
29
|
+
true
|
30
|
+
else
|
31
|
+
false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def matches_proc?(matcher, value)
|
36
|
+
matcher.call(value)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Director
|
2
|
+
class Middleware
|
3
|
+
def self.handled_request?(request)
|
4
|
+
request.env['director.original_url'].present?
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.original_request_url(request)
|
8
|
+
request.env['director.original_url'] || request.url
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(app)
|
12
|
+
@app = app
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(env)
|
16
|
+
@request = Rack::Request.new(env)
|
17
|
+
|
18
|
+
unless ignored?
|
19
|
+
env['director.original_url'] = @request.url
|
20
|
+
alias_entry = Director::Alias.resolve_with_constraint(request_path, @request)
|
21
|
+
end
|
22
|
+
|
23
|
+
return handle_alias(alias_entry, env)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def handle_alias(alias_entry, env)
|
29
|
+
return Handler.for(alias_entry).new(alias_entry).response(@app, env)
|
30
|
+
end
|
31
|
+
|
32
|
+
def ignored?
|
33
|
+
ignored_format?(format) || ignored_path?(request_path) || ignored_request?(@request)
|
34
|
+
end
|
35
|
+
|
36
|
+
def ignored_format?(format)
|
37
|
+
!Helpers.matches_constraint?(Configuration.constraints.format, format, coerce: :to_s)
|
38
|
+
end
|
39
|
+
|
40
|
+
def ignored_path?(path)
|
41
|
+
!Helpers.matches_constraint?(Configuration.constraints.source_path, path)
|
42
|
+
end
|
43
|
+
|
44
|
+
def ignored_request?(request)
|
45
|
+
!Helpers.matches_constraint?(Configuration.constraints.request, request)
|
46
|
+
end
|
47
|
+
|
48
|
+
def format
|
49
|
+
request_path[/\.([^.]+)$/, 1] || 'html'
|
50
|
+
end
|
51
|
+
|
52
|
+
def request_path
|
53
|
+
@request.path_info
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Director
|
2
|
+
module ModelExtensions
|
3
|
+
module ActMethod
|
4
|
+
def has_aliased_paths(canonical_path: )
|
5
|
+
extend ClassMethods
|
6
|
+
include Associations
|
7
|
+
include Callbacks
|
8
|
+
include InstanceMethods
|
9
|
+
|
10
|
+
self.aliased_paths_options = { canonical_path: canonical_path }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
end
|
16
|
+
|
17
|
+
module InstanceMethods
|
18
|
+
end
|
19
|
+
|
20
|
+
module Associations
|
21
|
+
BLANK_ALIAS = ->(attributes) { Director::Alias.new(attributes).blank? }
|
22
|
+
|
23
|
+
def self.included(base)
|
24
|
+
base.has_many :incoming_aliases, class_name: 'Director::Alias', as: :target, dependent: :delete_all, inverse_of: :target
|
25
|
+
base.has_one :outgoing_alias, class_name: 'Director::Alias', as: :source, dependent: :delete, inverse_of: :source
|
26
|
+
|
27
|
+
base.class_attribute :aliased_paths_options
|
28
|
+
base.accepts_nested_attributes_for :incoming_aliases, reject_if: BLANK_ALIAS, allow_destroy: true
|
29
|
+
base.accepts_nested_attributes_for :outgoing_alias, reject_if: BLANK_ALIAS, allow_destroy: true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module Callbacks
|
34
|
+
def self.included(base)
|
35
|
+
base.after_save :update_aliased_paths
|
36
|
+
end
|
37
|
+
|
38
|
+
def generate_canonical_path
|
39
|
+
generator = aliased_paths_options[:canonical_path]
|
40
|
+
Alias.sanitize_path do
|
41
|
+
case generator
|
42
|
+
when Symbol
|
43
|
+
send(generator)
|
44
|
+
when Proc
|
45
|
+
generator.call(self)
|
46
|
+
when String
|
47
|
+
generator
|
48
|
+
else # Assume it's an object that responds to canonical_path
|
49
|
+
generator.send(:canonical_path)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def update_aliased_paths
|
57
|
+
path = generate_canonical_path
|
58
|
+
update_incoming_alias_paths(path)
|
59
|
+
update_outgoing_alias_paths(path)
|
60
|
+
end
|
61
|
+
|
62
|
+
def update_incoming_alias_paths(path)
|
63
|
+
Alias.where(target: self).update_all(target_path: path)
|
64
|
+
end
|
65
|
+
|
66
|
+
def update_outgoing_alias_paths(path)
|
67
|
+
Alias.where(source: self).update_all(source_path: path)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
metadata
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: combinaut_director
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.2.2
|
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.com
|
74
|
+
executables: []
|
75
|
+
extensions: []
|
76
|
+
extra_rdoc_files: []
|
77
|
+
files:
|
78
|
+
- MIT-LICENSE
|
79
|
+
- README.md
|
80
|
+
- app/models/director/alias.rb
|
81
|
+
- lib/combinaut_director.rb
|
82
|
+
- lib/director.rb
|
83
|
+
- lib/director/configuration.rb
|
84
|
+
- lib/director/engine.rb
|
85
|
+
- lib/director/handler.rb
|
86
|
+
- lib/director/handler/base.rb
|
87
|
+
- lib/director/handler/passthrough.rb
|
88
|
+
- lib/director/handler/proxy.rb
|
89
|
+
- lib/director/handler/redirect.rb
|
90
|
+
- lib/director/helpers.rb
|
91
|
+
- lib/director/middleware.rb
|
92
|
+
- lib/director/model_extensions.rb
|
93
|
+
- lib/director/railtie.rb
|
94
|
+
- lib/director/version.rb
|
95
|
+
homepage: http://github.com/combinaut/director
|
96
|
+
licenses: []
|
97
|
+
metadata: {}
|
98
|
+
post_install_message:
|
99
|
+
rdoc_options: []
|
100
|
+
require_paths:
|
101
|
+
- lib
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
requirements: []
|
113
|
+
rubyforge_project:
|
114
|
+
rubygems_version: 2.7.9
|
115
|
+
signing_key:
|
116
|
+
specification_version: 4
|
117
|
+
summary: Rack Middleware that handles directs incoming requests to their aliased path
|
118
|
+
targets
|
119
|
+
test_files: []
|