versioncake 4.0.2 → 4.1.0

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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +33 -0
  3. data/Appraisals +9 -3
  4. data/CHANGELOG.md +16 -1
  5. data/Gemfile.lock +25 -23
  6. data/README.md +13 -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/response_strategy/http_content_type_strategy.rb +4 -2
  15. data/lib/versioncake/strategies/extraction_strategy.rb +11 -7
  16. data/lib/versioncake/version.rb +1 -1
  17. data/lib/versioncake/version_checker.rb +2 -1
  18. data/lib/versioncake/version_context_service.rb +1 -1
  19. data/lib/versioncake/versioned_request.rb +1 -1
  20. data/lib/versioncake/view_additions.rb +8 -88
  21. data/lib/versioncake/view_additions_rails5.rb +70 -0
  22. data/lib/versioncake/view_additions_rails6.rb +69 -0
  23. data/lib/versioncake/view_additions_rails7.rb +155 -0
  24. data/spec/integration/controller/unversioned_controller_spec.rb +1 -1
  25. data/spec/integration/view/render_spec.rb +8 -1
  26. data/spec/integration/view/view_additions_rails5_spec.rb +67 -0
  27. data/spec/integration/view/view_additions_rails6_spec.rb +44 -0
  28. data/spec/integration/view/view_additions_rails7_spec.rb +76 -0
  29. data/spec/unit/strategies/extraction_strategy_spec.rb +2 -2
  30. data/spec/unit/version_checker_spec.rb +1 -1
  31. data/spec/unit/versioned_request_spec.rb +0 -7
  32. metadata +16 -8
  33. data/.travis.yml +0 -20
  34. data/spec/integration/view/view_additions_spec.rb +0 -42
@@ -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.0)
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
@@ -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.2'
2
+ VERSION = '4.1.0'
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
@@ -0,0 +1,155 @@
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::Resolver::PathParser.class_eval do
9
+ def build_path_regex
10
+ handlers = ActionView::Template::Handlers.extensions.map { |x| Regexp.escape(x) }.join("|")
11
+ formats = ActionView::Template::Types.symbols.map { |x| Regexp.escape(x) }.join("|")
12
+ locales = "[a-z]{2}(?:-[A-Z]{2})?"
13
+ variants = "[^.]*"
14
+
15
+ %r{
16
+ \A
17
+ (?:(?<prefix>.*)/)?
18
+ (?<partial>_)?
19
+ (?<action>.*?)
20
+ (?:\.(?<locale>#{locales}))??
21
+ (?:\.(?<format>#{formats}))??
22
+ (?:\+(?<variant>#{variants}))??
23
+ (?:\.(?<versions>v[0-9]+))??
24
+ (?:\.(?<handler>#{handlers}))?
25
+ \z
26
+ }x
27
+ end
28
+
29
+ def parse(path)
30
+ @regex ||= build_path_regex
31
+ match = @regex.match(path)
32
+ path = ActionView::TemplatePath.build(match[:action], match[:prefix] || "", !!match[:partial])
33
+ details = ActionView::TemplateDetails.new(
34
+ match[:locale]&.to_sym,
35
+ match[:handler]&.to_sym,
36
+ match[:format]&.to_sym,
37
+ match[:variant]&.to_sym,
38
+ match[:versions]&.to_sym,
39
+ )
40
+
41
+ ActionView::Resolver::PathParser::ParsedPath.new(path, details)
42
+ end
43
+ end
44
+
45
+ ActionView::TemplateDetails::Requested.class_eval do
46
+ attr_reader :locale, :handlers, :formats, :variants, :versions
47
+ attr_reader :locale_idx, :handlers_idx, :formats_idx, :variants_idx, :versions_idx
48
+
49
+ def initialize(locale:, handlers:, formats:, variants:, versions:)
50
+ @locale = locale
51
+ @handlers = handlers
52
+ @formats = formats
53
+ @variants = variants
54
+ @versions = versions
55
+
56
+ @locale_idx = build_idx_hash(locale)
57
+ @handlers_idx = build_idx_hash(handlers)
58
+ @formats_idx = build_idx_hash(formats)
59
+ @versions_idx = build_idx_hash(versions)
60
+ if variants == :any
61
+ @variants_idx = ANY_HASH
62
+ else
63
+ @variants_idx = build_idx_hash(variants)
64
+ end
65
+ end
66
+ end
67
+
68
+ ActionView::TemplateDetails.class_eval do
69
+ attr_reader :locale, :handler, :format, :variant, :version
70
+
71
+ def initialize(locale, handler, format, variant, version)
72
+ @locale = locale
73
+ @handler = handler
74
+ @format = format
75
+ @variant = variant
76
+ @version = version
77
+ end
78
+
79
+ def matches?(requested)
80
+ requested.formats_idx[@format] &&
81
+ requested.locale_idx[@locale] &&
82
+ requested.versions_idx[@version] &&
83
+ requested.variants_idx[@variant] &&
84
+ requested.handlers_idx[@handler]
85
+ end
86
+
87
+ def sort_key_for(requested)
88
+ [
89
+ requested.formats_idx[@format],
90
+ requested.locale_idx[@locale],
91
+ requested.versions_idx[@version],
92
+ requested.variants_idx[@variant],
93
+ requested.handlers_idx[@handler]
94
+ ]
95
+ end
96
+ end
97
+
98
+ ActionView::Template.class_eval do
99
+ def initialize(source, identifier, handler, locals:, format: nil, variant: nil, virtual_path: nil, version: nil)
100
+ @source = source
101
+ @identifier = identifier
102
+ @handler = handler
103
+ @compiled = false
104
+ @locals = locals
105
+ @virtual_path = virtual_path
106
+
107
+ @variable = if @virtual_path
108
+ base = @virtual_path.end_with?("/") ? "" : ::File.basename(@virtual_path)
109
+ base =~ /\A_?(.*?)(?:\.\w+)*\z/
110
+ $1.to_sym
111
+ end
112
+
113
+ @format = format
114
+ @version = version
115
+ @variant = variant
116
+ @compile_mutex = Mutex.new
117
+ end
118
+
119
+ def marshal_dump # :nodoc:
120
+ [ @source, @identifier, @handler, @compiled, @locals, @virtual_path, @format, @variant, @version ]
121
+ end
122
+
123
+ def marshal_load(array) # :nodoc:
124
+ @source, @identifier, @handler, @compiled, @locals, @virtual_path, @format, @variant, @version = *array
125
+ @compile_mutex = Mutex.new
126
+ end
127
+
128
+ # the identifier method name filters out numbers,
129
+ # but we want to preserve them for v1 etc.
130
+ # TODO: Consider updating to match current implementation:
131
+ # short_identifier.tr("^a-z_", "_")
132
+ def identifier_method_name #:nodoc:
133
+ short_identifier.gsub(/[^a-z0-9_]/, '_')
134
+ end
135
+ end
136
+
137
+ ActionView::UnboundTemplate.class_eval do
138
+ delegate :version, to: :@details
139
+
140
+ def build_template(locals)
141
+ ActionView::Template.new(
142
+ @source,
143
+ @identifier,
144
+ details.handler_class,
145
+
146
+ format: details.format_or_default,
147
+ variant: variant&.to_s,
148
+ virtual_path: @virtual_path,
149
+ version: version&.to_s,
150
+
151
+ locals: locals.map(&:to_s)
152
+ )
153
+ end
154
+ end
155
+
@@ -6,7 +6,7 @@ describe UnversionedController, type: :controller do
6
6
  context '#index' do
7
7
  render_views
8
8
 
9
- it { expect(response).to be_success }
9
+ it { expect(response).to be_successful }
10
10
  it { expect(response.body).to eq 'unversioned index' }
11
11
  end
12
12
  end
@@ -3,7 +3,14 @@ require './spec/rails_helper'
3
3
  describe ActionView::Base do
4
4
  let(:path) { ActionView::FileSystemResolver.new('./spec/fixtures') }
5
5
  let(:view_paths) { ActionView::PathSet.new([path]) }
6
- let(:view) { ActionView::Base.new(view_paths) }
6
+ let(:view) do
7
+ clazz = if ActionPack::VERSION::MAJOR >= 7
8
+ ActionView::Base.with_empty_template_cache
9
+ else
10
+ ActionView::Base
11
+ end
12
+ clazz.new(ActionView::LookupContext.new(view_paths), [], nil)
13
+ end
7
14
  let(:version_override) { nil }
8
15
  subject { view.render(template: 'templates/versioned', versions: version_override) }
9
16