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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 4807b83a2eb921feedfb2b1a183c8cc2a8f27ef6
4
- data.tar.gz: 511aa8bd4d1c5c9b034b695545e083dd761e31b6
2
+ SHA256:
3
+ metadata.gz: ac2d0bc8954f6b27e54002015d71ed0d00f4264c8c1d57411dc789216c627e44
4
+ data.tar.gz: 98757a9fe142bafb324782725e56e85e3ba83d5a018e76a75cda00c37c73ebf4
5
5
  SHA512:
6
- metadata.gz: d17c495e276a6344ad8e9bbf2162aebf7e056f575367bcfde60399f683fceb90a5216a3b76155e42386b017411197b80afe7eeff96e8bdb31fc55c47f83ab1fe
7
- data.tar.gz: 2cc491b7e1298cb12e25bb4e365d0c86ca87fff0b73f125a1abc11caa80f575ac5d0ac1cb27ebdf42eb4fe1e3fd101a46fe23e0e4a307a3f06982fd7f355585b
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=master"
6
+ - "RAILS_VERSION=5.2.0"
7
+ - "RAILS_VERSION=6.1.0"
7
8
  rvm:
8
- - 2.2.7
9
- - 2.3.4
10
- - 2.4.1
11
- - jruby-9.1.12.0
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
@@ -8,7 +8,7 @@ rails = case rails_version
8
8
  when 'master'
9
9
  { github: 'rails/rails' }
10
10
  when "default"
11
- '~> 4.2.5'
11
+ '~> 5.2.1'
12
12
  else
13
13
  "~> #{rails_version}"
14
14
  end
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,108 @@ 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
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
- object :@user
211
- node(:full_name) { |u| u.first_name + " " + u.last_name }
212
- # => { "user" : { "full_name" : "John Doe" } }
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
- or with an assigned constant
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
- You can add condition on your custom nodes (if the condition is evaluated to false, the node will not be included).
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 the rendering time, so you can use any instance variables or view helpers inside them
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 you want to include directly the result into the current node, use the `merge` keyword (result returned from the block should be a hash)
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/users/base.json.rabl
276
+ # app/views/shared/_user.rabl
252
277
  attributes :id, :name
253
278
 
254
- # app/views/users/private.json.rabl
279
+ # app/views/users/show.rabl
280
+ object :@user
281
+
282
+ extends('shared/_user')
255
283
  attributes :super_secret_attribute
256
284
 
257
- extends 'users/base'
258
- # or using partial instead of extends
259
- # merge { |u| partial('users/base', object: u) }
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
- You can also extends template in child nodes using `partial` option (this is the same as using `extends` in the child block)
300
+ Extends can be used dynamically using rendered object and lambdas.
263
301
 
264
302
  ```ruby
265
- collection @posts
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 within `node`, partials can take locals variables that can be accessed in the included template.
314
+ When used this way, partials can take locals variables that can be accessed in the included template.
315
+
279
316
  ```ruby
280
- # base.json.rabl
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('users/base', object: u, locals: { display_credit_card: true }) }
323
+ merge { |u| partial('_credit_card', object: u, locals: { display_credit_card: true }) }
287
324
  ```
288
325
 
289
- ### Nesting
326
+ ### Putting it all together
290
327
 
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
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
- attribute :caption
296
- child :posts do
297
- attribute :title
298
- child :comments do
299
- extends 'comments/base'
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-2017 Christopher Cocchi-Perrier. See [MIT-LICENSE](http://github.com/ccocchi/rabl-rails/blob/master/MIT-LICENSE) for details.
372
+ Copyright © 2012-2020 Christopher Cocchi-Perrier. See [MIT-LICENSE](http://github.com/ccocchi/rabl-rails/blob/master/MIT-LICENSE) for details.
@@ -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
- # Notes that partial and blocks are not compatible
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] 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
@@ -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(path, options = nil)
128
- other = Library.instance.compile_template_from_path(path, @view)
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.key?(:partial)
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 |= 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
@@ -5,3 +5,6 @@ require 'rabl-rails/nodes/child'
5
5
  require 'rabl-rails/nodes/code'
6
6
  require 'rabl-rails/nodes/condition'
7
7
  require 'rabl-rails/nodes/extend'
8
+ require 'rabl-rails/nodes/polymorphic'
9
+ require 'rabl-rails/nodes/lookup'
10
+ require 'rabl-rails/nodes/fetch'
@@ -0,0 +1,12 @@
1
+ module RablRails
2
+ module Nodes
3
+ class Fetch < Child
4
+ attr_reader :field
5
+
6
+ def initialize(name, template, field)
7
+ super(name, template)
8
+ @field = field
9
+ end
10
+ end
11
+ end
12
+ end
@@ -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
@@ -0,0 +1,11 @@
1
+ module RablRails
2
+ module Nodes
3
+ class Polymorphic
4
+ attr_reader :template_lambda
5
+
6
+ def initialize(template_lambda)
7
+ @template_lambda = template_lambda
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,3 +1,3 @@
1
1
  module RablRails
2
- VERSION = '0.5.2'
2
+ VERSION = '0.6.2'
3
3
  end
@@ -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
@@ -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
@@ -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{
@@ -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.5.2
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: 2017-08-25 00:00:00.000000000 Z
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.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.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
- rubyforge_project:
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