versioncake 4.0.1 → 4.1.1

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +33 -0
  3. data/Appraisals +9 -3
  4. data/CHANGELOG.md +42 -1
  5. data/Gemfile.lock +26 -24
  6. data/README.md +14 -11
  7. data/SECURITY.md +9 -0
  8. data/gemfiles/rails5.0.gemfile.lock +26 -24
  9. data/gemfiles/rails5.2.gemfile.lock +36 -34
  10. data/gemfiles/rails6.0.gemfile +3 -3
  11. data/gemfiles/rails6.0.gemfile.lock +44 -40
  12. data/gemfiles/rails7.0.gemfile +9 -0
  13. data/gemfiles/rails7.0.gemfile.lock +127 -0
  14. data/lib/versioncake/controller_additions.rb +0 -6
  15. data/lib/versioncake/railtie.rb +9 -0
  16. data/lib/versioncake/response_strategy/http_content_type_strategy.rb +4 -2
  17. data/lib/versioncake/strategies/extraction_strategy.rb +11 -7
  18. data/lib/versioncake/version.rb +1 -1
  19. data/lib/versioncake/version_checker.rb +2 -1
  20. data/lib/versioncake/version_context_service.rb +1 -1
  21. data/lib/versioncake/versioned_request.rb +1 -1
  22. data/lib/versioncake/view_additions.rb +8 -88
  23. data/lib/versioncake/view_additions_rails5.rb +70 -0
  24. data/lib/versioncake/view_additions_rails6.rb +69 -0
  25. data/lib/versioncake/view_additions_rails7.rb +155 -0
  26. data/lib/versioncake.rb +3 -1
  27. data/spec/integration/controller/unversioned_controller_spec.rb +1 -1
  28. data/spec/integration/view/render_spec.rb +8 -1
  29. data/spec/integration/view/view_additions_rails5_spec.rb +67 -0
  30. data/spec/integration/view/view_additions_rails6_spec.rb +44 -0
  31. data/spec/integration/view/view_additions_rails7_spec.rb +76 -0
  32. data/spec/unit/strategies/extraction_strategy_spec.rb +2 -2
  33. data/spec/unit/version_checker_spec.rb +1 -1
  34. data/spec/unit/versioned_request_spec.rb +0 -7
  35. metadata +17 -8
  36. data/.travis.yml +0 -20
  37. data/spec/integration/view/view_additions_spec.rb +0 -42
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- versioncake (4.0.1)
4
+ versioncake (4.1.1)
5
5
  actionpack (> 5.0)
6
6
  activesupport (> 5.0)
7
7
  railties (> 5.0)
@@ -10,30 +10,31 @@ PATH
10
10
  GEM
11
11
  remote: https://rubygems.org/
12
12
  specs:
13
- actionpack (5.2.4.1)
14
- actionview (= 5.2.4.1)
15
- activesupport (= 5.2.4.1)
13
+ actionpack (6.0.4.4)
14
+ actionview (= 6.0.4.4)
15
+ activesupport (= 6.0.4.4)
16
16
  rack (~> 2.0, >= 2.0.8)
17
17
  rack-test (>= 0.6.3)
18
18
  rails-dom-testing (~> 2.0)
19
- rails-html-sanitizer (~> 1.0, >= 1.0.2)
20
- actionview (5.2.4.1)
21
- activesupport (= 5.2.4.1)
19
+ rails-html-sanitizer (~> 1.0, >= 1.2.0)
20
+ actionview (6.0.4.4)
21
+ activesupport (= 6.0.4.4)
22
22
  builder (~> 3.1)
23
23
  erubi (~> 1.4)
24
24
  rails-dom-testing (~> 2.0)
25
- rails-html-sanitizer (~> 1.0, >= 1.0.3)
26
- activesupport (5.2.4.1)
25
+ rails-html-sanitizer (~> 1.1, >= 1.2.0)
26
+ activesupport (6.0.4.4)
27
27
  concurrent-ruby (~> 1.0, >= 1.0.2)
28
28
  i18n (>= 0.7, < 2)
29
29
  minitest (~> 5.1)
30
30
  tzinfo (~> 1.1)
31
- appraisal (2.2.0)
31
+ zeitwerk (~> 2.2, >= 2.2.2)
32
+ appraisal (2.4.1)
32
33
  bundler
33
34
  rake
34
35
  thor (>= 0.14.0)
35
36
  builder (3.2.4)
36
- concurrent-ruby (1.1.6)
37
+ concurrent-ruby (1.1.9)
37
38
  coveralls (0.8.23)
38
39
  json (>= 1.8, < 3)
39
40
  simplecov (~> 0.16.1)
@@ -41,42 +42,44 @@ GEM
41
42
  thor (>= 0.19.4, < 2.0)
42
43
  tins (~> 1.6)
43
44
  crass (1.0.6)
44
- diff-lcs (1.3)
45
- docile (1.3.2)
46
- erubi (1.9.0)
47
- i18n (1.8.2)
45
+ diff-lcs (1.5.0)
46
+ docile (1.4.0)
47
+ erubi (1.10.0)
48
+ i18n (1.8.11)
48
49
  concurrent-ruby (~> 1.0)
49
- json (2.3.0)
50
- loofah (2.4.0)
50
+ json (2.6.1)
51
+ loofah (2.13.0)
51
52
  crass (~> 1.0.2)
52
53
  nokogiri (>= 1.5.9)
53
- method_source (0.9.2)
54
- mini_portile2 (2.4.0)
55
- minitest (5.14.0)
56
- nokogiri (1.10.9)
57
- mini_portile2 (~> 2.4.0)
58
- rack (2.2.2)
54
+ method_source (1.0.0)
55
+ mini_portile2 (2.7.1)
56
+ minitest (5.15.0)
57
+ nokogiri (1.13.1)
58
+ mini_portile2 (~> 2.7.0)
59
+ racc (~> 1.4)
60
+ racc (1.6.0)
61
+ rack (2.2.3)
59
62
  rack-test (1.1.0)
60
63
  rack (>= 1.0, < 3)
61
64
  rails-dom-testing (2.0.3)
62
65
  activesupport (>= 4.2.0)
63
66
  nokogiri (>= 1.6)
64
- rails-html-sanitizer (1.3.0)
67
+ rails-html-sanitizer (1.4.2)
65
68
  loofah (~> 2.3)
66
- railties (5.2.4.1)
67
- actionpack (= 5.2.4.1)
68
- activesupport (= 5.2.4.1)
69
+ railties (6.0.4.4)
70
+ actionpack (= 6.0.4.4)
71
+ activesupport (= 6.0.4.4)
69
72
  method_source
70
73
  rake (>= 0.8.7)
71
- thor (>= 0.19.0, < 2.0)
72
- rake (13.0.1)
74
+ thor (>= 0.20.3, < 2.0)
75
+ rake (13.0.6)
73
76
  rspec (3.9.0)
74
77
  rspec-core (~> 3.9.0)
75
78
  rspec-expectations (~> 3.9.0)
76
79
  rspec-mocks (~> 3.9.0)
77
- rspec-core (3.9.1)
78
- rspec-support (~> 3.9.1)
79
- rspec-expectations (3.9.0)
80
+ rspec-core (3.9.3)
81
+ rspec-support (~> 3.9.3)
82
+ rspec-expectations (3.9.4)
80
83
  diff-lcs (>= 1.2.0, < 2.0)
81
84
  rspec-support (~> 3.9.0)
82
85
  rspec-mocks (3.9.1)
@@ -90,7 +93,7 @@ GEM
90
93
  rspec-expectations (~> 3.9.0)
91
94
  rspec-mocks (~> 3.9.0)
92
95
  rspec-support (~> 3.9.0)
93
- rspec-support (3.9.2)
96
+ rspec-support (3.9.4)
94
97
  simplecov (0.16.1)
95
98
  docile (~> 1.1)
96
99
  json (>= 1.8, < 3)
@@ -99,26 +102,27 @@ GEM
99
102
  sync (0.5.0)
100
103
  term-ansicolor (1.7.1)
101
104
  tins (~> 1.0)
102
- thor (1.0.1)
105
+ thor (1.2.1)
103
106
  thread_safe (0.3.6)
104
- tins (1.24.1)
107
+ tins (1.31.0)
105
108
  sync
106
- tzinfo (1.2.6)
109
+ tzinfo (1.2.9)
107
110
  thread_safe (~> 0.1)
111
+ zeitwerk (2.5.3)
108
112
 
109
113
  PLATFORMS
110
114
  ruby
111
115
 
112
116
  DEPENDENCIES
113
- actionpack (~> 5.2.0)
114
- activesupport (~> 5.2.0)
117
+ actionpack (~> 6.0.0)
118
+ activesupport (~> 6.0.0)
115
119
  appraisal (~> 2.2)
116
120
  coveralls (~> 0.8)
117
- railties (~> 5.2.0)
121
+ railties (~> 6.0.0)
118
122
  rake (> 12.0)
119
123
  rspec (~> 3.6)
120
124
  rspec-rails (~> 3.6)
121
125
  versioncake!
122
126
 
123
127
  BUNDLED WITH
124
- 1.17.2
128
+ 2.2.15
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "actionpack", "~> 7.0.0"
6
+ gem "activesupport", "~> 7.0.0"
7
+ gem "railties", "~> 7.0.0"
8
+
9
+ gemspec path: "../"
@@ -0,0 +1,127 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ versioncake (4.1.1)
5
+ actionpack (> 5.0)
6
+ activesupport (> 5.0)
7
+ railties (> 5.0)
8
+ tzinfo (> 1.2)
9
+
10
+ GEM
11
+ remote: https://rubygems.org/
12
+ specs:
13
+ actionpack (7.0.1)
14
+ actionview (= 7.0.1)
15
+ activesupport (= 7.0.1)
16
+ rack (~> 2.0, >= 2.2.0)
17
+ rack-test (>= 0.6.3)
18
+ rails-dom-testing (~> 2.0)
19
+ rails-html-sanitizer (~> 1.0, >= 1.2.0)
20
+ actionview (7.0.1)
21
+ activesupport (= 7.0.1)
22
+ builder (~> 3.1)
23
+ erubi (~> 1.4)
24
+ rails-dom-testing (~> 2.0)
25
+ rails-html-sanitizer (~> 1.1, >= 1.2.0)
26
+ activesupport (7.0.1)
27
+ concurrent-ruby (~> 1.0, >= 1.0.2)
28
+ i18n (>= 1.6, < 2)
29
+ minitest (>= 5.1)
30
+ tzinfo (~> 2.0)
31
+ appraisal (2.2.0)
32
+ bundler
33
+ rake
34
+ thor (>= 0.14.0)
35
+ builder (3.2.4)
36
+ concurrent-ruby (1.1.9)
37
+ coveralls (0.8.23)
38
+ json (>= 1.8, < 3)
39
+ simplecov (~> 0.16.1)
40
+ term-ansicolor (~> 1.3)
41
+ thor (>= 0.19.4, < 2.0)
42
+ tins (~> 1.6)
43
+ crass (1.0.6)
44
+ diff-lcs (1.4.4)
45
+ docile (1.4.0)
46
+ erubi (1.10.0)
47
+ i18n (1.8.11)
48
+ concurrent-ruby (~> 1.0)
49
+ json (2.6.1)
50
+ loofah (2.13.0)
51
+ crass (~> 1.0.2)
52
+ nokogiri (>= 1.5.9)
53
+ method_source (1.0.0)
54
+ mini_portile2 (2.7.1)
55
+ minitest (5.15.0)
56
+ nokogiri (1.13.1)
57
+ mini_portile2 (~> 2.7.0)
58
+ racc (~> 1.4)
59
+ racc (1.6.0)
60
+ rack (2.2.3)
61
+ rack-test (1.1.0)
62
+ rack (>= 1.0, < 3)
63
+ rails-dom-testing (2.0.3)
64
+ activesupport (>= 4.2.0)
65
+ nokogiri (>= 1.6)
66
+ rails-html-sanitizer (1.4.2)
67
+ loofah (~> 2.3)
68
+ railties (7.0.1)
69
+ actionpack (= 7.0.1)
70
+ activesupport (= 7.0.1)
71
+ method_source
72
+ rake (>= 12.2)
73
+ thor (~> 1.0)
74
+ zeitwerk (~> 2.5)
75
+ rake (13.0.6)
76
+ rspec (3.9.0)
77
+ rspec-core (~> 3.9.0)
78
+ rspec-expectations (~> 3.9.0)
79
+ rspec-mocks (~> 3.9.0)
80
+ rspec-core (3.9.1)
81
+ rspec-support (~> 3.9.1)
82
+ rspec-expectations (3.9.0)
83
+ diff-lcs (>= 1.2.0, < 2.0)
84
+ rspec-support (~> 3.9.0)
85
+ rspec-mocks (3.9.1)
86
+ diff-lcs (>= 1.2.0, < 2.0)
87
+ rspec-support (~> 3.9.0)
88
+ rspec-rails (3.9.1)
89
+ actionpack (>= 3.0)
90
+ activesupport (>= 3.0)
91
+ railties (>= 3.0)
92
+ rspec-core (~> 3.9.0)
93
+ rspec-expectations (~> 3.9.0)
94
+ rspec-mocks (~> 3.9.0)
95
+ rspec-support (~> 3.9.0)
96
+ rspec-support (3.9.2)
97
+ simplecov (0.16.1)
98
+ docile (~> 1.1)
99
+ json (>= 1.8, < 3)
100
+ simplecov-html (~> 0.10.0)
101
+ simplecov-html (0.10.2)
102
+ sync (0.5.0)
103
+ term-ansicolor (1.7.1)
104
+ tins (~> 1.0)
105
+ thor (1.2.1)
106
+ tins (1.24.1)
107
+ sync
108
+ tzinfo (2.0.4)
109
+ concurrent-ruby (~> 1.0)
110
+ zeitwerk (2.5.3)
111
+
112
+ PLATFORMS
113
+ ruby
114
+
115
+ DEPENDENCIES
116
+ actionpack (~> 7.0.0)
117
+ activesupport (~> 7.0.0)
118
+ appraisal (~> 2.2)
119
+ coveralls (~> 0.8)
120
+ railties (~> 7.0.0)
121
+ rake (> 12.0)
122
+ rspec (~> 3.6)
123
+ rspec-rails (~> 3.6)
124
+ versioncake!
125
+
126
+ BUNDLED WITH
127
+ 2.2.15
@@ -83,9 +83,3 @@ module VersionCake
83
83
  end
84
84
  end
85
85
  end
86
-
87
- ActionController::Base.send(:include, VersionCake::ControllerAdditions)
88
-
89
- if defined?(ActionController::API)
90
- ActionController::API.send(:include, VersionCake::ControllerAdditions)
91
- end
@@ -0,0 +1,9 @@
1
+ require 'rails/railtie'
2
+
3
+ class Railtie < ::Rails::Railtie
4
+ initializer :versioncake do
5
+ ActiveSupport.on_load :action_controller do
6
+ include VersionCake::ControllerAdditions
7
+ end
8
+ end
9
+ end
@@ -4,8 +4,10 @@ module VersionCake
4
4
  def execute(context, _status, headers, _response)
5
5
  return if headers['Content-Type'].nil?
6
6
 
7
- headers['Content-Type'] << ';' unless headers['Content-Type'].end_with? ';'
8
- headers['Content-Type'] << " #{version_key}=#{context.version.to_s}"
7
+ content_type = headers['Content-Type']
8
+ content_type << ';' unless headers['Content-Type'].end_with?(';')
9
+
10
+ headers['Content-Type'] = "#{content_type} #{version_key}=#{context.version.to_s}"
9
11
  end
10
12
  end
11
13
  end
@@ -2,6 +2,10 @@ require 'active_support/core_ext/string/inflections.rb'
2
2
 
3
3
  module VersionCake
4
4
  class ExtractionStrategy
5
+ class InvalidStrategyError < StandardError
6
+ end
7
+ class InvalidVersionError < ArgumentError
8
+ end
5
9
 
6
10
  def extract(request)
7
11
  version = execute(request)
@@ -12,7 +16,7 @@ module VersionCake
12
16
  elsif version_blank?(version)
13
17
  nil
14
18
  else
15
- raise Exception, "Invalid format for version number."
19
+ raise InvalidVersionError, "Invalid format for version number."
16
20
  end
17
21
  end
18
22
 
@@ -28,7 +32,7 @@ module VersionCake
28
32
  # If no version is found, nil should be returned. Any other results returned will raise
29
33
  # an exception.
30
34
  def execute(request)
31
- raise Exception, "ExtractionStrategy requires execute to be implemented"
35
+ raise StandardError, "ExtractionStrategy requires execute to be implemented"
32
36
  end
33
37
 
34
38
  def self.list(*strategies)
@@ -44,24 +48,24 @@ module VersionCake
44
48
  begin
45
49
  VersionCake.const_get(strategy_name).new
46
50
  rescue
47
- raise Exception, "Unknown VersionCake extraction strategy #{strategy_name}"
51
+ raise InvalidStrategyError, "Unknown VersionCake extraction strategy #{strategy_name}"
48
52
  end
49
53
  when Proc
50
54
  if strategy.arity == 1
51
55
  VersionCake::CustomStrategy.new(strategy)
52
56
  else
53
- raise Exception, "Custom proc extraction strategy requires a single parameter"
57
+ raise InvalidStrategyError, "Custom proc extraction strategy requires a single parameter"
54
58
  end
55
59
  when Object
56
60
  if !strategy.methods.include?(:execute)
57
- raise Exception, "Custom extraction strategy requires an execute method"
61
+ raise InvalidStrategyError, "Custom extraction strategy requires an execute method"
58
62
  elsif strategy.method(:execute).arity != 1
59
- raise Exception, "Custom extraction strategy requires an execute method with a single parameter"
63
+ raise InvalidStrategyError, "Custom extraction strategy requires an execute method with a single parameter"
60
64
  else
61
65
  VersionCake::CustomStrategy.new(strategy)
62
66
  end
63
67
  else
64
- raise Exception, "Invalid extration strategy"
68
+ raise InvalidStrategyError, "Invalid extraction strategy"
65
69
  end
66
70
  end
67
71
  end
@@ -1,3 +1,3 @@
1
1
  module VersionCake
2
- VERSION = '4.0.1'
2
+ VERSION = '4.1.1'
3
3
  end
@@ -2,7 +2,8 @@ module VersionCake
2
2
  class VersionChecker
3
3
  attr_reader :result
4
4
  def initialize(version, resource)
5
- @version, @resource = resource, version
5
+ @version = version
6
+ @resource = resource
6
7
  end
7
8
 
8
9
  def execute
@@ -43,7 +43,7 @@ module VersionCake
43
43
  private
44
44
 
45
45
  def check_version(resource, version)
46
- VersionCake::VersionChecker.new(resource, version).execute
46
+ VersionCake::VersionChecker.new(version, resource).execute
47
47
  end
48
48
 
49
49
  def find_resource(uri)
@@ -15,7 +15,7 @@ module VersionCake
15
15
  else
16
16
  @version = extracted_version
17
17
  end
18
- rescue Exception
18
+ rescue VersionCake::ExtractionStrategy::InvalidVersionError
19
19
  @failed = true
20
20
  end
21
21
  end
@@ -1,91 +1,11 @@
1
1
  require 'action_view'
2
2
 
3
- # register an addition detail for the lookup context to understand,
4
- # this will allow us to have the versions available upon lookup in
5
- # the resolver.
6
- ActionView::LookupContext.register_detail(:versions){ [] }
7
-
8
- ActionView::PathResolver.class_eval do
9
- if ActionPack::VERSION::MAJOR >= 6
10
- ActionView::PathResolver::EXTENSIONS.replace({
11
- locale: ".",
12
- formats: ".",
13
- versions: ".",
14
- variants: "+",
15
- handlers: "."
16
- })
17
- Kernel::silence_warnings {
18
- ActionView::PathResolver::DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{.:versions,}{+:variants,}{.:handlers,}"
19
- }
20
- elsif ActionPack::VERSION::MAJOR >= 4 && ActionPack::VERSION::MINOR >= 1 || ActionPack::VERSION::MAJOR >= 5
21
- ActionView::PathResolver::EXTENSIONS.replace({
22
- locale: ".",
23
- formats: ".",
24
- versions: ".",
25
- variants: "+",
26
- handlers: "."
27
- })
28
-
29
- def initialize(pattern = nil)
30
- @pattern = pattern || ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:versions,}{.:handlers,}"
31
- super()
32
- end
33
- end
34
-
35
- # The default extract handler expects that the handler is the last extension and
36
- # the format is the next one. Since we are replacing the DEFAULT_PATTERN, we need to
37
- # make sure that we extract the format from the correct position.
38
- #
39
- # The version may be stuck inbetween the format and the handler. This is actually pretty tricky
40
- # because the version is optional and the locale is optional-which means when there are 3 'pieces'
41
- # it may be the locale, format and handler or the format, version and handler. To check this, we will
42
- # try one additional time if there are more pieces, which should cover all the cases:
43
- #
44
- # Cases:
45
- # 1: assume version is in the extension, pieces = ['html','erb']
46
- # 2: assume version is in the extension, pieces = ['html','v1','erb']
47
- # 3: assume version is in the extension, pieces = ['en','html','erb']
48
- # 4: assume version is in the extension, pieces = ['en','html','v1','erb']
49
- #
50
- def extract_handler_and_format(path, default_formats)
51
- pieces = File.basename(path).split(".")
52
- pieces.shift
53
-
54
- extension = pieces.pop
55
- if ActionPack::VERSION::MAJOR == 4
56
- unless extension
57
- message = "The file #{path} did not specify a template handler. The default is currently ERB, " \
58
- "but will change to RAW in the future."
59
- ActiveSupport::Deprecation.warn message
60
- end
61
- end
62
- handler = ActionView::Template.handler_for_extension(extension)
63
- format = get_format_from_pieces(pieces, (ActionPack::VERSION::MAJOR == 4 ? ActionView::Template::Types : Mime))
64
-
65
- [handler, format]
66
- end
67
-
68
- # If there are still pieces and we didn't find a valid format, we may
69
- # have a version in the extension, so try one more time to pop the format.
70
- def get_format_from_pieces(pieces, format_list)
71
- format = nil
72
- pieces.reverse.each do |piece|
73
- if ActionView::PathResolver::EXTENSIONS.is_a?(Hash) &&
74
- ActionView::PathResolver::EXTENSIONS.include?(:variants)
75
- piece = piece.split(ActionView::PathResolver::EXTENSIONS[:variants], 2).first # remove variant from format
76
- end
77
-
78
- format = format_list[piece]
79
- break unless format.nil?
80
- end
81
- format
82
- end
83
- end
84
-
85
- ActionView::Template.class_eval do
86
- # the identifier method name filters out numbers,
87
- # but we want to preserve them for v1 etc.
88
- def identifier_method_name #:nodoc:
89
- inspect.gsub(/[^a-z0-9_]/, '_')
90
- end
3
+ if ActionPack::VERSION::MAJOR >= 7
4
+ require_relative 'view_additions_rails7'
5
+ elsif ActionPack::VERSION::MAJOR >= 6
6
+ require_relative 'view_additions_rails6'
7
+ elsif ActionPack::VERSION::MAJOR >= 5
8
+ require_relative 'view_additions_rails5'
9
+ else
10
+ raise StandardError.new('Unsupported Rails version')
91
11
  end
@@ -0,0 +1,70 @@
1
+ require 'action_view'
2
+
3
+ # register an addition detail for the lookup context to understand,
4
+ # this will allow us to have the versions available upon lookup in
5
+ # the resolver.
6
+ ActionView::LookupContext.register_detail(:versions) { [] }
7
+
8
+ ActionView::PathResolver.class_eval do
9
+ ActionView::PathResolver::EXTENSIONS.replace({
10
+ locale: ".",
11
+ formats: ".",
12
+ versions: ".",
13
+ variants: "+",
14
+ handlers: "."
15
+ })
16
+
17
+ def initialize(pattern = nil)
18
+ @pattern = pattern || ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:versions,}{.:handlers,}"
19
+ super()
20
+ end
21
+
22
+ # The default extract handler expects that the handler is the last extension and
23
+ # the format is the next one. Since we are replacing the DEFAULT_PATTERN, we need to
24
+ # make sure that we extract the format from the correct position.
25
+ #
26
+ # The version may be stuck inbetween the format and the handler. This is actually pretty tricky
27
+ # because the version is optional and the locale is optional-which means when there are 3 'pieces'
28
+ # it may be the locale, format and handler or the format, version and handler. To check this, we will
29
+ # try one additional time if there are more pieces, which should cover all the cases:
30
+ #
31
+ # Cases:
32
+ # 1: assume version is in the extension, pieces = ['html','erb']
33
+ # 2: assume version is in the extension, pieces = ['html','v1','erb']
34
+ # 3: assume version is in the extension, pieces = ['en','html','erb']
35
+ # 4: assume version is in the extension, pieces = ['en','html','v1','erb']
36
+ #
37
+ def extract_handler_and_format_and_variant(path, default_formats=nil)
38
+ pieces = File.basename(path).split('.'.freeze)
39
+ pieces.shift
40
+
41
+ extension = pieces.pop
42
+
43
+ handler = ActionView::Template.handler_for_extension(extension)
44
+ format, variant = get_format_and_variant_from_pieces(pieces, Mime)
45
+ format &&= ActionView::Template::Types[format]
46
+
47
+ [handler, format, variant]
48
+ end
49
+
50
+ # If there are still pieces and we didn't find a valid format, we may
51
+ # have a version in the extension, so try one more time to pop the format.
52
+ def get_format_and_variant_from_pieces(pieces, format_list)
53
+ variant, format = nil
54
+ pieces.reverse.each do |piece|
55
+ piece, variant = piece.split(ActionView::PathResolver::EXTENSIONS[:variants], 2)
56
+
57
+ format = format_list[piece]
58
+ break unless format.nil?
59
+ end
60
+ [format, variant]
61
+ end
62
+ end
63
+
64
+ ActionView::Template.class_eval do
65
+ # the identifier method name filters out numbers,
66
+ # but we want to preserve them for v1 etc.
67
+ def identifier_method_name #:nodoc:
68
+ inspect.gsub(/[^a-z0-9_]/, '_')
69
+ end
70
+ end
@@ -0,0 +1,69 @@
1
+ require 'action_view'
2
+
3
+ # register an addition detail for the lookup context to understand,
4
+ # this will allow us to have the versions available upon lookup in
5
+ # the resolver.
6
+ ActionView::LookupContext.register_detail(:versions){ [] }
7
+
8
+ ActionView::PathResolver.class_eval do
9
+ ActionView::PathResolver::EXTENSIONS.replace({
10
+ locale: ".",
11
+ formats: ".",
12
+ versions: ".",
13
+ variants: "+",
14
+ handlers: "."
15
+ })
16
+ Kernel::silence_warnings {
17
+ ActionView::PathResolver::DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{.:versions,}{+:variants,}{.:handlers,}"
18
+ }
19
+
20
+ # The default extract handler expects that the handler is the last extension and
21
+ # the format is the next one. Since we are replacing the DEFAULT_PATTERN, we need to
22
+ # make sure that we extract the format from the correct position.
23
+ #
24
+ # The version may be stuck inbetween the format and the handler. This is actually pretty tricky
25
+ # because the version is optional and the locale is optional-which means when there are 3 'pieces'
26
+ # it may be the locale, format and handler or the format, version and handler. To check this, we will
27
+ # try one additional time if there are more pieces, which should cover all the cases:
28
+ #
29
+ # Cases:
30
+ # 1: assume version is in the extension, pieces = ['html','erb']
31
+ # 2: assume version is in the extension, pieces = ['html','v1','erb']
32
+ # 3: assume version is in the extension, pieces = ['en','html','erb']
33
+ # 4: assume version is in the extension, pieces = ['en','html','v1','erb']
34
+ #
35
+ def extract_handler_and_format(path, default_formats)
36
+ pieces = File.basename(path).split(".")
37
+ pieces.shift
38
+
39
+ extension = pieces.pop
40
+ handler = ActionView::Template.handler_for_extension(extension)
41
+ format = get_format_from_pieces(pieces, Mime)
42
+
43
+ [handler, format]
44
+ end
45
+
46
+ # If there are still pieces and we didn't find a valid format, we may
47
+ # have a version in the extension, so try one more time to pop the format.
48
+ def get_format_from_pieces(pieces, format_list)
49
+ format = nil
50
+ pieces.reverse.each do |piece|
51
+ if ActionView::PathResolver::EXTENSIONS.is_a?(Hash) &&
52
+ ActionView::PathResolver::EXTENSIONS.include?(:variants)
53
+ piece = piece.split(ActionView::PathResolver::EXTENSIONS[:variants], 2).first # remove variant from format
54
+ end
55
+
56
+ format = format_list[piece]
57
+ break unless format.nil?
58
+ end
59
+ format
60
+ end
61
+ end
62
+
63
+ ActionView::Template.class_eval do
64
+ # the identifier method name filters out numbers,
65
+ # but we want to preserve them for v1 etc.
66
+ def identifier_method_name #:nodoc:
67
+ inspect.gsub(/[^a-z0-9_]/, '_')
68
+ end
69
+ end