rabl-rails 0.5.2 → 0.6.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.travis.yml +6 -11
- data/CHANGELOG.md +20 -0
- data/Gemfile +1 -1
- data/README.md +122 -81
- data/lib/rabl-rails/compiler.rb +37 -5
- data/lib/rabl-rails/configuration.rb +3 -3
- data/lib/rabl-rails/handler.rb +3 -3
- data/lib/rabl-rails/library.rb +1 -1
- data/lib/rabl-rails/nodes.rb +3 -0
- data/lib/rabl-rails/nodes/fetch.rb +12 -0
- data/lib/rabl-rails/nodes/lookup.rb +23 -0
- data/lib/rabl-rails/nodes/polymorphic.rb +11 -0
- data/lib/rabl-rails/version.rb +1 -1
- data/lib/rabl-rails/visitors/to_hash.rb +27 -0
- data/rabl-rails.gemspec +1 -1
- data/test/helper.rb +2 -2
- data/test/test_compiler.rb +58 -3
- data/test/test_hash_visitor.rb +39 -0
- metadata +14 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ac2d0bc8954f6b27e54002015d71ed0d00f4264c8c1d57411dc789216c627e44
|
4
|
+
data.tar.gz: 98757a9fe142bafb324782725e56e85e3ba83d5a018e76a75cda00c37c73ebf4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d55bc5c73b2add30025cfb41a1735548a236928ca0372c4cbc2aeec731cfbb1db60376c9498570b0ea0a40e9e4c6f79caf32cc474698ad48163845fed5da5d3a
|
7
|
+
data.tar.gz: 4bb4b91c9f4045f94dca99ec126e8fb64389736f675af3ec85466f6c92b75844e1ddc30a8b61633ddce8531ed68d2a7e3b0abf0072a61b48c803f847c6084d28
|
data/.travis.yml
CHANGED
@@ -3,19 +3,14 @@ cache: bundler
|
|
3
3
|
dist: trusty
|
4
4
|
env:
|
5
5
|
- "RAILS_VERSION=4.2.6"
|
6
|
-
- "RAILS_VERSION=
|
6
|
+
- "RAILS_VERSION=5.2.0"
|
7
|
+
- "RAILS_VERSION=6.1.0"
|
7
8
|
rvm:
|
8
|
-
- 2.
|
9
|
-
- 2.
|
10
|
-
- 2.
|
11
|
-
- jruby
|
12
|
-
- rbx-3.71
|
9
|
+
- 2.5.3
|
10
|
+
- 2.6.0
|
11
|
+
- 2.7.2
|
12
|
+
- jruby
|
13
13
|
before_install:
|
14
14
|
- gem update bundler
|
15
15
|
matrix:
|
16
16
|
fast_finish: true
|
17
|
-
allow_failures:
|
18
|
-
- env: RAILS_VERSION=master
|
19
|
-
exclude:
|
20
|
-
- rvm: 2.1.8
|
21
|
-
env: "RAILS_VERSION=master"
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,25 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## 0.6.2
|
4
|
+
* Add `fetch` node
|
5
|
+
|
6
|
+
## 0.6.1
|
7
|
+
* Fix bug when template contains double quotes
|
8
|
+
|
9
|
+
## 0.6.0 (yanked)
|
10
|
+
* Remove Rails 6+ warnings
|
11
|
+
* Uniformize node options
|
12
|
+
* Refresh README.md
|
13
|
+
|
14
|
+
## 0.5.5
|
15
|
+
* Add `lookup` node
|
16
|
+
|
17
|
+
## 0.5.4
|
18
|
+
* Relax concurrent-ruby version dependency (javierjulio)
|
19
|
+
|
20
|
+
## 0.5.3
|
21
|
+
* Allow `extends` to accept lambdas
|
22
|
+
|
3
23
|
## 0.5.2
|
4
24
|
* Add `const` node
|
5
25
|
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
# RABL for Rails [![Build Status](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
|
-
|
3
|
+
`rabl-rails` is a ruby templating system for rendering your objects in different format (JSON, XML, PLIST).
|
4
4
|
|
5
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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,
|
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
|
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
|
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
|
-
|
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,108 @@ attributes :id, :title, :to_s
|
|
162
145
|
You can aliases these attributes in your response
|
163
146
|
|
164
147
|
```ruby
|
165
|
-
attributes
|
166
|
-
# => { "
|
148
|
+
attributes :my_custom_method, as: :title
|
149
|
+
# => { "title" : <result of my_custom_method> }
|
167
150
|
```
|
168
151
|
|
169
|
-
or show attributes
|
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
|
-
|
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
|
-
|
168
|
+
|
169
|
+
child(:user, as: :author) do
|
183
170
|
attributes :name
|
184
171
|
end
|
185
|
-
# => { "post"
|
172
|
+
# => { "post": { "author" : { "name" : "John D." } } }
|
186
173
|
```
|
187
174
|
|
188
|
-
|
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
|
-
|
191
|
-
|
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
|
-
|
186
|
+
Arbitrary data source can also be passed:
|
196
187
|
|
197
188
|
```ruby
|
198
|
-
|
199
|
-
|
200
|
-
|
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
|
-
# => { "
|
196
|
+
# => { "custom_data": [...] }
|
203
197
|
```
|
204
198
|
|
205
|
-
|
206
|
-
|
207
|
-
You can create custom node in your response, based on the result of a given block
|
199
|
+
You can use a Hash-like data source, as long as keys match a method or attribute of your main resource, using the `fetch` keyword:
|
208
200
|
|
209
201
|
```ruby
|
210
|
-
|
211
|
-
|
212
|
-
|
202
|
+
# assuming you have something similar in your controller
|
203
|
+
# @users_hash = { 1 => User.new(pseudo: 'Batman') }
|
204
|
+
|
205
|
+
# in the view
|
206
|
+
object :@post
|
207
|
+
|
208
|
+
fetch(:@users_hash, as: :user, field: :user_id) do
|
209
|
+
attributes :pseudo
|
210
|
+
end
|
211
|
+
# => { user: { pseudo: 'Batman' } }
|
213
212
|
```
|
214
213
|
|
215
|
-
|
214
|
+
This comes very handy when adding attributes from external queries not really bound to a relation, like statistics.
|
215
|
+
|
216
|
+
### Constants
|
217
|
+
|
218
|
+
Adds a new field to the response using an immutable value.
|
216
219
|
|
217
220
|
```ruby
|
218
221
|
const(:api_version, API::VERSION)
|
219
222
|
const(:locale, 'fr_FR')
|
220
223
|
```
|
221
224
|
|
222
|
-
|
225
|
+
### Lookups
|
226
|
+
|
227
|
+
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.
|
228
|
+
|
229
|
+
```ruby
|
230
|
+
collection :@posts
|
231
|
+
|
232
|
+
lookup(:comments_count, :@comments_count, field: :uuid, cast: false)
|
233
|
+
# => [{ "comments_count": 3 }, { "comments_count": 6 }]
|
234
|
+
```
|
235
|
+
|
236
|
+
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 `!!`.
|
237
|
+
|
238
|
+
|
239
|
+
### Custom nodes
|
240
|
+
|
241
|
+
Adds a new field to the response with block's result as value.
|
242
|
+
|
243
|
+
```ruby
|
244
|
+
object :@user
|
245
|
+
node(:full_name) { |u| u.first_name + " " + u.last_name }
|
246
|
+
# => { "user" : { "full_name" : "John Doe" } }
|
247
|
+
```
|
248
|
+
|
249
|
+
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
250
|
|
224
251
|
```ruby
|
225
252
|
node(:email, if: ->(u) { u.valid_email? }) do |u|
|
@@ -227,13 +254,13 @@ node(:email, if: ->(u) { u.valid_email? }) do |u|
|
|
227
254
|
end
|
228
255
|
```
|
229
256
|
|
230
|
-
Nodes are evaluated at
|
257
|
+
Nodes are evaluated at rendering time, so you can use any instance variables or view helpers within them
|
231
258
|
|
232
259
|
```ruby
|
233
260
|
node(:url) { |post| post_url(post) }
|
234
261
|
```
|
235
262
|
|
236
|
-
If
|
263
|
+
If the result of the block is a Hash, it can be directly merge into the response using `merge` instead of `node`
|
237
264
|
|
238
265
|
```ruby
|
239
266
|
object :@user
|
@@ -241,33 +268,42 @@ merge { |u| { name: u.first_name + " " + u.last_name } }
|
|
241
268
|
# => { "user" : { "name" : "John Doe" } }
|
242
269
|
```
|
243
270
|
|
244
|
-
Custom nodes are really usefull to create flexible representations of your resources.
|
245
|
-
|
246
271
|
### Extends & Partials
|
247
272
|
|
248
273
|
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
274
|
|
250
275
|
```ruby
|
251
|
-
# app/views/
|
276
|
+
# app/views/shared/_user.rabl
|
252
277
|
attributes :id, :name
|
253
278
|
|
254
|
-
# app/views/users/
|
279
|
+
# app/views/users/show.rabl
|
280
|
+
object :@user
|
281
|
+
|
282
|
+
extends('shared/_user')
|
255
283
|
attributes :super_secret_attribute
|
256
284
|
|
257
|
-
|
258
|
-
|
259
|
-
|
285
|
+
#=> { "id": 1, "name": "John", "super_secret_attribute": "Doe" }
|
286
|
+
```
|
287
|
+
|
288
|
+
When used with child node, if they are the only thing added you can instead use the `partial` option directly.
|
289
|
+
|
290
|
+
```ruby
|
291
|
+
child(:user, partial: 'shared/_user')
|
292
|
+
|
293
|
+
# is equivalent to
|
294
|
+
|
295
|
+
child(:user) do
|
296
|
+
extends('shared/_user')
|
297
|
+
end
|
260
298
|
```
|
261
299
|
|
262
|
-
|
300
|
+
Extends can be used dynamically using rendered object and lambdas.
|
263
301
|
|
264
302
|
```ruby
|
265
|
-
|
266
|
-
attribute :title
|
267
|
-
child(:user, partial: 'users/base')
|
303
|
+
extends ->(user) { "shared/_#{user.client_type}_infos" }
|
268
304
|
```
|
269
305
|
|
270
|
-
Partials can also be used inside custom nodes. When using partial this way, you MUST declare the object associated to the partial
|
306
|
+
Partials can also be used inside custom nodes. When using partial this way, you MUST declare the `object` associated to the partial
|
271
307
|
|
272
308
|
```ruby
|
273
309
|
node(:location) do |user|
|
@@ -275,28 +311,33 @@ node(:location) do |user|
|
|
275
311
|
end
|
276
312
|
```
|
277
313
|
|
278
|
-
When used
|
314
|
+
When used this way, partials can take locals variables that can be accessed in the included template.
|
315
|
+
|
279
316
|
```ruby
|
280
|
-
#
|
317
|
+
# _credit_card.rabl
|
281
318
|
node(:credit_card, if: ->(u) { locals[:display_credit_card] }) do |user|
|
282
319
|
user.credit_card_info
|
283
320
|
end
|
284
321
|
|
285
322
|
# user.json.rabl
|
286
|
-
merge { |u| partial('
|
323
|
+
merge { |u| partial('_credit_card', object: u, locals: { display_credit_card: true }) }
|
287
324
|
```
|
288
325
|
|
289
|
-
###
|
326
|
+
### Putting it all together
|
290
327
|
|
291
|
-
|
328
|
+
`rabl-rails` allows you to format your responses easily, from simple objects to hierarchy of 2 or 3 levels.
|
292
329
|
|
293
330
|
```ruby
|
294
331
|
object :@thread
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
332
|
+
|
333
|
+
attribute :caption, as: :title
|
334
|
+
|
335
|
+
child(:@sorted_posts, as: :posts) do
|
336
|
+
attributes :title, :slug
|
337
|
+
|
338
|
+
child :comments do
|
339
|
+
extends 'shared/_comment'
|
340
|
+
lookup(:upvotes, :@upvotes_per_comment)
|
300
341
|
end
|
301
342
|
end
|
302
343
|
```
|
@@ -328,4 +369,4 @@ Want to make another change ? Just fork and contribute, any help is very much ap
|
|
328
369
|
|
329
370
|
## Copyright
|
330
371
|
|
331
|
-
Copyright © 2012-
|
372
|
+
Copyright © 2012-2020 Christopher Cocchi-Perrier. See [MIT-LICENSE](http://github.com/ccocchi/rabl-rails/blob/master/MIT-LICENSE) for details.
|
data/lib/rabl-rails/compiler.rb
CHANGED
@@ -62,14 +62,15 @@ module RablRails
|
|
62
62
|
# Creates a child node to be included in the output.
|
63
63
|
# name_or data can be an object or collection or a method to call on the data. It
|
64
64
|
# accepts :root and :partial options.
|
65
|
-
#
|
65
|
+
# Note that partial and blocks are not compatible
|
66
66
|
# Example:
|
67
67
|
# child(:@posts, :root => :posts) { attribute :id }
|
68
68
|
# child(:posts, :partial => 'posts/base')
|
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]
|
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
|
@@ -84,6 +85,21 @@ module RablRails
|
|
84
85
|
@template.add_node Nodes::Glue.new(template)
|
85
86
|
end
|
86
87
|
|
88
|
+
#
|
89
|
+
# Creates a node to be added to the output by fetching an object using
|
90
|
+
# current resource's field as key to the data, and appliying given
|
91
|
+
# template to said object
|
92
|
+
# Example:
|
93
|
+
# fetch(:@stats, field: :id) { attributes :total }
|
94
|
+
#
|
95
|
+
def fetch(name_or_data, options = {})
|
96
|
+
data, name = extract_data_and_name(name_or_data)
|
97
|
+
name = options[:as] if options.key?(:as)
|
98
|
+
field = options.fetch(:field, :id)
|
99
|
+
template = partial_or_block(data, options) { yield }
|
100
|
+
@template.add_node Nodes::Fetch.new(name, template, field)
|
101
|
+
end
|
102
|
+
|
87
103
|
#
|
88
104
|
# Creates an arbitrary node in the json output.
|
89
105
|
# It accepts :if option to create conditionnal nodes. The current data will
|
@@ -107,6 +123,16 @@ module RablRails
|
|
107
123
|
@template.add_node Nodes::Const.new(name, value)
|
108
124
|
end
|
109
125
|
|
126
|
+
#
|
127
|
+
# Create a node `name` by looking the current resource being rendered in the
|
128
|
+
# `object` hash using, by default, the resource's id.
|
129
|
+
# Example:
|
130
|
+
# lookup(:favorite, :@user_favorites, cast: true)
|
131
|
+
#
|
132
|
+
def lookup(name, object, field: :id, cast: false)
|
133
|
+
@template.add_node Nodes::Lookup.new(name, object, field, cast)
|
134
|
+
end
|
135
|
+
|
110
136
|
#
|
111
137
|
# Merge arbitrary data into json output. Given block should
|
112
138
|
# return a hash.
|
@@ -122,10 +148,16 @@ module RablRails
|
|
122
148
|
# Extends an existing rabl template
|
123
149
|
# Example:
|
124
150
|
# extends 'users/base'
|
151
|
+
# extends ->(item) { "v1/#{item.class}/_core" }
|
125
152
|
# extends 'posts/base', locals: { hide_comments: true }
|
126
153
|
#
|
127
|
-
def extends(
|
128
|
-
|
154
|
+
def extends(path_or_lambda, options = nil)
|
155
|
+
if path_or_lambda.is_a?(Proc)
|
156
|
+
@template.add_node Nodes::Polymorphic.new(path_or_lambda)
|
157
|
+
return
|
158
|
+
end
|
159
|
+
|
160
|
+
other = Library.instance.compile_template_from_path(path_or_lambda, @view)
|
129
161
|
|
130
162
|
if options && options.is_a?(Hash)
|
131
163
|
@template.add_node Nodes::Extend.new(other.nodes, options[:locals])
|
@@ -154,7 +186,7 @@ module RablRails
|
|
154
186
|
protected
|
155
187
|
|
156
188
|
def partial_or_block(data, options)
|
157
|
-
if options
|
189
|
+
if options&.key?(:partial)
|
158
190
|
template = Library.instance.compile_template_from_path(options[:partial], @view)
|
159
191
|
template.data = data
|
160
192
|
template
|
@@ -33,9 +33,9 @@ module RablRails
|
|
33
33
|
def result_flags
|
34
34
|
@result_flags ||= begin
|
35
35
|
result = 0
|
36
|
-
result |=
|
37
|
-
result |=
|
38
|
-
result |= 0b100
|
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
|
data/lib/rabl-rails/handler.rb
CHANGED
@@ -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
|
data/lib/rabl-rails/library.rb
CHANGED
@@ -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.
|
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
|
data/lib/rabl-rails/nodes.rb
CHANGED
@@ -0,0 +1,23 @@
|
|
1
|
+
module RablRails
|
2
|
+
module Nodes
|
3
|
+
class Lookup
|
4
|
+
attr_reader :name, :data, :field
|
5
|
+
|
6
|
+
def initialize(name, data, field, cast = false)
|
7
|
+
@name = name
|
8
|
+
@data = data
|
9
|
+
@field = field
|
10
|
+
@cast = cast
|
11
|
+
@is_var = @data.to_s.start_with?('@')
|
12
|
+
end
|
13
|
+
|
14
|
+
def instance_variable_data?
|
15
|
+
@is_var
|
16
|
+
end
|
17
|
+
|
18
|
+
def cast_to_boolean?
|
19
|
+
@cast
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/rabl-rails/version.rb
CHANGED
@@ -39,6 +39,18 @@ module Visitors
|
|
39
39
|
@_result.merge!(sub_visit(object, n.nodes)) if object
|
40
40
|
end
|
41
41
|
|
42
|
+
def visit_Fetch n
|
43
|
+
hash = object_from_data(_resource, n)
|
44
|
+
key = _resource.public_send(n.field)
|
45
|
+
object = hash[key]
|
46
|
+
|
47
|
+
@_result[n.name] = if object
|
48
|
+
collection?(object) ? object.map { |o| sub_visit(o, n.nodes) } : sub_visit(object, n.nodes)
|
49
|
+
else
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
42
54
|
def visit_Code n
|
43
55
|
if !n.condition || instance_exec(_resource, &(n.condition))
|
44
56
|
result = instance_exec _resource, &(n.block)
|
@@ -56,6 +68,15 @@ module Visitors
|
|
56
68
|
@_result[n.name] = n.value
|
57
69
|
end
|
58
70
|
|
71
|
+
def visit_Lookup n
|
72
|
+
object = object_from_data(_resource, n)
|
73
|
+
key = _resource.public_send(n.field)
|
74
|
+
value = object[key]
|
75
|
+
value = !!value if n.cast_to_boolean?
|
76
|
+
|
77
|
+
@_result[n.name] = value
|
78
|
+
end
|
79
|
+
|
59
80
|
def visit_Condition n
|
60
81
|
@_result.merge!(sub_visit(_resource, n.nodes)) if instance_exec _resource, &(n.condition)
|
61
82
|
end
|
@@ -67,6 +88,12 @@ module Visitors
|
|
67
88
|
@_locals = {}
|
68
89
|
end
|
69
90
|
|
91
|
+
def visit_Polymorphic n
|
92
|
+
template_path = n.template_lambda.call(_resource)
|
93
|
+
template = RablRails::Library.instance.compile_template_from_path(template_path, @_context)
|
94
|
+
@_result.merge!(sub_visit(_resource, template.nodes))
|
95
|
+
end
|
96
|
+
|
70
97
|
def result
|
71
98
|
case RablRails.configuration.result_flags
|
72
99
|
when 0
|
data/rabl-rails.gemspec
CHANGED
@@ -20,7 +20,7 @@ Gem::Specification.new do |s|
|
|
20
20
|
|
21
21
|
s.add_dependency 'activesupport', '>= 4.2'
|
22
22
|
s.add_dependency 'railties', '>= 4.2'
|
23
|
-
s.add_dependency 'concurrent-ruby', '~> 1.0.0
|
23
|
+
s.add_dependency 'concurrent-ruby', '~> 1.0', ">= 1.0.2"
|
24
24
|
|
25
25
|
s.add_development_dependency 'actionpack', '>= 4.2'
|
26
26
|
s.add_development_dependency 'actionview', '>= 4.2'
|
data/test/helper.rb
CHANGED
data/test/test_compiler.rb
CHANGED
@@ -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::
|
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
|
@@ -165,8 +180,6 @@ class TestCompiler < Minitest::Test
|
|
165
180
|
end
|
166
181
|
|
167
182
|
it "compiles child with inline partial notation" do
|
168
|
-
|
169
|
-
|
170
183
|
t = @compiler.compile_source(%{child(:user, :partial => 'user') })
|
171
184
|
child_node = t.nodes.first
|
172
185
|
|
@@ -217,6 +230,29 @@ class TestCompiler < Minitest::Test
|
|
217
230
|
assert_equal([{ :id => :id }], extract_attributes(glue_node.nodes))
|
218
231
|
end
|
219
232
|
|
233
|
+
it "compiles fetch with record association" do
|
234
|
+
t = @compiler.compile_source(%{ fetch :address do attributes :foo end})
|
235
|
+
|
236
|
+
assert_equal(1, t.nodes.size)
|
237
|
+
fetch_node = t.nodes.first
|
238
|
+
|
239
|
+
assert_equal(:address, fetch_node.name)
|
240
|
+
assert_equal(:address, fetch_node.data)
|
241
|
+
assert_equal(:id, fetch_node.field)
|
242
|
+
assert_equal([{ foo: :foo }], extract_attributes(fetch_node.nodes))
|
243
|
+
end
|
244
|
+
|
245
|
+
it "compiles fetch with options" do
|
246
|
+
t = @compiler.compile_source(%{
|
247
|
+
fetch(:user, as: :author, field: :uid) do attributes :foo end
|
248
|
+
})
|
249
|
+
|
250
|
+
fetch_node = t.nodes.first
|
251
|
+
assert_equal(:author, fetch_node.name)
|
252
|
+
assert_equal(:user, fetch_node.data)
|
253
|
+
assert_equal(:uid, fetch_node.field)
|
254
|
+
end
|
255
|
+
|
220
256
|
it "compiles constant node" do
|
221
257
|
t = @compiler.compile_source(%{
|
222
258
|
const(:locale, 'fr_FR')
|
@@ -227,11 +263,30 @@ class TestCompiler < Minitest::Test
|
|
227
263
|
assert_equal 'fr_FR', const_node.value
|
228
264
|
end
|
229
265
|
|
266
|
+
it "compiles lookup node" do
|
267
|
+
t = @compiler.compile_source(%{
|
268
|
+
lookup(:favorite, :@user_favorites, cast: true)
|
269
|
+
})
|
270
|
+
|
271
|
+
lookup_node = t.nodes.first
|
272
|
+
assert_equal :favorite, lookup_node.name
|
273
|
+
assert_equal :@user_favorites, lookup_node.data
|
274
|
+
assert_equal :id, lookup_node.field
|
275
|
+
assert lookup_node.cast_to_boolean?
|
276
|
+
end
|
277
|
+
|
230
278
|
it "extends other template" do
|
231
279
|
t = @compiler.compile_source(%{ extends 'user' })
|
232
280
|
assert_equal([{ :id => :id }], extract_attributes(t.nodes))
|
233
281
|
end
|
234
282
|
|
283
|
+
it "extends with a lambda" do
|
284
|
+
t = @compiler.compile_source(%{ extends -> { 'user' } })
|
285
|
+
node = t.nodes.first
|
286
|
+
assert_instance_of(RablRails::Nodes::Polymorphic, node)
|
287
|
+
assert_equal('user', node.template_lambda.call)
|
288
|
+
end
|
289
|
+
|
235
290
|
it "compiles extend without overwriting nodes previously defined" do
|
236
291
|
File.open(@@tmp_path + 'xtnd.rabl', 'w') do |f|
|
237
292
|
f.puts %q{
|
data/test/test_hash_visitor.rb
CHANGED
@@ -105,6 +105,17 @@ class TestHashVisitor < Minitest::Test
|
|
105
105
|
assert_equal({ name: 'Marty'}, visitor_result)
|
106
106
|
end
|
107
107
|
|
108
|
+
it 'renders fetch node' do
|
109
|
+
template = RablRails::CompiledTemplate.new
|
110
|
+
template.add_node(RablRails::Nodes::Attribute.new(name: :name))
|
111
|
+
template.data = :@users_hash
|
112
|
+
|
113
|
+
@nodes << RablRails::Nodes::Fetch.new(:user, template, :id)
|
114
|
+
@context.assigns['users_hash'] = { @resource.id => @resource }
|
115
|
+
|
116
|
+
assert_equal({ user: { name: 'Marty' } }, visitor_result)
|
117
|
+
end
|
118
|
+
|
108
119
|
describe 'with a code node' do
|
109
120
|
before do
|
110
121
|
@proc = ->(object) { object.name }
|
@@ -141,6 +152,20 @@ class TestHashVisitor < Minitest::Test
|
|
141
152
|
assert_equal({ locale: 'fr_FR' }, visitor_result)
|
142
153
|
end
|
143
154
|
|
155
|
+
it 'renders a positive lookup node' do
|
156
|
+
@nodes << RablRails::Nodes::Lookup.new(:favorite, :@user_favorites, :id, true)
|
157
|
+
@context.assigns['user_favorites'] = { 1 => true }
|
158
|
+
|
159
|
+
assert_equal({ favorite: true }, visitor_result)
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'renders a negative lookup node' do
|
163
|
+
@nodes << RablRails::Nodes::Lookup.new(:favorite, :@user_favorites, :id, false)
|
164
|
+
@context.assigns['user_favorites'] = { 2 => true }
|
165
|
+
|
166
|
+
assert_equal({ favorite: nil }, visitor_result)
|
167
|
+
end
|
168
|
+
|
144
169
|
describe 'with a condition node' do
|
145
170
|
before do
|
146
171
|
@ns = [RablRails::Nodes::Attribute.new(name: :name)]
|
@@ -185,6 +210,20 @@ class TestHashVisitor < Minitest::Test
|
|
185
210
|
library.verify
|
186
211
|
end
|
187
212
|
|
213
|
+
it 'renders partial defined in node' do
|
214
|
+
template = RablRails::CompiledTemplate.new
|
215
|
+
template.add_node(RablRails::Nodes::Attribute.new(name: :name))
|
216
|
+
library = MiniTest::Mock.new
|
217
|
+
library.expect :compile_template_from_path, template, ['users/base', @context]
|
218
|
+
|
219
|
+
@nodes << RablRails::Nodes::Polymorphic.new(->(_) { 'users/base' })
|
220
|
+
RablRails::Library.stub :instance, library do
|
221
|
+
assert_equal({ name: 'Marty' }, visitor_result)
|
222
|
+
end
|
223
|
+
|
224
|
+
library.verify
|
225
|
+
end
|
226
|
+
|
188
227
|
it 'allows uses of locals variables with partials' do
|
189
228
|
template = RablRails::CompiledTemplate.new
|
190
229
|
template.add_node(RablRails::Nodes::Code.new(:hide_comments, ->(u) { locals[:hide_comments] }, ->(u) { locals.key?(:hide_comments) }))
|
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.
|
4
|
+
version: 0.6.2
|
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:
|
11
|
+
date: 2021-03-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -44,14 +44,20 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 1.0
|
47
|
+
version: '1.0'
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: 1.0.2
|
48
51
|
type: :runtime
|
49
52
|
prerelease: false
|
50
53
|
version_requirements: !ruby/object:Gem::Requirement
|
51
54
|
requirements:
|
52
55
|
- - "~>"
|
53
56
|
- !ruby/object:Gem::Version
|
54
|
-
version: 1.0
|
57
|
+
version: '1.0'
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: 1.0.2
|
55
61
|
- !ruby/object:Gem::Dependency
|
56
62
|
name: actionpack
|
57
63
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,7 +114,10 @@ files:
|
|
108
114
|
- lib/rabl-rails/nodes/condition.rb
|
109
115
|
- lib/rabl-rails/nodes/const.rb
|
110
116
|
- lib/rabl-rails/nodes/extend.rb
|
117
|
+
- lib/rabl-rails/nodes/fetch.rb
|
111
118
|
- lib/rabl-rails/nodes/glue.rb
|
119
|
+
- lib/rabl-rails/nodes/lookup.rb
|
120
|
+
- lib/rabl-rails/nodes/polymorphic.rb
|
112
121
|
- lib/rabl-rails/railtie.rb
|
113
122
|
- lib/rabl-rails/renderers/hash.rb
|
114
123
|
- lib/rabl-rails/renderers/json.rb
|
@@ -150,8 +159,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
150
159
|
- !ruby/object:Gem::Version
|
151
160
|
version: '0'
|
152
161
|
requirements: []
|
153
|
-
|
154
|
-
rubygems_version: 2.4.5.2
|
162
|
+
rubygems_version: 3.0.3
|
155
163
|
signing_key:
|
156
164
|
specification_version: 4
|
157
165
|
summary: Fast Rails 4+ templating system with JSON, XML and PList support
|