vigetlabs-routing-filter 0.2.4
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.
- data/.travis.yml +1 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +82 -0
- data/MIT-LICENSE +20 -0
- data/README.markdown +204 -0
- data/Rakefile +31 -0
- data/ci/Gemfile.rails-2.3.x +8 -0
- data/ci/Gemfile.rails-2.3.x.lock +40 -0
- data/ci/Gemfile.rails-3.x +8 -0
- data/ci/Gemfile.rails-3.x.lock +40 -0
- data/lib/routing-filter.rb +1 -0
- data/lib/routing_filter.rb +27 -0
- data/lib/routing_filter/adapters/rails_2.rb +69 -0
- data/lib/routing_filter/adapters/rails_3.rb +77 -0
- data/lib/routing_filter/chain.rb +22 -0
- data/lib/routing_filter/filter.rb +37 -0
- data/lib/routing_filter/filters/extension.rb +76 -0
- data/lib/routing_filter/filters/locale.rb +83 -0
- data/lib/routing_filter/filters/pagination.rb +47 -0
- data/lib/routing_filter/filters/uuid.rb +40 -0
- data/lib/routing_filter/version.rb +3 -0
- data/test/all.rb +1 -0
- data/test/filters/all_filters/generation.rb +42 -0
- data/test/filters/all_filters/recognition.rb +92 -0
- data/test/filters/all_filters_test.rb +25 -0
- data/test/filters/extension_test.rb +54 -0
- data/test/filters/locale_test.rb +69 -0
- data/test/filters/pagination_test.rb +29 -0
- data/test/filters/uuid_test.rb +40 -0
- data/test/rails_test.rb +92 -0
- data/test/routes_test.rb +31 -0
- data/test/routing_filter_test.rb +47 -0
- data/test/test_adapters/rails_2.rb +17 -0
- data/test/test_adapters/rails_3.rb +28 -0
- data/test/test_helper.rb +41 -0
- data/vigetlabs-routing-filter.gemspec +90 -0
- metadata +130 -0
@@ -0,0 +1 @@
|
|
1
|
+
require 'routing_filter'
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'action_pack'
|
2
|
+
require 'active_support/core_ext/string/inflections'
|
3
|
+
|
4
|
+
module RoutingFilter
|
5
|
+
autoload :Filter, 'routing_filter/filter'
|
6
|
+
autoload :Chain, 'routing_filter/chain'
|
7
|
+
autoload :Extension, 'routing_filter/filters/extension'
|
8
|
+
autoload :Locale, 'routing_filter/filters/locale'
|
9
|
+
autoload :Pagination, 'routing_filter/filters/pagination'
|
10
|
+
autoload :Uuid, 'routing_filter/filters/uuid'
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def build(name, options)
|
14
|
+
const_get(name.to_s.camelize).new(options)
|
15
|
+
end
|
16
|
+
|
17
|
+
def active=(active)
|
18
|
+
@@active = active
|
19
|
+
end
|
20
|
+
|
21
|
+
def active?
|
22
|
+
defined?(@@active) ? @@active : @@active = true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
require "routing_filter/adapters/rails_#{ActionPack::VERSION::MAJOR}"
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'action_controller'
|
2
|
+
|
3
|
+
# allows to install a filter to the route set by calling: map.filter 'locale'
|
4
|
+
ActionController::Routing::RouteSet::Mapper.class_eval do
|
5
|
+
def filter(*args)
|
6
|
+
@set.add_filters(*args)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
# same here for the optimized url generation in named routes
|
11
|
+
ActionController::Routing::RouteSet::NamedRouteCollection.class_eval do
|
12
|
+
# gosh. monkey engineering optimization code
|
13
|
+
def generate_optimisation_block_with_filtering(*args)
|
14
|
+
code = generate_optimisation_block_without_filtering(*args)
|
15
|
+
if match = code.match(%r(^return (.*) if (.*)))
|
16
|
+
# returned string must not contain newlines, or we'll spill out of inline code comments in
|
17
|
+
# ActionController::Routing::RouteSet::NamedRouteCollection#define_url_helper
|
18
|
+
(ActionPack::VERSION::MINOR >= 3 && ActionPack::VERSION::TINY >= 9 ? "#{match[1]}.tap { |result|" : "returning(#{match[1]}) { |result|") +
|
19
|
+
" ActionController::Routing::Routes.filters.run(:around_generate, *args, &lambda{ result }) " +
|
20
|
+
"} if #{match[2]}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
alias_method_chain :generate_optimisation_block, :filtering
|
24
|
+
end
|
25
|
+
|
26
|
+
ActionController::Routing::RouteSet.class_eval do
|
27
|
+
attr_writer :filters
|
28
|
+
|
29
|
+
def filters
|
30
|
+
@filters ||= RoutingFilter::Chain.new
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_filters(*names)
|
34
|
+
options = names.extract_options!
|
35
|
+
names.each { |name| filters.unshift(RoutingFilter.build(name, options)) }
|
36
|
+
end
|
37
|
+
|
38
|
+
def recognize_path_with_filtering(path, env = {})
|
39
|
+
path = ::URI.unescape(path.dup) # string is frozen due to memoize
|
40
|
+
filters.run(:around_recognize, path, env, &lambda{ recognize_path_without_filtering(path, env) })
|
41
|
+
end
|
42
|
+
alias_method_chain :recognize_path, :filtering
|
43
|
+
|
44
|
+
def generate_with_filtering(*args)
|
45
|
+
filters.run(:around_generate, args.first, &lambda{ generate_without_filtering(*args) })
|
46
|
+
end
|
47
|
+
alias_method_chain :generate, :filtering
|
48
|
+
|
49
|
+
def clear_with_filtering!
|
50
|
+
@filters.clear if @filters
|
51
|
+
clear_without_filtering!
|
52
|
+
end
|
53
|
+
alias_method_chain :clear!, :filtering
|
54
|
+
|
55
|
+
# add some useful information to the request environment
|
56
|
+
# right, this is from jamis buck's excellent article about routes internals
|
57
|
+
# http://weblog.jamisbuck.org/2006/10/26/monkey-patching-rails-extending-routes-2
|
58
|
+
# TODO move this ... where?
|
59
|
+
alias_method :extract_request_environment_without_host, :extract_request_environment unless method_defined? :extract_request_environment_without_host
|
60
|
+
def extract_request_environment(request)
|
61
|
+
extract_request_environment_without_host(request).tap do |env|
|
62
|
+
env.merge! :host => request.host,
|
63
|
+
:port => request.port,
|
64
|
+
:host_with_port => request.host_with_port,
|
65
|
+
:domain => request.domain,
|
66
|
+
:subdomain => request.subdomains.first
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'action_dispatch'
|
2
|
+
require 'active_support/core_ext/module/aliasing'
|
3
|
+
require 'active_support/core_ext/hash/reverse_merge'
|
4
|
+
|
5
|
+
mappers = [ActionDispatch::Routing::Mapper]
|
6
|
+
mappers << ActionDispatch::Routing::DeprecatedMapper if defined?(ActionDispatch::Routing::DeprecatedMapper)
|
7
|
+
mappers.each do |mapper|
|
8
|
+
mapper.class_eval do
|
9
|
+
def filter(*args)
|
10
|
+
@set.add_filters(*args)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
ActionDispatch::Routing::RouteSet.class_eval do
|
16
|
+
def filters
|
17
|
+
@set.filters if @set
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_filters(*names)
|
21
|
+
options = names.extract_options!
|
22
|
+
names.each { |name| filters.unshift(RoutingFilter.build(name, options)) }
|
23
|
+
end
|
24
|
+
|
25
|
+
# def recognize_path_with_filtering(path, env = {})
|
26
|
+
# @set.filters.run(:around_recognize, path.dup, env, &lambda{ recognize_path_without_filtering(path.dup, env) })
|
27
|
+
# end
|
28
|
+
# alias_method_chain :recognize_path, :filtering
|
29
|
+
|
30
|
+
def generate_with_filtering(options, recall = {}, extras = false)
|
31
|
+
filters.run(:around_generate, options, &lambda{ generate_without_filtering(options, recall, extras) })
|
32
|
+
end
|
33
|
+
alias_method_chain :generate, :filtering
|
34
|
+
|
35
|
+
def clear_with_filtering!
|
36
|
+
filters.clear if filters
|
37
|
+
clear_without_filtering!
|
38
|
+
end
|
39
|
+
alias_method_chain :clear!, :filtering
|
40
|
+
end
|
41
|
+
|
42
|
+
require 'rack/mount/route_set'
|
43
|
+
require 'rack/mount/code_generation'
|
44
|
+
|
45
|
+
Rack::Mount::RouteSet.class_eval do
|
46
|
+
def filters
|
47
|
+
@filters || RoutingFilter::Chain.new.tap { |f| @filters = f unless frozen? }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# gah. so who's hoped monkeypatching optimized code wouldn't be necessary with rails 3 anymore?
|
52
|
+
Rack::Mount::CodeGeneration.class_eval do
|
53
|
+
def optimize_recognize_with_filtering!
|
54
|
+
optimize_recognize_without_filtering!
|
55
|
+
(class << self; self; end).class_eval do
|
56
|
+
alias_method_chain :recognize, :filtering
|
57
|
+
end
|
58
|
+
end
|
59
|
+
alias :optimize_recognize_without_filtering! :optimize_recognize!
|
60
|
+
alias :optimize_recognize! :optimize_recognize_with_filtering!
|
61
|
+
|
62
|
+
# note: if you overly and unnecessarily use blocks in your lowlevel libraries you make it fricking
|
63
|
+
# hard for your users to hook in anywhere
|
64
|
+
def recognize_with_filtering(request, &block)
|
65
|
+
path, route, matches, params = request.env['PATH_INFO'], nil, nil, nil
|
66
|
+
original_path = path.dup
|
67
|
+
|
68
|
+
filters.run(:around_recognize, path, request.env) do
|
69
|
+
route, matches, params = recognize_without_filtering(request)
|
70
|
+
params || {}
|
71
|
+
end
|
72
|
+
|
73
|
+
request.env['PATH_INFO'] = original_path # hmm ...
|
74
|
+
block.call(route, matches, params) if route
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module RoutingFilter
|
2
|
+
class Chain < Array
|
3
|
+
def <<(filter)
|
4
|
+
filter.previous, last.next = last, filter if last
|
5
|
+
super
|
6
|
+
end
|
7
|
+
alias push <<
|
8
|
+
|
9
|
+
def unshift(filter)
|
10
|
+
filter.next, first.previous = first, filter if first
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
def run(method, *args, &final)
|
15
|
+
active? ? first.run(method, *args, &final) : final.call
|
16
|
+
end
|
17
|
+
|
18
|
+
def active?
|
19
|
+
RoutingFilter.active? && !empty?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module RoutingFilter
|
2
|
+
class Filter
|
3
|
+
attr_accessor :next, :previous, :options
|
4
|
+
|
5
|
+
def initialize(options = {})
|
6
|
+
@options = options
|
7
|
+
end
|
8
|
+
|
9
|
+
def run(method, *args, &block)
|
10
|
+
_next = self.next ? proc {|path, env| self.next.run(method, *args, &block) } : block
|
11
|
+
RoutingFilter.active? ? send(method, *args, &_next) : _next.call(*args)
|
12
|
+
end
|
13
|
+
|
14
|
+
def run_reverse(method, *args, &block)
|
15
|
+
_prev = previous ? lambda { previous.run_reverse(method, *args, &block) } : block
|
16
|
+
RoutingFilter.active? ? send(method, *args, &_prev) : _prev.call(*args)
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def extract_segment!(pattern, path)
|
22
|
+
path.sub!(pattern) { $2 || '' }
|
23
|
+
path.replace('/') if path.empty?
|
24
|
+
$1
|
25
|
+
end
|
26
|
+
|
27
|
+
def prepend_segment!(result, segment)
|
28
|
+
url = result.is_a?(Array) ? result.first : result
|
29
|
+
url.sub!(%r(^(http.?://[^/]*)?(.*))) { "#{$1}/#{segment}#{$2 == '/' ? '' : $2}" }
|
30
|
+
end
|
31
|
+
|
32
|
+
def append_segment!(result, segment)
|
33
|
+
url = result.is_a?(Array) ? result.first : result
|
34
|
+
url.sub!(%r(/?($|\?))) { "/#{segment}#{$1}" }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# The Extension filter chops a file extension off from the end of the
|
2
|
+
# recognized path. When a path is generated the filter re-adds the extension
|
3
|
+
# to the path accordingly.
|
4
|
+
#
|
5
|
+
# incoming url: /de/products/page/1
|
6
|
+
# filtered url: /de/products
|
7
|
+
# params: params[:locale] = 'de'
|
8
|
+
#
|
9
|
+
# You can install the filter like this:
|
10
|
+
#
|
11
|
+
# # in config/routes.rb
|
12
|
+
# Rails.application.routes.draw do
|
13
|
+
# filter :locale
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# To make your named_route helpers or url_for add the pagination segments you
|
17
|
+
# can use:
|
18
|
+
#
|
19
|
+
# products_path(:locale => 'de')
|
20
|
+
# url_for(:products, :locale => 'de'))
|
21
|
+
|
22
|
+
module RoutingFilter
|
23
|
+
class Extension < Filter
|
24
|
+
attr_reader :extension, :exclude
|
25
|
+
|
26
|
+
def initialize(*args)
|
27
|
+
super
|
28
|
+
@exclude = options[:exclude]
|
29
|
+
@extension = options[:extension] || 'html'
|
30
|
+
end
|
31
|
+
|
32
|
+
def around_recognize(path, env, &block)
|
33
|
+
extract_extension!(path) unless excluded?(path)
|
34
|
+
yield(path, env)
|
35
|
+
end
|
36
|
+
|
37
|
+
def around_generate(params, &block)
|
38
|
+
yield.tap do |result|
|
39
|
+
url = result.is_a?(Array) ? result.first : result
|
40
|
+
append_extension!(url) if append_extension?(url)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
|
46
|
+
def extract_extension!(path)
|
47
|
+
path.sub!(/\.#{extension}$/, '')
|
48
|
+
$1
|
49
|
+
end
|
50
|
+
|
51
|
+
def append_extension?(url)
|
52
|
+
!(blank?(url) || excluded?(url) || mime_extension?(url))
|
53
|
+
end
|
54
|
+
|
55
|
+
def append_extension!(url)
|
56
|
+
url.replace url.sub(/(\?|$)/, ".#{extension}\\1")
|
57
|
+
end
|
58
|
+
|
59
|
+
def blank?(url)
|
60
|
+
url.blank? || !!url.match(%r(^/(\?|$)))
|
61
|
+
end
|
62
|
+
|
63
|
+
def excluded?(url)
|
64
|
+
case exclude
|
65
|
+
when Regexp
|
66
|
+
url =~ exclude
|
67
|
+
when Proc
|
68
|
+
exclude.call(url)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def mime_extension?(url)
|
73
|
+
url =~ /\.#{Mime::EXTENSION_LOOKUP.keys.join('|')}(\?|$)/
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# The Locale filter extracts segments matching /:locale from the beginning of
|
2
|
+
# the recognized path and exposes the page parameter as params[:page]. When a
|
3
|
+
# path is generated the filter adds the segments to the path accordingly if
|
4
|
+
# the page parameter is passed to the url helper.
|
5
|
+
#
|
6
|
+
# incoming url: /de/products/page/1
|
7
|
+
# filtered url: /de/products
|
8
|
+
# params: params[:locale] = 'de'
|
9
|
+
#
|
10
|
+
# You can install the filter like this:
|
11
|
+
#
|
12
|
+
# # in config/routes.rb
|
13
|
+
# Rails.application.routes.draw do
|
14
|
+
# filter :locale
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# To make your named_route helpers or url_for add the pagination segments you
|
18
|
+
# can use:
|
19
|
+
#
|
20
|
+
# products_path(:locale => 'de')
|
21
|
+
# url_for(:products, :locale => 'de'))
|
22
|
+
|
23
|
+
require 'i18n'
|
24
|
+
|
25
|
+
module RoutingFilter
|
26
|
+
class Locale < Filter
|
27
|
+
@@include_default_locale = true
|
28
|
+
cattr_writer :include_default_locale
|
29
|
+
|
30
|
+
class << self
|
31
|
+
def include_default_locale?
|
32
|
+
@@include_default_locale
|
33
|
+
end
|
34
|
+
|
35
|
+
def locales
|
36
|
+
@@locales ||= I18n.available_locales.map(&:to_sym)
|
37
|
+
end
|
38
|
+
|
39
|
+
def locales=(locales)
|
40
|
+
@@locales = locales.map(&:to_sym)
|
41
|
+
end
|
42
|
+
|
43
|
+
def locales_pattern
|
44
|
+
@@locales_pattern ||= %r(^/(#{self.locales.map { |l| Regexp.escape(l.to_s) }.join('|')})(?=/|$))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def around_recognize(path, env, &block)
|
49
|
+
locale = extract_segment!(self.class.locales_pattern, path) # remove the locale from the beginning of the path
|
50
|
+
yield.tap do |params| # invoke the given block (calls more filters and finally routing)
|
51
|
+
params[:locale] = locale if locale # set recognized locale to the resulting params hash
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def around_generate(*args, &block)
|
56
|
+
params = args.extract_options! # this is because we might get a call like forum_topics_path(forum, topic, :locale => :en)
|
57
|
+
|
58
|
+
locale = params.delete(:locale) # extract the passed :locale option
|
59
|
+
locale = I18n.locale if locale.nil? # default to I18n.locale when locale is nil (could also be false)
|
60
|
+
locale = nil unless valid_locale?(locale) # reset to no locale when locale is not valid
|
61
|
+
|
62
|
+
args << params
|
63
|
+
|
64
|
+
yield.tap do |result|
|
65
|
+
prepend_segment!(result, locale) if prepend_locale?(locale)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
protected
|
70
|
+
|
71
|
+
def valid_locale?(locale)
|
72
|
+
locale && self.class.locales.include?(locale.to_sym)
|
73
|
+
end
|
74
|
+
|
75
|
+
def default_locale?(locale)
|
76
|
+
locale && locale.to_sym == I18n.default_locale.to_sym
|
77
|
+
end
|
78
|
+
|
79
|
+
def prepend_locale?(locale)
|
80
|
+
locale && (self.class.include_default_locale? || !default_locale?(locale))
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# The Pagination filter extracts segments matching /page/:page from the end of
|
2
|
+
# the recognized url and exposes the page parameter as params[:page]. When a
|
3
|
+
# url is generated the filter adds the segments to the url accordingly if the
|
4
|
+
# page parameter is passed to the url helper.
|
5
|
+
#
|
6
|
+
# incoming url: /products/page/1
|
7
|
+
# filtered url: /products
|
8
|
+
# params: params[:page] = 1
|
9
|
+
#
|
10
|
+
# You can install the filter like this:
|
11
|
+
#
|
12
|
+
# # in config/routes.rb
|
13
|
+
# Rails.application.routes.draw do
|
14
|
+
# filter :pagination
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# To make your named_route helpers or url_for add the pagination segments you
|
18
|
+
# can use:
|
19
|
+
#
|
20
|
+
# products_path(:page => 1)
|
21
|
+
# url_for(:products, :page => 1)
|
22
|
+
|
23
|
+
module RoutingFilter
|
24
|
+
class Pagination < Filter
|
25
|
+
PAGINATION_SEGMENT = %r(/page/([\d]+)/?$)
|
26
|
+
|
27
|
+
def around_recognize(path, env, &block)
|
28
|
+
page = extract_segment!(PAGINATION_SEGMENT, path)
|
29
|
+
yield(path, env).tap do |params|
|
30
|
+
params[:page] = page.to_i if page
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def around_generate(params, &block)
|
35
|
+
page = params.delete(:page)
|
36
|
+
yield.tap do |result|
|
37
|
+
append_segment!(result, "page/#{page}") if append_page?(page)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
def append_page?(page)
|
44
|
+
page && page.to_i != 1
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|