rabl 0.17.0 → 0.18.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5516efe91fd9188c57c63465ae6a6fda3e06d7f3970f95cced74f43fd8ca753d
4
- data.tar.gz: 972b6c08549be3090a41e26dd7161637bb8a1cbb970e074fae3917afb0bfbe40
3
+ metadata.gz: 14b2f3bca3110dd76fbfa8c05c2dc068ed41719a1ab7c3812450e82869681ed2
4
+ data.tar.gz: 925dbbbda7e8557f4bafd1d41004783239ba75f1ee0f630debd821a3b9fa3919
5
5
  SHA512:
6
- metadata.gz: d1b99e44ff691ad2b25e1a9ad5a2347c4cde07292d898ec23e6fb0a1c4691b00bd1e488d9b3bdf71014ba9561cd5cd8ea1745c3f359fba68d33fa702b1d2e969
7
- data.tar.gz: 723b8b9909c4454f5114bdbe4e81ec4cbe7b7bb80f9cbc1665d0a949e800ea207e82d2196eaf1a0346e52d2366e984768aed04eda2cbd6e4e315faf07f567b7b
6
+ metadata.gz: d41aacd3750c05a910b0dfeff320b1b3953c7569cfa8326f384e770a81fdb98ef1bd6dc7a57510e4cc31377e4f26cf52c3efbf97b7093f505057813226288a50
7
+ data.tar.gz: 1026b5def5c394e27dac2fcf15852045ea5d650a37c7734bc1b50d2f4bc83cb89bbe3aa67791b24cf0938841813af95215441b9e11085dcc1352dce121e8b16e
data/CHANGELOG.md CHANGED
@@ -1,5 +1,56 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 0.18.0 (Oct 31, 2026)
4
+
5
+ ### Requirements
6
+
7
+ * Require Ruby 2.7 or newer. rabl hasn't loaded on Ruby 1.9 since 0.16.0, and 2.0–2.6 are long past end of life; apps on older Rubies can stay on 0.17.0 (@nesquena)
8
+
9
+ ### Changes that can affect existing apps
10
+
11
+ * Every cache key changes once in this release (keys now start with `rabl-v2/` instead of `rabl/`), so no entry cached by an earlier version (which lacks this release's output fixes) is served after upgrading: expect a cold cache once. Old entries are never read again and expire under your store's own policy; in a store without expiry (a file store, or Redis without a TTL), delete keys starting with `rabl/` to reclaim the space. If you delete or match rabl's keys yourself (e.g. `delete_matched`), use the `rabl-v2/` prefix. Fragments Rails caches itself (a rabl template inside an ERB `cache` block, or `render …, cached: true`) are keyed by Rails, so clear those when upgrading. Template paths in keys are relative to the working directory, so only templates outside it get new keys when the application moves (@nesquena)
12
+ * Include the template's identity (its source, location and view path, or the Rails template digest) and the options that shape nested renderings in cache keys, so different templates, child blocks, formats, or an object and a collection caching the same key no longer share cached output (@nesquena)
13
+ * Validate JSONP callback names, write callbacks as `/**/name(...)`, and apply them after the cache so cached output never includes a callback. With `enable_json_callbacks` on, a `callback` parameter that isn't a JavaScript function name (ASCII letters, digits, `_` and `$`, optionally dotted, at most 128 characters) now renders plain JSON, and a valid one gets the `/**/` prefix, as with Rails' `render json:, callback:`. Browsers need no change; clients that compare the response text, or send other callback names, must be updated (@nesquena)
14
+ * `child :name` on a collection is now keyed by the name you wrote, the same as `child :name => :name`. Before, rabl used the relation's table name or the first item's class, so `child :units` on `has_many :units, through: ..., source: :product_unit` rendered `"product_units": [{"product_unit": ...}]` and two associations on one table overwrote each other. Existing apps whose association or method name differs from its table or item class get the new key and item root (`"units": [{"unit": ...}]`); to keep the old output, write the name: `child :units => :product_units`. Item roots are the singular of the name you wrote (`child :data` gives `"data": [{"datum": ...}]`), and with `:root => :other` the key is `other` while items are still named after the symbol (`child :favorites, :root => :likes` gives `"likes": [{"favorite": ...}]`). `child @collection` without a name is unchanged (@nesquena; reported by @jifka in #394)
15
+ * `child @items, :root => :name` now keeps its key when `@items` is empty or nil, rendering `"name": []` or `"name": null` (`<name type="array"/>` or `<name nil="true"/>` in XML), the same as `child @items => :name`. Before, the key was left out; existing apps using the `:root` form will see the new key for empty or nil data. To keep leaving it out, add a condition: `child @items, :root => :name, :unless => lambda { |_| @items.blank? }`. Other blank data (`false`, `""`) and a false `:if`/`:unless` still leave it out (@nesquena; reported by @Kinaan in #725)
16
+ * XML: a collection with an explicit `:object_root` now wraps each item once, in the object root. `collection @categories, :root => "data", :object_root => "category"` rendered `<data type="array"><datum><category>...</category></datum></data>` and now renders `<data type="array"><category>...</category></data>`. Existing XML clients of such collections lose the extra element and must read the items directly; JSON output and collections without `:object_root` are unchanged (@nesquena; reported by @jeffleeismyhero in #581)
17
+ * With Oj 3.17.7 or newer, `require 'rabl'` no longer overwrites Oj options the app has already set. If `Oj.default_options` still has Oj's shipped `mode`, `time_format` and `use_to_json` when rabl is loaded, rabl sets them globally as before (`mode: :compat, time_format: :ruby, use_to_json: true`); if the app (or a library loaded earlier) already chose any of them, they are left alone, and rabl passes `Oj.dump` only the options that differ from Oj's global ones, so rabl's JSON is unchanged. Options the app changes after loading rabl still apply to rabl's output as before: `time_format`, `use_to_json`, and `mode` when it is `:compat`, `:rails` (which escapes `<`, `>` and `&`) or `:custom`; a `:strict`, `:null` or `:object` mode set after loading rabl no longer makes rabl's JSON raise or drop values (it stays `:compat`). What changes for apps on Oj 3.17.7+ that set Oj options before loading rabl: their own `Oj.dump` calls, and a custom `config.json_engine` that calls `Oj.dump(obj)` without options, now use the app's options instead of rabl's (pass `Rabl.configuration.oj_dump_options`, or use `config.json_engine = Oj`, to get rabl's). With older Oj, behaviour is unchanged: rabl sets its options globally at load and calls `Oj.dump(obj)`, because Oj before 3.17.7 passes per-call options on to `to_json`, which json 3.x rejects (@nesquena; reported by @BuonOmo in #744)
18
+ * Fix XML collection items ignoring inflections that match only the underscored name: Array#to_xml singularizes the dasherized name, so with `inflect.irregular 'lesson_progress', 'lessons_progresses'`, `collection @progresses => :lessons_progresses` rendered `<lessons-progress>` items; they are now `<lesson-progress>`, for child collections and arrays returned by `node` too. A name changes when an app inflection that matches the underscored name but not the dasherized one is newer than the rule ActiveSupport applies to the dasherized name: a rule written in underscored form (the irregular above), an underscored uncountable word (`inflect.uncountable 'user_settings'` now gives `<user-settings>` items instead of `<user-setting>`), or a rule using `\w` (`inflect.singular(/^(\w+)ves$/, '\1ve')` now gives `<orange-grove>` for `:orange_groves` instead of `<orange-grofe>`). Built-in inflections, inflections written with dashes (the workaround in #559), acronyms and arrays with their own `to_xml` render as before. XML clients that read these items must use the new names (@nesquena; reported by @passalini in #559)
19
+ * Fix `:root` and `:child_root` given to the engine being ignored in favour of the global `include_*_root` setting, a regression since 0.11; they now win for every entry point, e.g. `Rabl::Renderer.new(..., :root => true)` and Sinatra/Padrino `rabl :template, :root => false`. If you pass these options and relied on them being ignored, remove them (@nesquena; reported by @ninkibah in #486)
20
+ * Fix options passed with an alias, as in `child :posts => :items, :if => ...`, being ignored (Ruby folds them into the data hash), and allow conditions on a `node` without a name (`node(:if => ...) { ... }`). Each method takes only its own options out of such a hash: `child` takes `:if`, `:unless`, `:root` and `:object_root`; `glue` takes `:if` and `:unless`; `collection` takes `:root` and `:object_root`. A Hash given to one of them with some of those keys plus exactly one other key whose value is a String or Symbol is now read as an alias with options, when no options are passed separately. To keep options apart from the data, pass them as their own hash: `child({ :posts => :items }, :if => ...)` (@nesquena; reported by @vimutter in #455 and @jfeust in #395)
21
+ * Fix `attributes :format` (or any name of a private Kernel method) on objects backed by `method_missing`, such as Hashie::Mash or Mongoid dynamic fields, calling the Kernel method. Objects whose `respond_to_missing?` is true for one of their own private methods (e.g. a Draper decorator with `delegate_all` and a private helper of the same name) now get `method_missing` or delegation instead of the private method; make the method public if the template should call it (@nesquena; reported by @johnvoloski in #533)
22
+ * Fix `object @foo => 'foo'` (a String alias) naming the root "string" instead of "foo". Clients reading the `string` root must read the alias you gave (@nesquena; reported by @aripollak in #619)
23
+ * `extends` on a collection of plain Hashes now renders each Hash's own values. Before, the item handed to the extended template was re-read as rabl's `@object => :name` syntax (one key) or as a collection (several keys), so `collection @friends` + `extends 'friends/show'` rendered `[{"uid":"uid"}]` or `[{}]`, or raised. Existing apps rendering Hash items through `extends` get the real values instead of that output, so update clients that relied on it; the alias syntax in `object`, `collection`, `child`, `glue` and an explicit `:object` option to `extends` or `partial` is unchanged. Unchanged too: an unnamed `collection @hashes` still takes its item root from the first Hash (name it, e.g. `collection @friends => :friends`) (@nesquena; reported by @ericgross in #396)
24
+ * Fix `escape_all_output` escaping values twice outside Rails (`a&amp;amp;b`) when they are rendered through `child`, `glue`, `extends` or `partial`; nested templates are now escaped once, with the template they are part of. Under Rails, a node that turns `partial` output into a string (e.g. `partial(...).to_json`) was escaped twice too and is now escaped once. Cached nested fragments now have their own cache keys. Clients that unescaped such values twice must now unescape them once (@nesquena)
25
+ * Fix `extends` and `partial` templates looked up during an HTML request on Rails 7.0 and higher: rabl asked Rails' lookup for the JSON format as a String, which Rails 7.0+ never matches, so the lookup always failed and rabl fell back to its own file search, which ignores the locale and variant (it took the first such file in alphabetical order: `base.fr.json.rabl` for every locale, and `base.json+mobile.rabl` with or without the `+mobile` variant). For the default `.rabl` handler's renders, they are now looked up the same way as for a JSON request. Apps rendering RABL with the default handler for HTML requests on Rails 7.0+ now get the template a `.json` request for the same page gets, where they got a different one before; if a page relied on the template it got before, give that template a name of its own and `extends` it by that name (@nesquena)
26
+ * Under Rails, a template rendered in a format other than JSON during an HTML request (e.g. `Rabl.render(@post, 'posts/show', :format => :xml, :scope => view_context)` in an HTML action) now finds the template itself, its `partial`s and its `extends` (nested ones too) in that format, on every Rails version. Before, the template and its `partial`s were found as JSON (`show.json.rabl`) while a top-level `extends` used the rendered format, and with caching on such a render could share cached output with the same render during a JSON request. Apps rendering a JSON-named template this way now get the template named for the rendered format (`show.xml.rabl`) when there is one; to keep the JSON one, give it a name of its own and render it by that name. Renders during `.json` or `.xml` requests still find templates in the request's format (@nesquena)
27
+ * Outside Rails' own lookup (Sinatra, Padrino, `Rabl::Renderer`, and templates Rails can't find, such as those in `config.view_paths` or a `:view_path` option), `extends` and `partial` now pick the template for the format being rendered and the current locale, as Rails does. Before, they took the first file in alphabetical order: `show.json.rabl` when rendering XML next to `show.xml.rabl`, and `base.fr.json.rabl` for every locale next to `base.json.rabl`. In each view path in order, and the template before the partial, rabl now prefers a file named for the locale and format (`show.fr.json.rabl`), then the locale (`show.fr.rabl`), then the format (`show.json.rabl`), then neither (`show.rabl`); the locales are `I18n.locale`, its fallbacks and the default locale (under Rails, the lookup context's locales). When no file in any view path matches, it takes the first file found, as before, so no template that was found goes missing. What changes for existing apps: templates for several formats or locales under one name now render for their own format or locale; a `show.rabl` next to `show.json.rabl` is now used for formats other than JSON; and a matching template in a later view path now wins over one named for another format or locale in an earlier path. To keep rendering a particular file, give it a name of its own and `extends` it by that name. `cache_sources` keeps these lookups apart by format and locale. The template Sinatra or Padrino renders first is still found by Sinatra or Padrino (@nesquena)
28
+
29
+ ### Fixes
30
+
31
+ * Ignore `extends` called while a template renders, e.g. inside a `node` block, with a warning, instead of writing rabl's internal settings (including the view path) into the response and adding the template to later elements of a collection; `attribute`, `node`, `child` and `glue` called there keep working but now warn too. Use `partial` there (@nesquena; reported by @barmstrong in #477)
32
+ * Fix XML rendering outside Rails raising `NoMethodError` for `to_xml` unless the app had loaded ActiveSupport's XML conversions; rabl now loads them, and explains when the `builder` gem is missing (@nesquena)
33
+ * Fix JSON rendering without Oj raising `NameError` (`uninitialized constant JSON`) unless the app had loaded the json library; rabl now loads it when Oj isn't available (@nesquena)
34
+ * Fix `config.xml_options` being ignored when set after the first XML render: rabl kept the options it read on that render. Each XML render now uses the current `xml_options`; the defaults are unchanged (@nesquena)
35
+ * Fix `extract_options!` being undefined (a `NoMethodError`) when nothing else had loaded ActiveSupport's Array extensions, e.g. in Sinatra apps (@A1bi)
36
+ * Fix caching outside Rails: rendering a cached collection raised `LocalJumpError`, and cached collection fragments were never read back. Sinatra, Padrino and `Rabl::Renderer` cache with `config.perform_caching = true` (@nesquena; reported by @destroytoday in #582)
37
+ * Fix cached `child` nodes in a collection being flattened (or dropped, for a child collection) on a cache hit (@nesquena)
38
+ * Fix cache keys in templates rendered for each item (`extends`, `partial`, `child`) that use the instance variable the template names in `object` or `collection`, such as `cache @user` or `cache ['v1', @topic]`: `object @user` takes the item, but `@user` keeps the outer template's value (or nil), so every item shared one cache entry and could render another item's cached output. The same applied to a template rendered by `Rabl::Renderer` for an object its instance variable doesn't hold (`object @article` rendered with a `Post`). When such a key has that variable but not the item, the item is now added to it, so each item gets its own entry; the rest of the key (e.g. `current_user`) is kept. When the variable is nil, any nil part of the key counts as it, so a key with a nil part (such as an anonymous `current_user`) gets the item added too. Keys that already include the item, such as those using `root_object`, are unaffected by this fix (@nesquena; reported by @quidproquo in #490 and @chancancode in #391)
39
+ * Fix `NoMethodError` (`undefined method 'replace_engine' for nil`) when rendering a cached collection whose `read_multi` results can't be matched to the templates that asked for them, for example with a custom cache engine whose `read_multi` returns other keys than it was given (such as tenant-prefixed ones). Such results are now skipped and rendered as cache misses; to get cache hits, return the keys `read_multi` was given (@nesquena; reported by @Ed-Whitwell-The-Intern in #784 and @BastianHofmann in #639, root cause found by @djlmills)
40
+ * Fix `cache_sources` serving one request's template to another when `extends` or `partial` resolves to a different file per request. Under Rails the source cache is now keyed by the request's format, the lookup details (locale, variants, handlers and registered details such as versioncake's versions) and the view paths, and under Sinatra and Padrino by the app's views, so `show.xml.rabl`, `show.json+mobile.rabl`, `show.fr.json.rabl`, versioncake's `show.json.v2.rabl`, a template in a prepended view path, or the same name in another mounted app's views are no longer replaced by whichever was looked up first. Apps with `cache_sources` on keep one cache entry per distinct lookup, and a lookup through a view path that has no path of its own (a custom resolver without `to_path`), or with a lookup context whose details can't be listed (one wrapped in a delegator), is no longer cached (@nesquena; reported by @alepore in #493)
41
+ * Fix `escape_all_output` changing the app's own arrays and hashes in place (and re-escaping cached fragments outside Rails on every render), raising `FrozenError` on frozen hashes and arrays, and raising `NameError` for `ERB` outside Rails (@nesquena)
42
+ * Errors raised in a template now point at the template's file and line. Under Rails, the backtrace named `(eval at .../rabl/engine.rb)` instead of the `.rabl` file, so the error page showed the wrong line; with `Rabl::Renderer`, `Rabl.render`, Tilt, Sinatra and Padrino the file was right but the line was one too high. This covers errors at the top level, in `node`, `child` and `glue` blocks, in `extends` and `partial` templates, syntax errors, and the location in the warning for `extends` inside a `node` block (@nesquena; reported by @jontonsoup in #423)
43
+ * Errors raised in a Sinatra inline template (one after `__END__` in the app's file) now point at the template's line in that file; they pointed at the line within the template instead, i.e. at the top of the app's file (@nesquena; refs #423)
44
+ * Fix templates saved with a UTF-8 byte order mark (as some Windows editors save files) raising `NoMethodError` for an invisible-prefixed `object` (or whatever the first word is); the mark is now ignored, under Rails, Sinatra, Padrino, Tilt and `Rabl::Renderer`, and in `extends` and `partial` templates (@nesquena)
45
+
46
+ ### Other changes
47
+
48
+ * Allow BSON >= 2.0 (@tagliala)
49
+ * Support frozen string literals: rabl's files are marked `# frozen_string_literal: true`, and the suite runs with `--enable-frozen-string-literal` (@tagliala)
50
+ * Speed up template lookups with `cache_sources` when there are many view paths (@pharris2411)
51
+ * README: `camelize_keys` applies to the attribute, node and child names rabl builds, not to root names or to keys inside a hash a `node` block returns; documents the `deep_transform_keys` workaround. No behaviour change (@nesquena; reported by @apuntovanini in #701)
52
+ * README: caching outside Rails, what `cache_all_output` keys include (don't use it for output that differs per viewer), nil parts in per-item cache keys, and rendering through the controller (`PostsController.render`) outside a request to get the same cache keys as a request, e.g. to warm the cache. No behaviour change (@nesquena; reported by @blotto in #546)
53
+
3
54
  ## 0.17.0 (Nov 26, 2024)
4
55
 
5
56
  * Support for Rails 8 (@basex)
data/README.md CHANGED
@@ -26,6 +26,10 @@ For a breakdown of common misconceptions about RABL, please check out our guide
26
26
 
27
27
  ## Breaking Changes ##
28
28
 
29
+ * v0.18.0 requires Ruby 2.7 or newer (older Rubies can stay on 0.17.0). Every cache key changes once: expect a cold
30
+ cache after upgrading. It also fixes several long-standing output bugs in ways that can change existing output. Check the
31
+ [CHANGELOG](https://github.com/nesquena/rabl/blob/master/CHANGELOG.md) before upgrading.
32
+
29
33
  * v0.9.0 (released Oct 14, 2013) changes the [default node name for certain associations](https://github.com/nesquena/rabl/issues/505)
30
34
  especially around STI models. You might want to verify for any breakages as a result and
31
35
  be more explicit by specifying an alias i.e `@users => :users`
@@ -52,13 +56,15 @@ or add to your Gemfile:
52
56
  # Gemfile
53
57
  gem 'rabl'
54
58
  # Also add either `oj` or `yajl-ruby` as the JSON parser
55
- # If using `oj`, Rabl will set the mode to :compat
59
+ # If using `oj`, Rabl renders with Oj's :compat mode and sets it as Oj's default (with Oj 3.17.7+, only if you haven't configured Oj yet)
56
60
  gem 'oj'
57
61
  ```
58
62
 
59
- and run `bundle install` to install the dependency.
63
+ and run `bundle install` to install the dependency. RABL needs Ruby 2.7 or newer.
64
+
65
+ To render XML, also add `gem 'builder'` (Rails apps already include it); RABL loads ActiveSupport's XML conversions itself.
60
66
 
61
- If you are using **Rails 2.3.8 (and up), Rails 3.X or Padrino**, RABL works without configuration.
67
+ If you are using **Rails or Padrino**, RABL works without configuration.
62
68
 
63
69
  **Important:** With Padrino, be sure that **the rabl gem is listed after the padrino gem in your Gemfile**, otherwise
64
70
  Rabl will not register properly as a template engine.
@@ -154,22 +160,32 @@ end
154
160
  Each option specifies behavior related to RABL's output.
155
161
 
156
162
  If `include_json_root` is disabled that removes the root node for each root object in the output, and `enable_json_callbacks` enables support for 'jsonp' style callback
157
- output if the incoming request has a 'callback' parameter.
163
+ output if the incoming request has a 'callback' parameter. The callback must be a JavaScript function name (letters, digits, `_` and `$`,
164
+ optionally dotted, up to 128 characters) and the output is written as `/**/callback(...)`; any other value renders plain JSON.
158
165
 
159
166
  If `include_child_root` is set to false then child objects in the response will not include
160
167
  a root node by default. This allows you to further fine-tune your desired response structure.
161
168
 
162
- If `cache_engine` is set, you should assign it to a class with a `fetch` method. See the [default engine](https://github.com/nesquena/rabl/blob/master/lib/rabl/cache_engine.rb) for an example.
169
+ If `cache_engine` is set, you should assign it to an object with `fetch`, `write` and `read_multi` methods (`read_multi` reads the
170
+ items of a collection at once unless `config.use_read_multi = false`, and must return a hash keyed by the keys it was given).
171
+ See the [default engine](https://github.com/nesquena/rabl/blob/master/lib/rabl/cache_engine.rb) for an example. Outside Rails, the
172
+ default engine keeps entries in memory, in each process, and ignores `expires_in`: entries live until the process ends or the
173
+ 100,000-entry limit evicts them. For expiry, use an ActiveSupport store, e.g. `config.cache_engine = ActiveSupport::Cache::MemoryStore.new`.
163
174
 
164
175
  If `perform_caching` is set to `true` then it will perform caching. You
165
176
  can ignore this option if you are using Rails, it's same to Rails
166
- `config.action_controller.perform_caching`
177
+ `config.action_controller.perform_caching`. In a process without Rails (Sinatra, Padrino, plain `Rabl::Renderer`), set it to `true` to
178
+ turn caching on; once Rails is loaded, rabl follows `ActionController::Base.perform_caching` instead, also for `Rabl::Renderer`.
167
179
 
168
180
  If `cache_sources` is set to `true`, template lookups will be cached for improved performance.
169
181
  The cache can be reset manually by running `Rabl.reset_source_cache!` within your application.
170
182
 
171
183
  If `cache_all_output` is set to `true`, every template including each individual template used as part of a collection will be cached separately.
172
184
  Additionally, anything within child, glue and partial will also be cached separately.
185
+ Those entries are keyed by the object (its `cache_key`), the template and the format only. The template's own `cache` key is added
186
+ to the entry of the object the template renders, but no `cache` key, including one inside a child block, is part of the entries of
187
+ collection items or of what child, glue and partial render, so anything else the output depends on (the current user, locals, params)
188
+ is not part of those keys: don't turn `cache_all_output` on for templates whose output differs per viewer or per request.
173
189
  To cache just a single template, see the section titled 'Caching' below.
174
190
 
175
191
  If `escape_all_output` is set to `true` and ActiveSupport is available, attribute output will be escaped using [ERB::Util.html_escape](http://api.rubyonrails.org/classes/ERB/Util.html).
@@ -188,7 +204,14 @@ If `exclude_nil_values` is set to `true`, all values that are `nil` and would no
188
204
 
189
205
  if `exclude_empty_values_in_collections` is set to `true`, all values in a collection that are `{}` and would normally be displayed as `{}` in the response are not included in the response.
190
206
 
191
- If `camelize_keys` is set to `true`, all object keys will be converted to camel case. By default the first character will be lower case. The value can be set to `:upper` to set the first character to upper case.
207
+ If `camelize_keys` is set to `true`, the keys rabl builds from your template (attribute, node and child names) will be converted to camel case. By default the first character will be lower case. The value can be set to `:upper` to set the first character to upper case. Root and object root names (`object @user => :app_user`) are left as written, and so are the keys inside a hash that a `node` block returns, which rabl inserts unchanged. To camelize those, transform the hash in the block (`deep_transform_keys` comes with ActiveSupport, loaded in Rails; elsewhere `require 'active_support/core_ext/hash/keys'`):
208
+
209
+ ```ruby
210
+ node(:preferences) do |user|
211
+ { has_visible_profile: true }.deep_transform_keys { |key| key.to_s.camelize(:lower) }
212
+ end
213
+ # => "preferences": { "hasVisibleProfile": true }
214
+ ```
192
215
 
193
216
  If you wish to use [oj](https://github.com/ohler55/oj) as
194
217
  the primary JSON encoding engine simply add that to your Gemfile:
@@ -551,15 +574,65 @@ Note that RABL can be nested arbitrarily deep within child nodes to allow for th
551
574
 
552
575
  ### Caching ###
553
576
 
554
- RABL has built-in caching support for templates leveraging fragment caching strategies. Note that caching is currently **only available** for Rails but support for other frameworks is planned in a future release. Simplest caching usage is:
577
+ RABL has built-in caching support for templates leveraging fragment caching strategies. It works with Rails (using `Rails.cache`, or
578
+ `config.cache_engine` if set), and with Sinatra, Padrino and `Rabl::Renderer` when `config.perform_caching` is `true` (using
579
+ `config.cache_engine`; see Configuration above). Inside a Rails process, `Rabl::Renderer` follows `ActionController::Base.perform_caching`.
580
+ Simplest caching usage is:
555
581
 
556
582
  ```ruby
557
583
  # app/views/users/show.json.rabl
558
584
  object @quiz
559
- cache @quiz # key = rabl/quiz/[cache_key]
585
+ cache @quiz # key = rabl-v2/quiz/[cache_key]
560
586
  attribute :title
561
587
  ```
562
588
 
589
+ The full cache key also includes the format and identifies the template (the Rails template digest when available, otherwise a digest of
590
+ the template's source, location and view path), so different templates, child blocks, or an object and a collection caching the same key are stored separately.
591
+ As with Rails fragment caching, anything else the output depends on must be part of the key: a block that runs several times with different
592
+ data (for example inside a loop), locals, or the current user. That includes the locale and the request variant when `extends` or `partial`
593
+ picks a template for each (`base.fr.json.rabl`, `base.json+mobile.rabl`): the key has the format templates are looked up in, but not
594
+ those, so add them, e.g. `cache ['v1', root_object, I18n.locale, request.variant]` under Rails.
595
+ Blocks are told apart by where they start in the template, so a block written once in a method (a `def` in the template, or a helper)
596
+ and called several times is the same block each time: give each call a distinct cache key, e.g. by adding the arguments the method was
597
+ called with. On runtimes without `RubyVM::InstructionSequence` (such as JRuby) the start is the line only, so two caching blocks that
598
+ start on the same line need distinct cache keys there.
599
+
600
+ The key covers the template's own source, but not the templates it uses through `extends` or `partial` (under Rails, the template
601
+ digest looks those up as underscore partials, such as `_base`, so it leaves out templates named without the underscore): after editing
602
+ one of those, the output cached before the edit is still served. Change the cache key of each template that uses it, e.g. from
603
+ `cache ['v1', root_object]` to `cache ['v2', root_object]`.
604
+
605
+ In a template that is also rendered for each item (through `extends`, `partial` or `child`), key the cache on `root_object`, the item
606
+ being rendered:
607
+
608
+ ```ruby
609
+ # app/views/topics/base.json.rabl, used by `collection @topics` + `extends 'topics/base'`
610
+ object @topic
611
+ cache ['v1', root_object]
612
+ attribute :id
613
+ ```
614
+
615
+ There, `object @topic` takes the item, but `@topic` itself keeps the controller's value (or nil). When a key has the variable the
616
+ template names in `object` but not the item, rabl adds the item to it, so `cache @topic` and `cache ['v1', @topic]` also get one entry
617
+ per item. When that variable is nil, any nil part of the key counts as it, so a key such as `cache ['v1', current_user]` gets the item
618
+ added for an anonymous (nil) viewer. The other parts of the key are used as they are: any other instance variable keeps its outer value,
619
+ and a helper such as `current_user` keeps the viewer, so `cache ['v1', current_user]` without `root_object` gives all the items one
620
+ entry per viewer, apart from the nil case above. Include `root_object` whenever the output depends on the item.
621
+
622
+ Under Rails, keys include the Rails template digest when the template is rendered through a controller or view. `Rabl.render` and
623
+ `Rabl::Renderer` used outside a request have no view context, so their keys identify the template differently and don't match the keys of
624
+ a request. To render outside a request with the same keys, for example to warm the cache in a background job, render through the
625
+ controller (Rails 5 and newer):
626
+
627
+ ```ruby
628
+ PostsController.render(:show, assigns: { post: post }, formats: [:json])
629
+ # or ApplicationController.render(template: 'posts/show', assigns: { post: post }, formats: [:json])
630
+ ```
631
+
632
+ RABL takes the format from the request, so for another format set the request's `Accept` header as well:
633
+ `PostsController.renderer.new('HTTP_ACCEPT' => 'application/xml').render(:show, assigns: { post: post }, formats: [:xml])`.
634
+ The renderer's request has empty params and an empty session, so output that depends on them renders as it would for such a request.
635
+
563
636
  Caching can significantly speed up the rendering of RABL templates in production and is strongly recommended when possible. For more a more detailed look at caching, check out the [Caching](https://github.com/nesquena/rabl/wiki/Caching-in-RABL) guide on the wiki.
564
637
 
565
638
  ### Rendering Templates Directly ###
data/lib/rabl/builder.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support/inflector' # for the sake of camelcasing keys
2
4
 
3
5
  module Rabl
@@ -70,6 +72,11 @@ module Rabl
70
72
  end
71
73
 
72
74
  protected
75
+ # Returns the engine whose template this builder renders
76
+ def rabl_template_engine
77
+ @options[:template_engine]
78
+ end
79
+
73
80
  def replace_nil_values
74
81
  @_result = deep_replace_nil_values(@_result)
75
82
  end
@@ -169,7 +176,9 @@ module Rabl
169
176
  # child(@user => :person) { ... }
170
177
  # child(@users => :people) { ... }
171
178
  def child(data, options = {}, &block)
172
- return unless data.present? && resolve_condition(options)
179
+ # A child named by :root keeps its key for nil or an empty collection, like child(@users => :people) (#725)
180
+ keep_blank = is_name_value?(options[:root]) && (data.nil? || is_collection?(data))
181
+ return unless (data.present? || keep_blank) && resolve_condition(options)
173
182
 
174
183
  name = is_name_value?(options[:root]) ? options[:root] : data_name(data)
175
184
  object = data_object(data)
@@ -179,6 +188,7 @@ module Rabl
179
188
  engine_options[:object_root_name] = options[:object_root] if is_name_value?(options[:object_root])
180
189
 
181
190
  object = { object => name } if data.is_a?(Hash) && object # child :users => :people
191
+ object = { object => data } if data.is_a?(Symbol) && is_collection?(object) # child :units: items are "unit" (#394)
182
192
 
183
193
  engines << { create_key(name) => object_to_engine(object, engine_options, &block) }
184
194
  end
@@ -199,6 +209,7 @@ module Rabl
199
209
  return unless resolve_condition(options)
200
210
 
201
211
  options = @options.slice(:child_root).merge!(:object => @_object).merge!(options)
212
+ options[:data_item] = @_object if @_object.is_a?(Hash) # a Hash item is data, not alias syntax (#396)
202
213
  engines << partial_as_engine(file, options, &block)
203
214
  end
204
215
 
@@ -247,6 +258,13 @@ module Rabl
247
258
  def cache_results(&block)
248
259
  if template_cache_configured? && Rabl.configuration.cache_all_output && @_object.respond_to?(:cache_key)
249
260
  cache_key = [@_object, @options[:root_name], @options[:format]]
261
+ engine = rabl_template_engine
262
+ if engine
263
+ cache_key << engine.rabl_template_identity
264
+ # the template's own `cache` key also applies when it renders this object
265
+ cache_key.concat(engine.rabl_cache_key_parts) if engine.root_object.equal?(@_object)
266
+ end
267
+ cache_key.concat(TemplateCacheKey.shape(@options, source_lookup_format))
250
268
 
251
269
  fetch_result_from_cache(cache_key, &block)
252
270
  else # skip cache
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Defines the default cache engine for RABL when caching is invoked for a template.
2
4
  # You can define your own caching engines by creating an object that responds to fetch and
3
5
  # setting the configuration option:
@@ -52,7 +54,7 @@ module Rabl
52
54
  if defined?(Rails)
53
55
  Rails.cache.write(key, value, options)
54
56
  else
55
- @cache[key] = yield
57
+ @cache[key] = value
56
58
  end
57
59
  end
58
60
 
@@ -61,7 +63,7 @@ module Rabl
61
63
  if defined?(Rails)
62
64
  Rails.cache.read_multi(*keys, options)
63
65
  else
64
- keys.inject({}) { |hash, key| hash[key] = nil; hash }
66
+ keys.inject({}) { |hash, key| hash[key] = @cache[key]; hash }
65
67
  end
66
68
  end
67
69
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # We load the msgpack library if it is available.
2
4
  begin
3
5
  require 'msgpack'
@@ -16,11 +18,11 @@ begin
16
18
  rescue LoadError
17
19
  end
18
20
 
19
- # Set default options for Oj json parser (if exists)
21
+ # We load the oj library if it is available (it becomes the default json_engine).
20
22
  begin
21
23
  require 'oj'
22
- Oj.default_options = { :mode => :compat, :time_format => :ruby, :use_to_json => true }
23
24
  rescue LoadError
25
+ require 'json' # the default json_engine without Oj
24
26
  end
25
27
 
26
28
  module Rabl
@@ -56,6 +58,43 @@ module Rabl
56
58
 
57
59
  DEFAULT_XML_OPTIONS = { :dasherize => true, :skip_types => false }
58
60
 
61
+ # True when version (e.g. Oj::VERSION) is at least minimum, compared part by part as
62
+ # numbers: oj_version_at_least?("3.10.0", [3, 17, 7]) => false
63
+ def self.oj_version_at_least?(version, minimum)
64
+ (version.to_s.split('.').map(&:to_i) <=> minimum) >= 0
65
+ end
66
+
67
+ # The Oj options rabl renders with, set globally at load as rabl always did (without :nan).
68
+ # :nan => :word keeps NaN/Infinity as words when :mode is passed per call (#744); a global
69
+ # :compat already writes them that way.
70
+ OJ_OPTIONS = { :mode => :compat, :time_format => :ruby, :use_to_json => true, :nan => :word }.freeze
71
+
72
+ # Oj before 3.17.7 hands the options given to Oj.dump on to to_json, which json 3.x rejects
73
+ # (ArgumentError: unknown keywords). So rabl leaves the app's own Oj options alone and passes
74
+ # its own per call only from 3.17.7 (#744); with older Oj it behaves as it always did: it sets
75
+ # OJ_OPTIONS globally at load and calls Oj.dump(obj).
76
+ OJ_PER_CALL_OPTIONS = !!(defined?(::Oj::VERSION) && oj_version_at_least?(::Oj::VERSION, [3, 17, 7]))
77
+
78
+ # What Oj ships for the options rabl sets globally
79
+ OJ_SHIPPED_OPTIONS = { :mode => :object, :time_format => :unix, :use_to_json => false }.freeze
80
+
81
+ # With Oj 3.17.7+, rabl still sets its options globally when the app (or a library loaded
82
+ # earlier) has not chosen any of them yet, so a json_engine wrapper or app code calling
83
+ # Oj.dump(obj) without options keeps rabl's settings; otherwise the app's Oj.default_options
84
+ # are left alone (#744). An app that sets exactly Oj's shipped values cannot be told apart
85
+ # from one that set nothing, and gets rabl's settings as before.
86
+ if defined?(::Oj) &&
87
+ (!OJ_PER_CALL_OPTIONS || ::Oj.default_options.values_at(*OJ_SHIPPED_OPTIONS.keys) == OJ_SHIPPED_OPTIONS.values)
88
+ ::Oj.default_options = OJ_OPTIONS.reject { |key, _| key == :nan }
89
+ end
90
+
91
+ # Oj modes an app can switch to after loading rabl that rabl's output follows, as it did
92
+ # when rabl's settings were global (e.g. :rails, which escapes "<", ">" and "&")
93
+ OJ_APP_MODES = [:compat, :rails, :custom].freeze
94
+
95
+ # Oj.default_options once rabl was loaded, to tell which ones the app changed afterwards
96
+ OJ_DEFAULTS_AT_LOAD = (::Oj.default_options.freeze if defined?(::Oj))
97
+
59
98
  def initialize
60
99
  @include_json_root = true
61
100
  @include_child_root = true
@@ -90,6 +129,34 @@ module Rabl
90
129
  @json_engine || (defined?(::Oj) ? ::Oj : ::JSON)
91
130
  end
92
131
 
132
+ # The options rabl passes to Oj.dump (#744): OJ_OPTIONS, except where the app changed
133
+ # Oj.default_options after rabl was loaded. Those changes win, as they did when rabl's
134
+ # settings were global: a new :time_format or :use_to_json, and a new mode if it is one
135
+ # of OJ_APP_MODES (then :nan follows that mode too). Another new mode (:strict, :null,
136
+ # :object, ...) is ignored, since rabl's output would raise on or drop plain objects.
137
+ # Options the app set before rabl was loaded are ignored too: rabl's output stays as it
138
+ # was when rabl overwrote them.
139
+ #
140
+ # Only options that differ from the current global ones are returned, so with rabl's
141
+ # globals in place the hash is empty and Oj.dump gets no options, as before. :nan is only
142
+ # needed with an explicit :mode. With Oj before 3.17.7 it is always empty (see
143
+ # OJ_PER_CALL_OPTIONS).
144
+ # ceiling: Oj.default_options builds a new Hash on each render (a few microseconds);
145
+ # caching it would miss changes the app makes later.
146
+ def oj_dump_options
147
+ return {} unless OJ_PER_CALL_OPTIONS
148
+
149
+ current = ::Oj.default_options
150
+ changed = lambda { |key| OJ_DEFAULTS_AT_LOAD && current[key] != OJ_DEFAULTS_AT_LOAD[key] }
151
+ app_mode = changed.call(:mode) && OJ_APP_MODES.include?(current[:mode])
152
+
153
+ options = OJ_OPTIONS.reject do |key, value|
154
+ ([:mode, :nan].include?(key) ? app_mode : changed.call(key)) || current[key] == value
155
+ end
156
+ options.delete(:nan) unless options.key?(:mode)
157
+ options
158
+ end
159
+
93
160
  ##
94
161
  # @return the MessagePack encoder/engine to use.
95
162
  def msgpack_engine
@@ -115,9 +182,9 @@ module Rabl
115
182
  __send__(option)
116
183
  end
117
184
 
118
- # Returns merged default and inputted xml options
185
+ # Returns merged default and inputted xml options, as they are now
119
186
  def default_xml_options
120
- @_default_xml_options ||= @xml_options.reverse_merge(DEFAULT_XML_OPTIONS)
187
+ @xml_options.reverse_merge(DEFAULT_XML_OPTIONS)
121
188
  end
122
189
  end
123
190
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'action_view'
2
4
 
3
5
  module Rabl
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'action_view'
2
4
 
3
5
  module Rabl
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'action_view'
2
4
 
3
5
  module Rabl
data/lib/rabl/digestor.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'action_view'
2
4
 
3
5
  module Rabl