rabl-rails 0.5.5 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ee0aee27278d987f9cca5d2c0a1660ae9cb4e0d6ae51734fa120f09d7c66449c
4
- data.tar.gz: 64d9d559ba02a59de347391031bc16bb21ace05d54d293c3b302125668b76f7e
3
+ metadata.gz: 4fc57874aa491be7a0c8cc778a9d4e0a307740cf4648c3733d5e49f855eb1cc4
4
+ data.tar.gz: d4024078d1b780d66a297fb0a2aef0a7e37e6c4db392fa577afa1f16686c595c
5
5
  SHA512:
6
- metadata.gz: c5b87c14f4c3f356f00c47ea35d3506475bdfd109b4144b60194b151e8b04f180844d1e9ebf5acb6755b834d886bc4a80141a3cba240740c8aeabe2893875d59
7
- data.tar.gz: 9eca1d27951909e3f309240a46d0408de07379c91f21c8366d4620ef0bc4d090b3365e61f942f5a8c076f258472edb86ed74594f0336b0ef847497a81cb4a5c2
6
+ metadata.gz: 6b80cd63b8e17d97833f5696d04e82535c154d69b830ea291c7db344c485dead0ffb355cb75a83b1d155b992801f67815a142aa6da2657a4f7ce65820dd7d929
7
+ data.tar.gz: 05f604364890ce38ab76e311d76c51ce328b2f5bcf1c389b2837f95f17b89c31b7529dcadd42e52d745d11ce1be2e74f8a5a83d2a13e3e9478185a69c5e47d23
data/.travis.yml CHANGED
@@ -4,13 +4,13 @@ dist: trusty
4
4
  env:
5
5
  - "RAILS_VERSION=4.2.6"
6
6
  - "RAILS_VERSION=5.2.0"
7
+ - "RAILS_VERSION=6.1.0"
7
8
  rvm:
8
9
  - 2.5.3
9
10
  - 2.6.0
11
+ - 2.7.2
10
12
  - jruby
11
13
  before_install:
12
14
  - gem update bundler
13
15
  matrix:
14
16
  fast_finish: true
15
- allow_failures:
16
- - env: RAILS_VERSION=master
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 0.6.1
4
+ * Fix bug when template contains double quotes
5
+
6
+ ## 0.6.0 (yanked)
7
+ * Remove Rails 6+ warnings
8
+ * Uniformize node options
9
+ * Refresh README.md
10
+
3
11
  ## 0.5.5
4
12
  * Add `lookup` node
5
13
 
data/README.md CHANGED
@@ -1,10 +1,10 @@
1
- # RABL for Rails [![Build Status](https://travis-ci.org/ccocchi/rabl-rails.png?branch=master)](https://travis-ci.org/ccocchi/rabl-rails)
1
+ # RABL for Rails [![Build Status](https://travis-ci.org/ccocchi/rabl-rails.svg?branch=master)](https://travis-ci.org/ccocchi/rabl-rails)
2
2
 
3
- RABL (Ruby API Builder Language) is a ruby templating system for rendering resources in different format (JSON, XML, BSON, ...). You can find documentation [here](http://github.com/nesquena/rabl).
3
+ `rabl-rails` is a ruby templating system for rendering your objects in different format (JSON, XML, PLIST).
4
4
 
5
- rabl-rails is **faster** and uses **less memory** than the standard rabl gem while letting you access the same features. There are some slight changes to do on your templates to get this gem to work but it should't take you more than 5 minutes.
5
+ This gem aims for speed and little memory footprint while letting you build complex response with a very intuitive DSL.
6
6
 
7
- rabl-rails only targets **Rails 4.2+ application** and is compatible with mri 2.2+, jRuby and rubinius.
7
+ `rabl-rails` targets **Rails 4.2/5/6 application** and have been testing with MRI and jRuby.
8
8
 
9
9
  ## Installation
10
10
 
@@ -17,23 +17,18 @@ gem install rabl-rails
17
17
  or add directly to your `Gemfile`
18
18
 
19
19
  ```
20
- gem 'rabl-rails'
20
+ gem 'rabl-rails', '~> 0.6.0'
21
21
  ```
22
22
 
23
- And that's it !
24
-
25
23
  ## Overview
26
24
 
27
- Once you have installed rabl-rails, you can directly used RABL-rails templates to render your resources without changing anything to you controller. As example,
28
- assuming you have a `Post` model filled with blog posts, and a `PostController` that look like this :
25
+ The gem enables you to build responses using views like you would using HTML/erb/haml.
26
+ As example, assuming you have a `Post` model filled with blog posts, and a `PostController` that look like this:
29
27
 
30
28
  ```ruby
31
29
  class PostController < ApplicationController
32
- respond_to :html, :json, :xml
33
-
34
30
  def index
35
31
  @posts = Post.order('created_at DESC')
36
- respond_with(@posts)
37
32
  end
38
33
  end
39
34
  ```
@@ -43,6 +38,7 @@ You can create the following RABL-rails template to express the API output of `@
43
38
  ```ruby
44
39
  # app/views/post/index.rabl
45
40
  collection :@posts
41
+
46
42
  attributes :id, :title, :subject
47
43
  child(:user) { attributes :full_name }
48
44
  node(:read) { |post| post.read_by?(@user) }
@@ -58,15 +54,13 @@ This would output the following JSON when visiting `http://localhost:3000/posts.
58
54
  }]
59
55
  ```
60
56
 
61
- That's a basic overview but there is a lot more to see such as partials, inheritance or fragment caching.
62
-
63
57
  ## How it works
64
58
 
65
- As opposed to standard RABL gem, this gem separate compiling (a.k.a transforming a RABL-rails template into a Ruby hash) and the actual rendering of the object or collection. This allow to only compile the template once and only Ruby hashes.
59
+ This gem separates compiling, ie. transforming a RABL-rails template into a Ruby hash, and the actual rendering of the object or collection. This allows to only compile the template once (when template caching is enabled) which is the slow part, and only use hashes during rendering.
66
60
 
67
- The fact of compiling the template outside of any rendering context prevent us to use any instances variables (with the exception of node) in the template because they are rendering objects. So instead, you'll have to use symbols of these variables.For example, to render the collection `@posts` inside your `PostController`, you need to use `:@posts` inside of the template.
61
+ The drawback of compiling the template outside of any rendering context is that we can't access instance variables like usual. Instead, you'll mostly use symbols representing your variables and the gem will retrieve them when needed.
68
62
 
69
- The only places where you can actually used instance variables are into Proc (or lambda) or into custom node (because they are treated as Proc).
63
+ There are places where the gem allows for "dynamic code" -- code that is evaluated at each rendering, such as within `node` or `condition` blocks.
70
64
 
71
65
  ```ruby
72
66
  # We reference the @posts varibles that will be used at rendering time
@@ -79,7 +73,7 @@ node(:read) { |post| post.read_by?(@user) }
79
73
 
80
74
  The same rule applies for view helpers such as `current_user`
81
75
 
82
- After the template is compiled into a hash, Rabl-rails will use a renderer to do the actual output. Actually, only JSON and XML formats are supported.
76
+ After the template is compiled into a hash, `rabl-rails` will use a renderer to create the actual output. Currently, JSON, XML and PList formats are supported.
83
77
 
84
78
  ## Configuration
85
79
 
@@ -87,6 +81,7 @@ RablRails works out of the box, with default options and fastest engine availabl
87
81
 
88
82
  ```ruby
89
83
  # config/initializers/rabl_rails.rb
84
+
90
85
  RablRails.configure do |config|
91
86
  # These are the default
92
87
  # config.cache_templates = true
@@ -125,7 +120,7 @@ collection :@posts => :articles
125
120
  # => { "articles" : [{...}, {...}] }
126
121
  ```
127
122
 
128
- There are rares cases when the template doesn't map directly to any object. In these cases, you can set data to false or skip data declaration altogether.
123
+ There are rares cases when the template doesn't map directly to any object. In these cases, you can set data to false.
129
124
 
130
125
  ```ruby
131
126
  object false
@@ -133,27 +128,15 @@ node(:some_count) { |_| @user.posts.count }
133
128
  child(:@user) { attribute :name }
134
129
  ```
135
130
 
136
- If you use gem like *decent_exposure* or *focused_controller*, you can use your variable directly without the leading `@`
131
+ If you use gems like *decent_exposure* or *focused_controller*, you can use your variable directly without the leading `@`
137
132
 
138
133
  ```ruby
139
134
  object :object_exposed
140
135
  ```
141
136
 
142
- You can even skip data declaration at all. If you used `respond_with`, rabl-rails will render the data you passed to it.
143
- As there is no name, you can set a root via the `root` macro. This allow you to use your template without caring about variables passed to it.
144
-
145
- ```ruby
146
- # in controller
147
- respond_with(@post)
148
-
149
- # in rabl-rails template
150
- root :article
151
- attribute :title
152
- ```
153
-
154
137
  ### Attributes / Methods
155
138
 
156
- Basic usage is to declared attributes to include in the response. These can be database attributes or any instance method.
139
+ Adds a new field to the response object, calling the method on the object being rendered. Methods called this way should return natives types from the format you're using (such as `String`, `integer`, etc for JSON). For more complex objects, see `child` nodes.
157
140
 
158
141
  ```ruby
159
142
  attributes :id, :title, :to_s
@@ -162,64 +145,91 @@ attributes :id, :title, :to_s
162
145
  You can aliases these attributes in your response
163
146
 
164
147
  ```ruby
165
- attributes title: :foo, to_s: :bar
166
- # => { "foo" : <title value>, "bar" : <to_s value> }
148
+ attributes :my_custom_method, as: :title
149
+ # => { "title" : <result of my_custom_method> }
167
150
  ```
168
151
 
169
- or show attributes only if a condition is true
152
+ or show attributes based on a condition. The currently rendered object is given to the `proc` condition.
153
+
170
154
  ```ruby
171
155
  attributes :published_at, :anchor, if: ->(post) { post.published? }
172
156
  ```
173
157
 
174
158
  ### Child nodes
175
159
 
176
- You can include informations from data associated with the parent model or arbitrary data. These informations can be grouped under a node or directly merged into current node.
160
+ Changes the object being rendered for the duration of the block. Depending on if you use `node` or `glue`, the result will be added as a new field or merged respectively.
161
+
162
+ Data passed can be a method or a reference to an instance variable.
177
163
 
178
- For example if you have a `Post` model that belongs to a `User`
164
+ For example if you have a `Post` model that belongs to a `User` and want to add the user's name to your response.
179
165
 
180
166
  ```ruby
181
167
  object :@post
182
- child(user: :author) do
168
+
169
+ child(:user, as: :author) do
183
170
  attributes :name
184
171
  end
185
- # => { "post" : { "author" : { "name" : "John D." } } }
172
+ # => { "post": { "author" : { "name" : "John D." } } }
186
173
  ```
187
174
 
188
- You can also use arbitrary data source with child nodes
175
+ If instead of having an `author` node in your response you wanted the name at the root level, you can use `glue`:
176
+
189
177
  ```ruby
190
- child(:@users) do
191
- attributes :id, :name
178
+ object :@post
179
+
180
+ glue(:user) do
181
+ attributes :name, as: :author_name
192
182
  end
183
+ # => { "post": { "author_name" : "John D." } }
193
184
  ```
194
185
 
195
- If you want to merge directly into current node, you can use the `glue` keywork
186
+ Arbitrary data source can also be passed:
196
187
 
197
188
  ```ruby
198
- attribute :title
199
- glue(:user) do
200
- attributes :name => :author_name
189
+ # in your controller
190
+ # @custom_data = [...]
191
+
192
+ # in the view
193
+ child(:@custom_data) do
194
+ attributes :id, :name
201
195
  end
202
- # => { "post" : { "title" : "Foo", "author_name" : "John D." } }
196
+ # => { "custom_data": [...] }
203
197
  ```
204
198
 
205
- ### Custom nodes
199
+ ### Constants
206
200
 
207
- You can create custom node in your response, based on the result of a given block
201
+ Adds a new field to the response using an immutable value.
208
202
 
209
203
  ```ruby
210
- object :@user
211
- node(:full_name) { |u| u.first_name + " " + u.last_name }
212
- # => { "user" : { "full_name" : "John Doe" } }
204
+ const(:api_version, API::VERSION)
205
+ const(:locale, 'fr_FR')
213
206
  ```
214
207
 
215
- or with an assigned constant
208
+ ### Lookups
209
+
210
+ Adds a new field to the response, using rendered resource's id by default or any method to fetch a value from the given hash variable.
216
211
 
217
212
  ```ruby
218
- const(:api_version, API::VERSION)
219
- const(:locale, 'fr_FR')
213
+ collection :@posts
214
+
215
+ lookup(:comments_count, :@comments_count, field: :uuid, cast: false)
216
+ # => [{ "comments_count": 3 }, { "comments_count": 6 }]
217
+ ```
218
+
219
+ In the example above, for each post it will fetch the value from `@comments_count` using the post's `uuid` as key. When the `cast` value is set to `true` (it is `false` by default), the value will be casted to a boolean using `!!`.
220
+
221
+
222
+ ### Custom nodes
223
+
224
+ Adds a new field to the response with block's result as value.
225
+
226
+ ```ruby
227
+ object :@user
228
+ node(:full_name) { |u| u.first_name + " " + u.last_name }
229
+ # => { "user" : { "full_name" : "John Doe" } }
220
230
  ```
221
231
 
222
- You can add condition on your custom nodes (if the condition is evaluated to false, the node will not be included).
232
+ You can add condition on your custom nodes. If the condition evaluates to a falsey value, the node will not added to the response at all.
223
233
 
224
234
  ```ruby
225
235
  node(:email, if: ->(u) { u.valid_email? }) do |u|
@@ -227,13 +237,13 @@ node(:email, if: ->(u) { u.valid_email? }) do |u|
227
237
  end
228
238
  ```
229
239
 
230
- Nodes are evaluated at the rendering time, so you can use any instance variables or view helpers inside them
240
+ Nodes are evaluated at rendering time, so you can use any instance variables or view helpers within them
231
241
 
232
242
  ```ruby
233
243
  node(:url) { |post| post_url(post) }
234
244
  ```
235
245
 
236
- If you want to include directly the result into the current node, use the `merge` keyword (result returned from the block should be a hash)
246
+ If the result of the block is a Hash, it can be directly merge into the response using `merge` instead of `node`
237
247
 
238
248
  ```ruby
239
249
  object :@user
@@ -241,33 +251,42 @@ merge { |u| { name: u.first_name + " " + u.last_name } }
241
251
  # => { "user" : { "name" : "John Doe" } }
242
252
  ```
243
253
 
244
- Custom nodes are really usefull to create flexible representations of your resources.
245
-
246
254
  ### Extends & Partials
247
255
 
248
256
  Often objects have a basic representation that is shared accross different views and enriched according to it. To avoid code redundancy you can extend your template from any other RABL template.
249
257
 
250
258
  ```ruby
251
- # app/views/users/base.json.rabl
259
+ # app/views/shared/_user.rabl
252
260
  attributes :id, :name
253
261
 
254
- # app/views/users/private.json.rabl
262
+ # app/views/users/show.rabl
263
+ object :@user
264
+
265
+ extends('shared/_user')
255
266
  attributes :super_secret_attribute
256
267
 
257
- extends 'users/base'
258
- # or using partial instead of extends
259
- # merge { |u| partial('users/base', object: u) }
268
+ #=> { "id": 1, "name": "John", "super_secret_attribute": "Doe" }
269
+ ```
270
+
271
+ When used with child node, if they are the only thing added you can instead use the `partial` option directly.
272
+
273
+ ```ruby
274
+ child(:user, partial: 'shared/_user')
275
+
276
+ # is equivalent to
277
+
278
+ child(:user) do
279
+ extends('shared/_user')
280
+ end
260
281
  ```
261
282
 
262
- You can also extends template in child nodes using `partial` option (this is the same as using `extends` in the child block)
283
+ Extends can be used dynamically using rendered object and lambdas.
263
284
 
264
285
  ```ruby
265
- collection @posts
266
- attribute :title
267
- child(:user, partial: 'users/base')
286
+ extends ->(user) { "shared/_#{user.client_type}_infos" }
268
287
  ```
269
288
 
270
- Partials can also be used inside custom nodes. When using partial this way, you MUST declare the object associated to the partial
289
+ Partials can also be used inside custom nodes. When using partial this way, you MUST declare the `object` associated to the partial
271
290
 
272
291
  ```ruby
273
292
  node(:location) do |user|
@@ -275,28 +294,33 @@ node(:location) do |user|
275
294
  end
276
295
  ```
277
296
 
278
- When used within `node`, partials can take locals variables that can be accessed in the included template.
297
+ When used this way, partials can take locals variables that can be accessed in the included template.
298
+
279
299
  ```ruby
280
- # base.json.rabl
300
+ # _credit_card.rabl
281
301
  node(:credit_card, if: ->(u) { locals[:display_credit_card] }) do |user|
282
302
  user.credit_card_info
283
303
  end
284
304
 
285
305
  # user.json.rabl
286
- merge { |u| partial('users/base', object: u, locals: { display_credit_card: true }) }
306
+ merge { |u| partial('_credit_card', object: u, locals: { display_credit_card: true }) }
287
307
  ```
288
308
 
289
- ### Nesting
309
+ ### Putting it all together
290
310
 
291
- Rabl allow you to define easily your templates, even with hierarchy of 2 or 3 levels. Let's suppose your have a `thread` model that has many `posts` and that each post has many `comments`. We can display a full thread in a few lines
311
+ `rabl-rails` allows you to format your responses easily, from simple objects to hierarchy of 2 or 3 levels.
292
312
 
293
313
  ```ruby
294
314
  object :@thread
295
- attribute :caption
296
- child :posts do
297
- attribute :title
298
- child :comments do
299
- extends 'comments/base'
315
+
316
+ attribute :caption, as: :title
317
+
318
+ child(:@sorted_posts, as: :posts) do
319
+ attributes :title, :slug
320
+
321
+ child :comments do
322
+ extends 'shared/_comment'
323
+ lookup(:upvotes, :@upvotes_per_comment)
300
324
  end
301
325
  end
302
326
  ```
@@ -328,4 +352,4 @@ Want to make another change ? Just fork and contribute, any help is very much ap
328
352
 
329
353
  ## Copyright
330
354
 
331
- Copyright © 2012-2017 Christopher Cocchi-Perrier. See [MIT-LICENSE](http://github.com/ccocchi/rabl-rails/blob/master/MIT-LICENSE) for details.
355
+ Copyright © 2012-2020 Christopher Cocchi-Perrier. See [MIT-LICENSE](http://github.com/ccocchi/rabl-rails/blob/master/MIT-LICENSE) for details.
@@ -69,7 +69,8 @@ module RablRails
69
69
  #
70
70
  def child(name_or_data, options = {})
71
71
  data, name = extract_data_and_name(name_or_data)
72
- name = options[:root] if options.has_key? :root
72
+ name = options[:root] if options.has_key? :root
73
+ name = options[:as] if options.has_key? :as
73
74
  template = partial_or_block(data, options) { yield }
74
75
  @template.add_node Nodes::Child.new(name, template)
75
76
  end
@@ -33,9 +33,9 @@ module RablRails
33
33
  def result_flags
34
34
  @result_flags ||= begin
35
35
  result = 0
36
- result |= 0b01 if @replace_nil_values_with_empty_strings
37
- result |= 0b10 if @replace_empty_string_values_with_nil
38
- result |= 0b100 if @exclude_nil_values
36
+ result |= 0b001 if @replace_nil_values_with_empty_strings
37
+ result |= 0b010 if @replace_empty_string_values_with_nil
38
+ result |= 0b100 if @exclude_nil_values
39
39
  result
40
40
  end
41
41
  end
@@ -3,12 +3,12 @@ require 'active_support/core_ext/class/attribute'
3
3
  module RablRails
4
4
  module Handlers
5
5
  class Rabl
6
- def self.call(template)
6
+ def self.call(template, source = nil)
7
7
  %{
8
8
  RablRails::Library.instance.
9
- get_rendered_template(#{template.source.inspect}, self, local_assigns)
9
+ get_rendered_template(#{(source || template.source).inspect}, self, local_assigns)
10
10
  }
11
11
  end
12
12
  end
13
13
  end
14
- end
14
+ end
@@ -25,7 +25,7 @@ module RablRails
25
25
 
26
26
  def get_rendered_template(source, view, locals = nil)
27
27
  compiled_template = compile_template_from_source(source, view)
28
- format = view.lookup_context.rendered_format || :json
28
+ format = view.lookup_context.formats.first || :json
29
29
  raise UnknownFormat, "#{format} is not supported in rabl-rails" unless RENDERER_MAP.key?(format)
30
30
  RENDERER_MAP[format].render(compiled_template, view, locals)
31
31
  end
@@ -1,3 +1,3 @@
1
1
  module RablRails
2
- VERSION = '0.5.5'
2
+ VERSION = '0.6.1'
3
3
  end
@@ -57,7 +57,7 @@ module Visitors
57
57
  end
58
58
 
59
59
  def visit_Lookup n
60
- object = object_from_data(nil, n)
60
+ object = object_from_data(_resource, n)
61
61
  key = _resource.public_send(n.field)
62
62
  value = object[key]
63
63
  value = !!value if n.cast_to_boolean?
data/rabl-rails.gemspec CHANGED
@@ -14,9 +14,7 @@ Gem::Specification.new do |s|
14
14
 
15
15
  s.required_ruby_version = '>= 2.2.0'
16
16
 
17
- s.files = `git ls-files -z`.split("\x0").reject do |file|
18
- file.start_with?('test/')
19
- end
17
+ s.files = `git ls-files`.split("\n")
20
18
  s.test_files = `git ls-files -- test/*`.split("\n")
21
19
  s.require_paths = ["lib"]
22
20
 
data/test/helper.rb CHANGED
@@ -49,8 +49,8 @@ class Context
49
49
  @format = format
50
50
  end
51
51
 
52
- def rendered_format
53
- @format.to_sym
52
+ def formats
53
+ [@format]
54
54
  end
55
55
  end
56
56
 
@@ -11,13 +11,20 @@ class TestCompiler < Minitest::Test
11
11
  }
12
12
  end
13
13
 
14
+ @@view_class = if ActionView::Base.respond_to?(:with_empty_template_cache)
15
+ # From Rails 6.1
16
+ ActionView::Base.with_empty_template_cache
17
+ else
18
+ ActionView::Base
19
+ end
20
+
14
21
  describe 'compiler' do
15
22
  def extract_attributes(nodes)
16
23
  nodes.map(&:hash)
17
24
  end
18
25
 
19
26
  before do
20
- @view = ActionView::Base.new(@@tmp_path, {})
27
+ @view = @@view_class.new(ActionView::LookupContext.new(@@tmp_path), {}, nil)
21
28
  @compiler = RablRails::Compiler.new(@view)
22
29
  end
23
30
 
@@ -156,6 +163,14 @@ class TestCompiler < Minitest::Test
156
163
  assert_equal(:user, child_node.data)
157
164
  end
158
165
 
166
+ it "compiles child with root name defined with `as` option" do
167
+ t = @compiler.compile_source(%{ child(:user, as: :author) do attributes :foo end })
168
+ child_node = t.nodes.first
169
+
170
+ assert_equal(:author, child_node.name)
171
+ assert_equal(:user, child_node.data)
172
+ end
173
+
159
174
  it "compiles child with arbitrary source" do
160
175
  t = @compiler.compile_source(%{ child :@user => :author do attribute :name end })
161
176
  child_node = t.nodes.first
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rabl-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.5
4
+ version: 0.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christopher Cocchi-Perrier
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-02-19 00:00:00.000000000 Z
11
+ date: 2021-03-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -158,8 +158,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
158
158
  - !ruby/object:Gem::Version
159
159
  version: '0'
160
160
  requirements: []
161
- rubyforge_project:
162
- rubygems_version: 2.7.6
161
+ rubygems_version: 3.1.4
163
162
  signing_key:
164
163
  specification_version: 4
165
164
  summary: Fast Rails 4+ templating system with JSON, XML and PList support