versioncake 4.0.2 → 4.1.0

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