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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5516efe91fd9188c57c63465ae6a6fda3e06d7f3970f95cced74f43fd8ca753d
4
- data.tar.gz: 972b6c08549be3090a41e26dd7161637bb8a1cbb970e074fae3917afb0bfbe40
3
+ metadata.gz: 8bcb492fc67214455ff0bce236a63bdf0d2cb8a696253f3c3b1fa2eef3d6a08a
4
+ data.tar.gz: 79a96efbd90a60df008fd50313ef1f097bfbac97cee4298529491e00dac3f13c
5
5
  SHA512:
6
- metadata.gz: d1b99e44ff691ad2b25e1a9ad5a2347c4cde07292d898ec23e6fb0a1c4691b00bd1e488d9b3bdf71014ba9561cd5cd8ea1745c3f359fba68d33fa702b1d2e969
7
- data.tar.gz: 723b8b9909c4454f5114bdbe4e81ec4cbe7b7bb80f9cbc1665d0a949e800ea207e82d2196eaf1a0346e52d2366e984768aed04eda2cbd6e4e315faf07f567b7b
6
+ metadata.gz: dfe2684ff81aa3b56be1a911da48f779f0493746cd996dd48d63715a9ebdfcfc544684cbf0b1fcd5ed86ba0526a536d356cbf10fc8fae48b02c7b7a9e5cc8ab1
7
+ data.tar.gz: 5241f944033242864fe36ec1ddca572b35e0cbad72067340de2c7be76a8d4d43b854d6caaebd4fec9c303ac96488091d857764a06c81cd1d3f5daf79daf2f021
data/CHANGELOG.md CHANGED
@@ -1,5 +1,75 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 0.19.0 (Nov 1, 2026)
4
+
5
+ ### Requirements
6
+
7
+ * Require ActiveSupport 6.1 or newer, and so Rails 6.1 or newer under Rails. The code rabl kept for Rails 2 to 5 (their template handlers, digestors and template lookups) is removed; Rails 6.1 and newer, Sinatra and Padrino render as before. Apps on an older Rails or ActiveSupport can stay on 0.18.x (@nesquena)
8
+
9
+ ### Fixes
10
+
11
+ * Load rabl's Rails integration on Rails 9 and later: rabl recognised Rails 3 to 8 by the first digit of `Rails.version`, so on a newer Rails the railtie and the `.rabl` template handler never loaded and actions rendered empty responses. rabl no longer checks the Rails version, so its integration loads on every Rails from 6.1 on, with no upper bound; Rails 6.1 to 8 load the same parts as before (@nesquena)
12
+ * Fix rabl defining a top-level `LRU` class for its own cache, which raised `TypeError: superclass mismatch for class LRU` in apps with an `LRU` class of their own; it is now `Rabl::CacheEngine::LRU` (use that name if your code used rabl's `LRU`). Once that cache (used outside Rails) held its 100,000 entries, each write also copied every key to drop the oldest one, about 0.5 ms per write; it now drops the same entries in a few µs (@nesquena)
13
+ * Fix `Rabl::Renderer` and `Rabl.render` raising `NoMethodError` (`undefined method 'underscore' for nil`) for an object, or a collection, whose class has no name, such as a `Struct.new` or `Class.new` object. The renderer sets an instance variable named after the object's class (`@post` for a `Post`); for an unnamed class it now sets none, and the template gets the object as for any other (@nesquena)
14
+
15
+ ### Other changes
16
+
17
+ * `Rabl::Renderer#process_source` and the JSONP callback check in `Rabl::Engine` answer their yes/no question about a string with `String#include?` and `Regexp#match?` instead of `=~`, which built a `MatchData` neither check read: creating a renderer for a template string is about 1.2x faster and allocates 3 objects fewer, a full render about 2% faster and 3 objects fewer, and a render with `enable_json_callbacks` 3 objects fewer. The output is unchanged, and a source that is not valid in its encoding still raises the same `ArgumentError: invalid byte sequence in UTF-8`, from `Engine#eval_source` when it has a newline and from the template lookup when it doesn't; a source in a non-ASCII-compatible encoding, such as UTF-16LE, still raises `Encoding::CompatibilityError` from the check, now with Ruby's `incompatible character encodings` message rather than `incompatible encoding regexp match` (@tagliala; change written by DeepSeek Flash, reviewed by Claude Opus 5.5 and Claude Fable 5.1)
18
+ * CI now generates fresh Rails 7.2, Rails 8.1 and Sinatra apps that use rabl from the checkout and runs their tests (`test/apps/build_and_test.sh`), in place of the checked-in fixture apps for Rails 2 to 6 and Padrino under `fixtures/`, which CI never ran. No change to the gem (@nesquena)
19
+ * README: a `child` block is passed the child's data (e.g. the posts) and a `glue` block the glued object, not the parent, with examples that run; the partial example passes the post (`:object => post`, not `:post`); the JSON engine is Oj when it is loaded and Ruby's JSON otherwise (yajl isn't used); and under Rails, Rails sets the content type from the template's format. No behaviour change (@nesquena; reported by @nirvdrum in #393 and @adie in #580)
20
+ * The gem's rubygems.org page links the changelog, source code and issue tracker, and pushes to rubygems.org now require MFA (@nesquena)
21
+
22
+ ## 0.18.0 (Oct 31, 2026)
23
+
24
+ ### Requirements
25
+
26
+ * 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)
27
+
28
+ ### Changes that can affect existing apps
29
+
30
+ * 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)
31
+ * 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)
32
+ * 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)
33
+ * `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)
34
+ * `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)
35
+ * 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)
36
+ * 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)
37
+ * 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)
38
+ * 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)
39
+ * 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)
40
+ * 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)
41
+ * 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)
42
+ * `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)
43
+ * 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)
44
+ * 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)
45
+ * 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)
46
+ * 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)
47
+
48
+ ### Fixes
49
+
50
+ * 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)
51
+ * 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)
52
+ * 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)
53
+ * 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)
54
+ * Fix `extract_options!` being undefined (a `NoMethodError`) when nothing else had loaded ActiveSupport's Array extensions, e.g. in Sinatra apps (@A1bi)
55
+ * 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)
56
+ * Fix cached `child` nodes in a collection being flattened (or dropped, for a child collection) on a cache hit (@nesquena)
57
+ * 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)
58
+ * 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)
59
+ * 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)
60
+ * 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)
61
+ * 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)
62
+ * 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)
63
+ * 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)
64
+
65
+ ### Other changes
66
+
67
+ * Allow BSON >= 2.0 (@tagliala)
68
+ * Support frozen string literals: rabl's files are marked `# frozen_string_literal: true`, and the suite runs with `--enable-frozen-string-literal` (@tagliala)
69
+ * Speed up template lookups with `cache_sources` when there are many view paths (@pharris2411)
70
+ * 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)
71
+ * 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)
72
+
3
73
  ## 0.17.0 (Nov 26, 2024)
4
74
 
5
75
  * Support for Rails 8 (@basex)
data/README.md CHANGED
@@ -26,6 +26,13 @@ For a breakdown of common misconceptions about RABL, please check out our guide
26
26
 
27
27
  ## Breaking Changes ##
28
28
 
29
+ * v0.19.0 requires ActiveSupport 6.1 or newer (and so Rails 6.1 or newer under Rails). Apps on an older Rails or
30
+ ActiveSupport can stay on 0.18.x.
31
+
32
+ * 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
33
+ cache after upgrading. It also fixes several long-standing output bugs in ways that can change existing output. Check the
34
+ [CHANGELOG](https://github.com/nesquena/rabl/blob/master/CHANGELOG.md) before upgrading.
35
+
29
36
  * v0.9.0 (released Oct 14, 2013) changes the [default node name for certain associations](https://github.com/nesquena/rabl/issues/505)
30
37
  especially around STI models. You might want to verify for any breakages as a result and
31
38
  be more explicit by specifying an alias i.e `@users => :users`
@@ -51,14 +58,16 @@ or add to your Gemfile:
51
58
  ```ruby
52
59
  # Gemfile
53
60
  gem 'rabl'
54
- # Also add either `oj` or `yajl-ruby` as the JSON parser
55
- # If using `oj`, Rabl will set the mode to :compat
61
+ # Optionally add `oj`: RABL encodes JSON with Oj when it is loaded, and with Ruby's JSON library otherwise
62
+ # 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
63
  gem 'oj'
57
64
  ```
58
65
 
59
- and run `bundle install` to install the dependency.
66
+ and run `bundle install` to install the dependency. RABL needs Ruby 2.7 or newer and ActiveSupport 6.1 or newer (on Rails, Rails 6.1 or newer).
67
+
68
+ To render XML, also add `gem 'builder'` (Rails apps already include it); RABL loads ActiveSupport's XML conversions itself.
60
69
 
61
- If you are using **Rails 2.3.8 (and up), Rails 3.X or Padrino**, RABL works without configuration.
70
+ If you are using **Rails or Padrino**, RABL works without configuration.
62
71
 
63
72
  **Important:** With Padrino, be sure that **the rabl gem is listed after the padrino gem in your Gemfile**, otherwise
64
73
  Rabl will not register properly as a template engine.
@@ -129,7 +138,7 @@ Rabl.configure do |config|
129
138
  # config.cache_engine = Rabl::CacheEngine.new # Defaults to Rails cache
130
139
  # config.perform_caching = false
131
140
  # config.escape_all_output = false
132
- # config.json_engine = nil # Class with #dump class method (defaults JSON)
141
+ # config.json_engine = nil # Class with #dump class method (defaults to Oj when it is loaded, else ::JSON)
133
142
  # config.msgpack_engine = nil # Defaults to ::MessagePack
134
143
  # config.bson_engine = nil # Defaults to ::BSON
135
144
  # config.plist_engine = nil # Defaults to ::Plist::Emit
@@ -154,22 +163,32 @@ end
154
163
  Each option specifies behavior related to RABL's output.
155
164
 
156
165
  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.
166
+ output if the incoming request has a 'callback' parameter. The callback must be a JavaScript function name (letters, digits, `_` and `$`,
167
+ optionally dotted, up to 128 characters) and the output is written as `/**/callback(...)`; any other value renders plain JSON.
158
168
 
159
169
  If `include_child_root` is set to false then child objects in the response will not include
160
170
  a root node by default. This allows you to further fine-tune your desired response structure.
161
171
 
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.
172
+ If `cache_engine` is set, you should assign it to an object with `fetch`, `write` and `read_multi` methods (`read_multi` reads the
173
+ items of a collection at once unless `config.use_read_multi = false`, and must return a hash keyed by the keys it was given).
174
+ See the [default engine](https://github.com/nesquena/rabl/blob/master/lib/rabl/cache_engine.rb) for an example. Outside Rails, the
175
+ default engine keeps entries in memory, in each process, and ignores `expires_in`: entries live until the process ends or the
176
+ 100,000-entry limit evicts them. For expiry, use an ActiveSupport store, e.g. `config.cache_engine = ActiveSupport::Cache::MemoryStore.new`.
163
177
 
164
178
  If `perform_caching` is set to `true` then it will perform caching. You
165
179
  can ignore this option if you are using Rails, it's same to Rails
166
- `config.action_controller.perform_caching`
180
+ `config.action_controller.perform_caching`. In a process without Rails (Sinatra, Padrino, plain `Rabl::Renderer`), set it to `true` to
181
+ turn caching on; once Rails is loaded, rabl follows `ActionController::Base.perform_caching` instead, also for `Rabl::Renderer`.
167
182
 
168
183
  If `cache_sources` is set to `true`, template lookups will be cached for improved performance.
169
184
  The cache can be reset manually by running `Rabl.reset_source_cache!` within your application.
170
185
 
171
186
  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
187
  Additionally, anything within child, glue and partial will also be cached separately.
188
+ Those entries are keyed by the object (its `cache_key`), the template and the format only. The template's own `cache` key is added
189
+ 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
190
+ collection items or of what child, glue and partial render, so anything else the output depends on (the current user, locals, params)
191
+ is not part of those keys: don't turn `cache_all_output` on for templates whose output differs per viewer or per request.
173
192
  To cache just a single template, see the section titled 'Caching' below.
174
193
 
175
194
  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 +207,14 @@ If `exclude_nil_values` is set to `true`, all values that are `nil` and would no
188
207
 
189
208
  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
209
 
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.
210
+ 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'`):
211
+
212
+ ```ruby
213
+ node(:preferences) do |user|
214
+ { has_visible_profile: true }.deep_transform_keys { |key| key.to_s.camelize(:lower) }
215
+ end
216
+ # => "preferences": { "hasVisibleProfile": true }
217
+ ```
192
218
 
193
219
  If you wish to use [oj](https://github.com/ohler55/oj) as
194
220
  the primary JSON encoding engine simply add that to your Gemfile:
@@ -346,15 +372,18 @@ child :posts => :foobar do
346
372
  end
347
373
  ```
348
374
 
349
- You can also pass in the current object:
375
+ The block is passed the child's data (here the user's posts, not the user), and the parent is still
376
+ available as `@user`:
350
377
 
351
378
  ```ruby
352
379
  object @user
353
- child :posts do |user|
354
- attribute :title unless user.suspended?
380
+ child :posts do |posts|
381
+ attribute :title unless @user.suspended?
355
382
  end
356
383
  ```
357
384
 
385
+ To decide for each post, use a condition, which is passed the post: `attribute :title, :if => lambda { |post| post.published? }`.
386
+
358
387
  ### Gluing Attributes ###
359
388
 
360
389
  You can also append child attributes back to the root node:
@@ -368,11 +397,11 @@ end
368
397
 
369
398
  Use glue to add additional attributes to the parent object.
370
399
 
371
- You can also pass in the current object:
400
+ The block is passed the glued object (here `@post`, not the user):
372
401
 
373
402
  ```ruby
374
403
  object @user
375
- glue(@post) {|user| attribute :title if user.active? }
404
+ glue(@post) { |post| attribute :title if post.published? }
376
405
  ```
377
406
 
378
407
  ### Custom Nodes ###
@@ -458,7 +487,7 @@ collection @posts
458
487
 
459
488
  extends('posts/show', :locals => { :hide_comments => true })
460
489
  # or using partial instead of extends
461
- # node(false) { |post| partial('posts/show', :object => :post, :locals => { :hide_comments => true })}
490
+ # node(false) { |post| partial('posts/show', :object => post, :locals => { :hide_comments => true })}
462
491
  ```
463
492
 
464
493
  and then access locals in the sub-template:
@@ -551,15 +580,65 @@ Note that RABL can be nested arbitrarily deep within child nodes to allow for th
551
580
 
552
581
  ### Caching ###
553
582
 
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:
583
+ RABL has built-in caching support for templates leveraging fragment caching strategies. It works with Rails (using `Rails.cache`, or
584
+ `config.cache_engine` if set), and with Sinatra, Padrino and `Rabl::Renderer` when `config.perform_caching` is `true` (using
585
+ `config.cache_engine`; see Configuration above). Inside a Rails process, `Rabl::Renderer` follows `ActionController::Base.perform_caching`.
586
+ Simplest caching usage is:
555
587
 
556
588
  ```ruby
557
589
  # app/views/users/show.json.rabl
558
590
  object @quiz
559
- cache @quiz # key = rabl/quiz/[cache_key]
591
+ cache @quiz # key = rabl-v2/quiz/[cache_key]
560
592
  attribute :title
561
593
  ```
562
594
 
595
+ The full cache key also includes the format and identifies the template (the Rails template digest when available, otherwise a digest of
596
+ 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.
597
+ 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
598
+ data (for example inside a loop), locals, or the current user. That includes the locale and the request variant when `extends` or `partial`
599
+ 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
600
+ those, so add them, e.g. `cache ['v1', root_object, I18n.locale, request.variant]` under Rails.
601
+ 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)
602
+ 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
603
+ called with. On runtimes without `RubyVM::InstructionSequence` (such as JRuby) the start is the line only, so two caching blocks that
604
+ start on the same line need distinct cache keys there.
605
+
606
+ The key covers the template's own source, but not the templates it uses through `extends` or `partial` (under Rails, the template
607
+ digest looks those up as underscore partials, such as `_base`, so it leaves out templates named without the underscore): after editing
608
+ 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
609
+ `cache ['v1', root_object]` to `cache ['v2', root_object]`.
610
+
611
+ In a template that is also rendered for each item (through `extends`, `partial` or `child`), key the cache on `root_object`, the item
612
+ being rendered:
613
+
614
+ ```ruby
615
+ # app/views/topics/base.json.rabl, used by `collection @topics` + `extends 'topics/base'`
616
+ object @topic
617
+ cache ['v1', root_object]
618
+ attribute :id
619
+ ```
620
+
621
+ There, `object @topic` takes the item, but `@topic` itself keeps the controller's value (or nil). When a key has the variable the
622
+ 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
623
+ 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
624
+ 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,
625
+ and a helper such as `current_user` keeps the viewer, so `cache ['v1', current_user]` without `root_object` gives all the items one
626
+ entry per viewer, apart from the nil case above. Include `root_object` whenever the output depends on the item.
627
+
628
+ Under Rails, keys include the Rails template digest when the template is rendered through a controller or view. `Rabl.render` and
629
+ `Rabl::Renderer` used outside a request have no view context, so their keys identify the template differently and don't match the keys of
630
+ a request. To render outside a request with the same keys, for example to warm the cache in a background job, render through the
631
+ controller:
632
+
633
+ ```ruby
634
+ PostsController.render(:show, assigns: { post: post }, formats: [:json])
635
+ # or ApplicationController.render(template: 'posts/show', assigns: { post: post }, formats: [:json])
636
+ ```
637
+
638
+ RABL takes the format from the request, so for another format set the request's `Accept` header as well:
639
+ `PostsController.renderer.new('HTTP_ACCEPT' => 'application/xml').render(:show, assigns: { post: post }, formats: [:xml])`.
640
+ 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.
641
+
563
642
  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
643
 
565
644
  ### Rendering Templates Directly ###
@@ -600,13 +679,14 @@ node(:title) { locals[:custom_title] }
600
679
 
601
680
  ### Content Type Headers ###
602
681
 
603
- Currently in RABL, the content-type of your response is not set automatically. This is because RABL is intended
604
- to work for any Rack-based framework and as agnostic to format as possible.
605
- Check [this issue](https://github.com/nesquena/rabl/issues/185#issuecomment-4501232) for more
606
- details, and if you have any ideas or patches please let me know.
682
+ Under Rails, Rails sets the content type from the template's format: `show.json.rabl` is served as
683
+ `application/json` and `show.xml.rabl` as `application/xml`. A template without a format in its name
684
+ (`show.rabl`) is served as `application/json` whatever it renders, so name templates by format when you
685
+ serve more than JSON.
607
686
 
608
- In the meantime, be sure to set the proper content-types if needed. This is usually pretty simple in both
609
- Rails and Padrino. I recommend a before_filter on that controller or directly specified in an action.
687
+ Outside Rails, RABL doesn't set the content type, as it is intended to work for any Rack-based framework and
688
+ to be as agnostic to format as possible (see [this issue](https://github.com/nesquena/rabl/issues/185#issuecomment-4501232)).
689
+ Set it in your app, e.g. with Sinatra's `content_type :json` in the route or a `before` filter.
610
690
 
611
691
  ## Resources ##
612
692
 
@@ -668,6 +748,8 @@ Check out the [Issues](https://github.com/nesquena/rabl/issues) tab for a full l
668
748
 
669
749
  * Rigorous benchmarking and performance optimizations
670
750
 
751
+ To report a security vulnerability, don't open an issue; see [SECURITY.md](https://github.com/nesquena/rabl/blob/master/SECURITY.md).
752
+
671
753
  ## Authors and Contributors ##
672
754
 
673
755
  Active Maintainers:
data/SECURITY.md ADDED
@@ -0,0 +1,14 @@
1
+ # Security Policy
2
+
3
+ ## Supported versions
4
+
5
+ Security fixes go into the latest release line of rabl. Please check that the problem occurs on the latest release.
6
+
7
+ ## Reporting a vulnerability
8
+
9
+ Please report vulnerabilities privately, through GitHub's private vulnerability reporting: on the repository's
10
+ [Security tab](https://github.com/nesquena/rabl/security), choose "Report a vulnerability". Don't open a public issue or
11
+ pull request for a vulnerability.
12
+
13
+ Include the rabl version, the steps to reproduce the problem and its impact. The discussion continues in the report
14
+ until a fix is released.
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:
@@ -5,29 +7,28 @@
5
7
  # config.cache_engine = AdvancedCacheEngine.new
6
8
  #
7
9
 
8
- class LRU < Hash
9
- attr_accessor :max_size
10
+ module Rabl
11
+ class CacheEngine
12
+ # A Hash holding at most max_size entries, which drops the entries added first
13
+ class LRU < Hash
14
+ attr_accessor :max_size
10
15
 
11
- def initialize
12
- super
13
- self.max_size = 100_000
14
- end
16
+ def initialize
17
+ super
18
+ self.max_size = 100_000
19
+ end
15
20
 
16
- def []= k,v
17
- r = super
18
- limit_size
19
- r
20
- end
21
+ def []= k,v
22
+ r = super
23
+ limit_size
24
+ r
25
+ end
21
26
 
22
- def limit_size
23
- if size > max_size then
24
- delete keys.shift while size > max_size
27
+ def limit_size
28
+ shift while size > max_size
29
+ end
25
30
  end
26
- end
27
- end
28
31
 
29
- module Rabl
30
- class CacheEngine
31
32
  def initialize
32
33
  unless defined?(Rails)
33
34
  @cache = LRU.new
@@ -52,7 +53,7 @@ module Rabl
52
53
  if defined?(Rails)
53
54
  Rails.cache.write(key, value, options)
54
55
  else
55
- @cache[key] = yield
56
+ @cache[key] = value
56
57
  end
57
58
  end
58
59
 
@@ -61,7 +62,7 @@ module Rabl
61
62
  if defined?(Rails)
62
63
  Rails.cache.read_multi(*keys, options)
63
64
  else
64
- keys.inject({}) { |hash, key| hash[key] = nil; hash }
65
+ keys.inject({}) { |hash, key| hash[key] = @cache[key]; hash }
65
66
  end
66
67
  end
67
68
  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