rabl 0.17.0 → 0.19.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.
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest/sha1'
4
+
5
+ module Rabl
6
+ # Marks every cache key rabl reads or writes. Bump it when a release changes rendered output so
7
+ # that entries cached by earlier versions would be wrong: the keys change once, so no entry
8
+ # cached by an earlier version, which lacks that release's output fixes, is served.
9
+ # Keys before 0.18.0 had no marker ("rabl/", and "rabl_build/" in old releases); 2 is 0.18.0.
10
+ # The README's caching example shows the resulting prefix: update it when this changes.
11
+ CACHE_KEY_VERSION = 2
12
+
13
+ # Builds the parts of template cache keys. These are module functions rather than
14
+ # Engine methods so that they never shadow helpers a template calls.
15
+ module TemplateCacheKey
16
+ module_function
17
+
18
+ # Returns the key rabl reads and writes in the cache engine for the given parts. Earlier
19
+ # versions put their keys under "rabl/", so no key built here can be one of theirs.
20
+ # TemplateCacheKey.expand([user, "json"]) => "rabl-v2/users/1/json"
21
+ def expand(parts)
22
+ ActiveSupport::Cache.expand_cache_key(parts, "rabl-v#{CACHE_KEY_VERSION}")
23
+ end
24
+
25
+ # Returns a digest of the given parts
26
+ # TemplateCacheKey.digest(["app/views/show.rabl", "object @user"]) => "4a1b..."
27
+ def digest(parts)
28
+ digest = Digest::SHA1.new
29
+ Array(parts).flatten.each { |part| digest << part.to_s << "\0" }
30
+ digest.hexdigest
31
+ end
32
+
33
+ # Returns a path that stays the same when the application moves: relative to the
34
+ # working directory when inside it, and nil for "(eval)" locations (the identity of
35
+ # the parent template tells evaluated sources apart).
36
+ # ceiling: paths outside the working directory stay absolute, so moving them changes
37
+ # the keys; a configurable root would let those move too.
38
+ # TemplateCacheKey.path("/app/views/show.rabl", "/app/") => "views/show.rabl"
39
+ def path(path, root = self.root)
40
+ path = path.to_s.dup.force_encoding(Encoding::BINARY) # paths may differ in encoding
41
+ return if path.empty? || path.start_with?("(eval")
42
+
43
+ root && path.start_with?(root) ? path.byteslice(root.bytesize..-1) : path
44
+ end
45
+
46
+ # Returns the column a block starts at, which tells apart blocks on one line, or nil when
47
+ # the runtime doesn't expose it.
48
+ # ceiling: without RubyVM::InstructionSequence (e.g. on JRuby), blocks that start on the
49
+ # same line of a template share an identity; put them on separate lines, or give them
50
+ # distinct cache keys.
51
+ def column(block)
52
+ return unless defined?(RubyVM::InstructionSequence)
53
+
54
+ iseq = RubyVM::InstructionSequence.of(block)
55
+ iseq && iseq.to_a[4][:code_location][1]
56
+ rescue StandardError
57
+ nil
58
+ end
59
+
60
+ # Returns the working directory with a trailing separator, as bytes, or nil if it is gone
61
+ def root
62
+ File.join(Dir.pwd, "").force_encoding(Encoding::BINARY)
63
+ rescue SystemCallError
64
+ nil
65
+ end
66
+
67
+ # Returns the options that change the shape of a rendering. A nested (hash format)
68
+ # rendering gets its root options from its parent; for other formats only root
69
+ # options given explicitly are added, so default keys have none. The format `extends`
70
+ # and `partial` look templates up in (lookup_format) is added when it isn't the one
71
+ # rendered, a hash counting as JSON: a nested rendering is a hash for every format.
72
+ # TemplateCacheKey.shape({ :format => "hash", :root => true }, "json") => ["root", "no_child_root"]
73
+ # TemplateCacheKey.shape({ :format => "hash", :child_root => true }, "xml") => ["lookup_format=xml"]
74
+ # TemplateCacheKey.shape({ :format => "json", :except => :b }, "json") => ["except=:b"]
75
+ def shape(options, lookup_format)
76
+ shape = []
77
+ format = options[:format].to_s
78
+
79
+ if format == "hash"
80
+ shape << "root" if options[:root]
81
+ shape << "no_child_root" unless options[:child_root]
82
+ shape << "object_root=#{options[:object_root_name].inspect}" if options[:object_root_name]
83
+ else
84
+ shape << "root=#{options[:root].inspect}" if options.key?(:root)
85
+ shape << "child_root=#{options[:child_root].inspect}" if options.key?(:child_root)
86
+ end
87
+
88
+ shape << "except=#{Array(options[:except]).map { |name| name.inspect }.join(',')}" if options[:except]
89
+ shape << "lookup_format=#{lookup_format}" unless lookup_format == (format == "hash" ? "json" : format)
90
+ shape
91
+ end
92
+ end
93
+ end
data/lib/rabl/tracker.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rabl
2
4
  # DependencyTracker for ActionView to support cache digest
3
5
  class Tracker
data/lib/rabl/version.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rabl
2
- VERSION = "0.17.0"
4
+ VERSION = "0.19.0"
3
5
  end
data/lib/rabl.rb CHANGED
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support'
4
+ require 'active_support/core_ext/array/extract_options'
2
5
  require 'active_support/core_ext/string/inflections'
3
6
  require 'active_support/core_ext/object/blank'
4
7
  require 'active_support/core_ext/hash/reverse_merge'
@@ -15,11 +18,12 @@ require 'rabl/multi_builder'
15
18
  require 'rabl/configuration'
16
19
  require 'rabl/renderer'
17
20
  require 'rabl/cache_engine'
21
+ require 'rabl/template_cache_key'
18
22
 
19
23
  if defined?(Rails) && Rails.respond_to?(:version)
20
- require 'rabl/tracker' if Rails.version =~ /^[45678]/
21
- require 'rabl/digestor' if Rails.version =~ /^[45678]/
22
- require 'rabl/railtie' if Rails.version =~ /^[345678]/
24
+ require 'rabl/tracker'
25
+ require 'rabl/digestor'
26
+ require 'rabl/railtie'
23
27
  end
24
28
 
25
29
  # Rabl.register!
@@ -54,16 +58,18 @@ module Rabl
54
58
  end
55
59
 
56
60
  # Fetches from the source_cache, stores block result in cache if nil
57
- # Used to cache the contents and paths to various rabl templates
58
- # source_cache("users/index", "path/to/view") { "/full/path/to/template/users/index" }
59
- def source_cache(file, view_path, &block)
61
+ # Used to cache the contents and paths to various rabl templates, keyed by
62
+ # everything the lookup depends on (the file, the view path and the lookup details)
63
+ # source_cache("users/index", ["path/to/view"], [:json]) { "/full/path/to/template/users/index" }
64
+ def source_cache(file, view_path, lookup = nil)
60
65
  return yield unless Rabl.configuration.cache_sources
61
66
 
62
- cache_key = [file, view_path].compact.join(":")
63
-
64
67
  @_source_cache ||= {}
65
68
 
66
- @_source_cache[cache_key] ||= yield
69
+ # stores a frozen copy of the key, so later changes to the caller's strings don't reach it
70
+ @_source_cache.fetch([file, view_path, lookup]) do
71
+ @_source_cache[frozen_copy([file, view_path, lookup])] = yield
72
+ end
67
73
  end
68
74
 
69
75
  # Resets the RABL source cache
@@ -71,6 +77,16 @@ module Rabl
71
77
  @_source_cache = {}
72
78
  end
73
79
 
80
+ # Returns a copy of value whose Strings and Arrays are frozen, for cache keys that
81
+ # must not change when the caller's objects do
82
+ def frozen_copy(value)
83
+ case value
84
+ when Array then value.map { |item| frozen_copy(item) }.freeze
85
+ when String then value.frozen? ? value : value.dup.freeze
86
+ else value
87
+ end
88
+ end
89
+
74
90
  # Renders an object using a specified template within an application.
75
91
  # render(@post, 'posts/show', :view_path => "/path/to/app/views")
76
92
  def render(object, source, options = {})
@@ -83,6 +99,4 @@ end
83
99
  if defined?(Padrino)
84
100
  require 'padrino-core'
85
101
  Padrino.after_load { Rabl.register! }
86
- elsif defined?(Rails.version) && Rails.version =~ /^2/
87
- Rabl.register!
88
102
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rabl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.17.0
4
+ version: 0.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan Esquenazi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-11-26 00:00:00.000000000 Z
11
+ date: 2026-11-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 2.3.14
19
+ version: '6.1'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 2.3.14
26
+ version: '6.1'
27
27
  description: General ruby templating with json, bson, xml and msgpack support
28
28
  email:
29
29
  - nesquena@gmail.com
@@ -35,14 +35,12 @@ files:
35
35
  - CONTRIBUTING.md
36
36
  - MIT-LICENSE
37
37
  - README.md
38
+ - SECURITY.md
38
39
  - lib/rabl.rb
39
40
  - lib/rabl/builder.rb
40
41
  - lib/rabl/cache_engine.rb
41
42
  - lib/rabl/configuration.rb
42
43
  - lib/rabl/digestor.rb
43
- - lib/rabl/digestor/rails3.rb
44
- - lib/rabl/digestor/rails41.rb
45
- - lib/rabl/digestor/rails5.rb
46
44
  - lib/rabl/engine.rb
47
45
  - lib/rabl/helpers.rb
48
46
  - lib/rabl/multi_builder.rb
@@ -51,12 +49,17 @@ files:
51
49
  - lib/rabl/renderer.rb
52
50
  - lib/rabl/sources.rb
53
51
  - lib/rabl/template.rb
52
+ - lib/rabl/template_cache_key.rb
54
53
  - lib/rabl/tracker.rb
55
54
  - lib/rabl/version.rb
56
55
  homepage: https://github.com/nesquena/rabl
57
56
  licenses:
58
57
  - MIT
59
- metadata: {}
58
+ metadata:
59
+ changelog_uri: https://github.com/nesquena/rabl/blob/master/CHANGELOG.md
60
+ source_code_uri: https://github.com/nesquena/rabl
61
+ bug_tracker_uri: https://github.com/nesquena/rabl/issues
62
+ rubygems_mfa_required: 'true'
60
63
  post_install_message:
61
64
  rdoc_options: []
62
65
  require_paths:
@@ -65,7 +68,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
65
68
  requirements:
66
69
  - - ">="
67
70
  - !ruby/object:Gem::Version
68
- version: '0'
71
+ version: '2.7'
69
72
  required_rubygems_version: !ruby/object:Gem::Requirement
70
73
  requirements:
71
74
  - - ">="
@@ -1,12 +0,0 @@
1
- require 'action_view'
2
-
3
- module Rabl
4
- class Digestor < ActionView::Digestor
5
- def self.digest(name, format, finder, options = {})
6
- cache_key = [name, format] + Array.wrap(options[:dependencies])
7
- @@cache[cache_key.join('.')] ||= begin
8
- Digestor.new(name, format, finder, options).digest
9
- end
10
- end
11
- end
12
- end
@@ -1,12 +0,0 @@
1
- require 'action_view'
2
-
3
- module Rabl
4
- class Digestor < ActionView::Digestor
5
- def self.digest(options = {})
6
- cache_key = [options[:name]] + Array.wrap(options[:dependencies])
7
- @@cache[cache_key.join('.')] ||= begin
8
- Digestor.new({ :name => options[:name], :finder => options[:finder] }.merge!(options)).digest
9
- end
10
- end
11
- end
12
- end
@@ -1,6 +0,0 @@
1
- require 'action_view'
2
-
3
- module Rabl
4
- class Digestor < ActionView::Digestor
5
- end
6
- end