jbuilder 2.10.0 → 2.14.1
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 +4 -4
- data/.devcontainer/devcontainer.json +25 -0
- data/.github/workflows/ruby.yml +56 -0
- data/.gitignore +2 -0
- data/Appraisals +14 -13
- data/CONTRIBUTING.md +5 -13
- data/Gemfile +2 -0
- data/README.md +113 -26
- data/Rakefile +3 -1
- data/bin/release +14 -0
- data/bin/test +6 -0
- data/gemfiles/{rails_5_1.gemfile → rails_7_0.gemfile} +2 -1
- data/gemfiles/{rails_5_2.gemfile → rails_7_1.gemfile} +1 -1
- data/gemfiles/{rails_6_0.gemfile → rails_7_2.gemfile} +1 -1
- data/gemfiles/{rails_5_0.gemfile → rails_8_0.gemfile} +1 -1
- data/gemfiles/rails_head.gemfile +1 -1
- data/jbuilder.gemspec +22 -3
- data/lib/generators/rails/jbuilder_generator.rb +10 -0
- data/lib/generators/rails/scaffold_controller_generator.rb +2 -0
- data/lib/generators/rails/templates/api_controller.rb +8 -2
- data/lib/generators/rails/templates/controller.rb +19 -17
- data/lib/generators/rails/templates/index.json.jbuilder +1 -1
- data/lib/generators/rails/templates/partial.json.jbuilder +14 -0
- data/lib/generators/rails/templates/show.json.jbuilder +1 -1
- data/lib/jbuilder/blank.rb +2 -0
- data/lib/jbuilder/collection_renderer.rb +58 -0
- data/lib/jbuilder/errors.rb +3 -1
- data/lib/jbuilder/jbuilder.rb +3 -7
- data/lib/jbuilder/jbuilder_dependency_tracker.rb +75 -0
- data/lib/jbuilder/jbuilder_template.rb +82 -48
- data/lib/jbuilder/key_formatter.rb +19 -21
- data/lib/jbuilder/railtie.rb +17 -19
- data/lib/jbuilder/version.rb +5 -0
- data/lib/jbuilder.rb +96 -51
- data/test/jbuilder_dependency_tracker_test.rb +2 -3
- data/test/jbuilder_generator_test.rb +22 -0
- data/test/jbuilder_template_test.rb +133 -8
- data/test/jbuilder_test.rb +211 -9
- data/test/scaffold_api_controller_generator_test.rb +63 -47
- data/test/scaffold_controller_generator_test.rb +47 -11
- data/test/test_helper.rb +24 -11
- metadata +37 -18
- data/.travis.yml +0 -47
- data/CHANGELOG.md +0 -283
- data/lib/jbuilder/dependency_tracker.rb +0 -61
data/lib/jbuilder.rb
CHANGED
|
@@ -1,31 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support'
|
|
1
4
|
require 'jbuilder/jbuilder'
|
|
2
5
|
require 'jbuilder/blank'
|
|
3
6
|
require 'jbuilder/key_formatter'
|
|
4
7
|
require 'jbuilder/errors'
|
|
5
8
|
require 'json'
|
|
6
|
-
require 'ostruct'
|
|
7
9
|
require 'active_support/core_ext/hash/deep_merge'
|
|
8
10
|
|
|
9
11
|
class Jbuilder
|
|
10
12
|
@@key_formatter = nil
|
|
11
13
|
@@ignore_nil = false
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
@@deep_format_keys = false
|
|
15
|
+
|
|
16
|
+
def initialize(
|
|
17
|
+
key_formatter: @@key_formatter,
|
|
18
|
+
ignore_nil: @@ignore_nil,
|
|
19
|
+
deep_format_keys: @@deep_format_keys,
|
|
20
|
+
&block
|
|
21
|
+
)
|
|
14
22
|
@attributes = {}
|
|
23
|
+
@key_formatter = key_formatter
|
|
24
|
+
@ignore_nil = ignore_nil
|
|
25
|
+
@deep_format_keys = deep_format_keys
|
|
15
26
|
|
|
16
|
-
|
|
17
|
-
@ignore_nil = options.fetch(:ignore_nil, @@ignore_nil)
|
|
18
|
-
|
|
19
|
-
yield self if ::Kernel.block_given?
|
|
27
|
+
yield self if block
|
|
20
28
|
end
|
|
21
29
|
|
|
22
30
|
# Yields a builder and automatically turns the result into a JSON string
|
|
23
|
-
def self.encode(
|
|
24
|
-
new(
|
|
31
|
+
def self.encode(...)
|
|
32
|
+
new(...).target!
|
|
25
33
|
end
|
|
26
34
|
|
|
27
35
|
BLANK = Blank.new
|
|
28
|
-
NON_ENUMERABLES = [ ::Struct, ::OpenStruct ].to_set
|
|
29
36
|
|
|
30
37
|
def set!(key, value = BLANK, *args, &block)
|
|
31
38
|
result = if ::Kernel.block_given?
|
|
@@ -43,11 +50,11 @@ class Jbuilder
|
|
|
43
50
|
# json.age 32
|
|
44
51
|
# json.person another_jbuilder
|
|
45
52
|
# { "age": 32, "person": { ... }
|
|
46
|
-
value.attributes!
|
|
53
|
+
_format_keys(value.attributes!)
|
|
47
54
|
else
|
|
48
55
|
# json.age 32
|
|
49
56
|
# { "age": 32 }
|
|
50
|
-
value
|
|
57
|
+
_format_keys(value)
|
|
51
58
|
end
|
|
52
59
|
elsif _is_collection?(value)
|
|
53
60
|
# json.comments @post.comments, :content, :created_at
|
|
@@ -56,20 +63,12 @@ class Jbuilder
|
|
|
56
63
|
else
|
|
57
64
|
# json.author @post.creator, :name, :email_address
|
|
58
65
|
# { "author": { "name": "David", "email_address": "david@loudthinking.com" } }
|
|
59
|
-
_merge_block(key){
|
|
66
|
+
_merge_block(key){ _extract value, args }
|
|
60
67
|
end
|
|
61
68
|
|
|
62
69
|
_set_value key, result
|
|
63
70
|
end
|
|
64
71
|
|
|
65
|
-
def method_missing(*args, &block)
|
|
66
|
-
if ::Kernel.block_given?
|
|
67
|
-
set!(*args, &block)
|
|
68
|
-
else
|
|
69
|
-
set!(*args)
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
|
|
73
72
|
# Specifies formatting to be applied to the key. Passing in a name of a function
|
|
74
73
|
# will cause that function to be called on the key. So :upcase will upper case
|
|
75
74
|
# the key. You can also pass in lambdas for more complex transformations.
|
|
@@ -98,13 +97,13 @@ class Jbuilder
|
|
|
98
97
|
#
|
|
99
98
|
# { "_first_name": "David" }
|
|
100
99
|
#
|
|
101
|
-
def key_format!(
|
|
102
|
-
@key_formatter = KeyFormatter.new(
|
|
100
|
+
def key_format!(...)
|
|
101
|
+
@key_formatter = KeyFormatter.new(...)
|
|
103
102
|
end
|
|
104
103
|
|
|
105
104
|
# Same as the instance method key_format! except sets the default.
|
|
106
|
-
def self.key_format(
|
|
107
|
-
@@key_formatter = KeyFormatter.new(
|
|
105
|
+
def self.key_format(...)
|
|
106
|
+
@@key_formatter = KeyFormatter.new(...)
|
|
108
107
|
end
|
|
109
108
|
|
|
110
109
|
# If you want to skip adding nil values to your JSON hash. This is useful
|
|
@@ -131,6 +130,31 @@ class Jbuilder
|
|
|
131
130
|
@@ignore_nil = value
|
|
132
131
|
end
|
|
133
132
|
|
|
133
|
+
# Deeply apply key format to nested hashes and arrays passed to
|
|
134
|
+
# methods like set!, merge! or array!.
|
|
135
|
+
#
|
|
136
|
+
# Example:
|
|
137
|
+
#
|
|
138
|
+
# json.key_format! camelize: :lower
|
|
139
|
+
# json.settings({some_value: "abc"})
|
|
140
|
+
#
|
|
141
|
+
# { "settings": { "some_value": "abc" }}
|
|
142
|
+
#
|
|
143
|
+
# json.key_format! camelize: :lower
|
|
144
|
+
# json.deep_format_keys!
|
|
145
|
+
# json.settings({some_value: "abc"})
|
|
146
|
+
#
|
|
147
|
+
# { "settings": { "someValue": "abc" }}
|
|
148
|
+
#
|
|
149
|
+
def deep_format_keys!(value = true)
|
|
150
|
+
@deep_format_keys = value
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Same as instance method deep_format_keys! except sets the default.
|
|
154
|
+
def self.deep_format_keys(value = true)
|
|
155
|
+
@@deep_format_keys = value
|
|
156
|
+
end
|
|
157
|
+
|
|
134
158
|
# Turns the current element into an array and yields a builder to add a hash.
|
|
135
159
|
#
|
|
136
160
|
# Example:
|
|
@@ -188,12 +212,12 @@ class Jbuilder
|
|
|
188
212
|
elsif ::Kernel.block_given?
|
|
189
213
|
_map_collection(collection, &block)
|
|
190
214
|
elsif attributes.any?
|
|
191
|
-
_map_collection(collection) { |element|
|
|
215
|
+
_map_collection(collection) { |element| _extract element, attributes }
|
|
192
216
|
else
|
|
193
|
-
collection.to_a
|
|
217
|
+
_format_keys(collection.to_a)
|
|
194
218
|
end
|
|
195
219
|
|
|
196
|
-
|
|
220
|
+
@attributes = _merge_values(@attributes, array)
|
|
197
221
|
end
|
|
198
222
|
|
|
199
223
|
# Extracts the mentioned attributes or hash elements from the passed object and turns them into attributes of the JSON.
|
|
@@ -214,18 +238,14 @@ class Jbuilder
|
|
|
214
238
|
#
|
|
215
239
|
# json.(@person, :name, :age)
|
|
216
240
|
def extract!(object, *attributes)
|
|
217
|
-
|
|
218
|
-
_extract_hash_values(object, attributes)
|
|
219
|
-
else
|
|
220
|
-
_extract_method_values(object, attributes)
|
|
221
|
-
end
|
|
241
|
+
_extract object, attributes
|
|
222
242
|
end
|
|
223
243
|
|
|
224
244
|
def call(object, *attributes, &block)
|
|
225
245
|
if ::Kernel.block_given?
|
|
226
246
|
array! object, &block
|
|
227
247
|
else
|
|
228
|
-
|
|
248
|
+
_extract object, attributes
|
|
229
249
|
end
|
|
230
250
|
end
|
|
231
251
|
|
|
@@ -241,9 +261,10 @@ class Jbuilder
|
|
|
241
261
|
@attributes
|
|
242
262
|
end
|
|
243
263
|
|
|
244
|
-
# Merges hash or
|
|
245
|
-
def merge!(
|
|
246
|
-
|
|
264
|
+
# Merges hash, array, or Jbuilder instance into current builder.
|
|
265
|
+
def merge!(object)
|
|
266
|
+
hash_or_array = ::Jbuilder === object ? object.attributes! : object
|
|
267
|
+
@attributes = _merge_values(@attributes, _format_keys(hash_or_array))
|
|
247
268
|
end
|
|
248
269
|
|
|
249
270
|
# Encodes the current builder as JSON.
|
|
@@ -253,17 +274,27 @@ class Jbuilder
|
|
|
253
274
|
|
|
254
275
|
private
|
|
255
276
|
|
|
277
|
+
alias_method :method_missing, :set!
|
|
278
|
+
|
|
279
|
+
def _extract(object, attributes)
|
|
280
|
+
if ::Hash === object
|
|
281
|
+
_extract_hash_values(object, attributes)
|
|
282
|
+
else
|
|
283
|
+
_extract_method_values(object, attributes)
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
256
287
|
def _extract_hash_values(object, attributes)
|
|
257
|
-
attributes.each{ |key| _set_value key, object.fetch(key) }
|
|
288
|
+
attributes.each{ |key| _set_value key, _format_keys(object.fetch(key)) }
|
|
258
289
|
end
|
|
259
290
|
|
|
260
291
|
def _extract_method_values(object, attributes)
|
|
261
|
-
attributes.each{ |key| _set_value key, object.public_send(key) }
|
|
292
|
+
attributes.each{ |key| _set_value key, _format_keys(object.public_send(key)) }
|
|
262
293
|
end
|
|
263
294
|
|
|
264
295
|
def _merge_block(key)
|
|
265
296
|
current_value = _blank? ? BLANK : @attributes.fetch(_key(key), BLANK)
|
|
266
|
-
raise NullError.build(key) if current_value.nil?
|
|
297
|
+
::Kernel.raise NullError.build(key) if current_value.nil?
|
|
267
298
|
new_value = _scope{ yield self }
|
|
268
299
|
_merge_values(current_value, new_value)
|
|
269
300
|
end
|
|
@@ -278,17 +309,35 @@ class Jbuilder
|
|
|
278
309
|
elsif ::Hash === current_value && ::Hash === updates
|
|
279
310
|
current_value.deep_merge(updates)
|
|
280
311
|
else
|
|
281
|
-
raise MergeError.build(current_value, updates)
|
|
312
|
+
::Kernel.raise MergeError.build(current_value, updates)
|
|
282
313
|
end
|
|
283
314
|
end
|
|
284
315
|
|
|
285
316
|
def _key(key)
|
|
286
|
-
|
|
317
|
+
if @key_formatter
|
|
318
|
+
@key_formatter.format(key)
|
|
319
|
+
elsif key.is_a?(::Symbol)
|
|
320
|
+
key.name
|
|
321
|
+
else
|
|
322
|
+
key.to_s
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def _format_keys(hash_or_array)
|
|
327
|
+
return hash_or_array unless @deep_format_keys
|
|
328
|
+
|
|
329
|
+
if ::Array === hash_or_array
|
|
330
|
+
hash_or_array.map { |value| _format_keys(value) }
|
|
331
|
+
elsif ::Hash === hash_or_array
|
|
332
|
+
::Hash[hash_or_array.collect { |k, v| [_key(k), _format_keys(v)] }]
|
|
333
|
+
else
|
|
334
|
+
hash_or_array
|
|
335
|
+
end
|
|
287
336
|
end
|
|
288
337
|
|
|
289
338
|
def _set_value(key, value)
|
|
290
|
-
raise NullError.build(key) if @attributes.nil?
|
|
291
|
-
raise ArrayError.build(key) if ::Array === @attributes
|
|
339
|
+
::Kernel.raise NullError.build(key) if @attributes.nil?
|
|
340
|
+
::Kernel.raise ArrayError.build(key) if ::Array === @attributes
|
|
292
341
|
return if @ignore_nil && value.nil? or _blank?(value)
|
|
293
342
|
@attributes = {} if _blank?
|
|
294
343
|
@attributes[_key(key)] = value
|
|
@@ -301,25 +350,21 @@ class Jbuilder
|
|
|
301
350
|
end
|
|
302
351
|
|
|
303
352
|
def _scope
|
|
304
|
-
parent_attributes, parent_formatter = @attributes, @key_formatter
|
|
353
|
+
parent_attributes, parent_formatter, parent_deep_format_keys = @attributes, @key_formatter, @deep_format_keys
|
|
305
354
|
@attributes = BLANK
|
|
306
355
|
yield
|
|
307
356
|
@attributes
|
|
308
357
|
ensure
|
|
309
|
-
@attributes, @key_formatter = parent_attributes, parent_formatter
|
|
358
|
+
@attributes, @key_formatter, @deep_format_keys = parent_attributes, parent_formatter, parent_deep_format_keys
|
|
310
359
|
end
|
|
311
360
|
|
|
312
361
|
def _is_collection?(object)
|
|
313
|
-
|
|
362
|
+
object.respond_to?(:map) && object.respond_to?(:count) && !(::Struct === object)
|
|
314
363
|
end
|
|
315
364
|
|
|
316
365
|
def _blank?(value=@attributes)
|
|
317
366
|
BLANK == value
|
|
318
367
|
end
|
|
319
|
-
|
|
320
|
-
def _object_respond_to?(object, *methods)
|
|
321
|
-
methods.all?{ |m| object.respond_to?(m) }
|
|
322
|
-
end
|
|
323
368
|
end
|
|
324
369
|
|
|
325
370
|
require 'jbuilder/railtie' if defined?(Rails)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
require 'test_helper'
|
|
2
|
-
require 'jbuilder/
|
|
3
|
-
|
|
2
|
+
require 'jbuilder/jbuilder_dependency_tracker'
|
|
4
3
|
|
|
5
4
|
class FakeTemplate
|
|
6
5
|
attr_reader :source, :handler
|
|
@@ -61,7 +60,7 @@ class JbuilderDependencyTrackerTest < ActiveSupport::TestCase
|
|
|
61
60
|
assert_equal %w[comments/comment], dependencies
|
|
62
61
|
end
|
|
63
62
|
|
|
64
|
-
test 'detects explicit
|
|
63
|
+
test 'detects explicit dependency' do
|
|
65
64
|
dependencies = track_dependencies <<-RUBY
|
|
66
65
|
# Template Dependency: path/to/partial
|
|
67
66
|
json.foo 'bar'
|
|
@@ -43,4 +43,26 @@ class JbuilderGeneratorTest < Rails::Generators::TestCase
|
|
|
43
43
|
assert_no_match %r{:created_at, :updated_at}, content
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
|
+
|
|
47
|
+
test 'namespaced views are generated correctly for index' do
|
|
48
|
+
run_generator %w(Admin::Post --model-name=Post)
|
|
49
|
+
|
|
50
|
+
assert_file 'app/views/admin/posts/index.json.jbuilder' do |content|
|
|
51
|
+
assert_match %r{json\.array! @posts, partial: "admin/posts/post", as: :post}, content
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
assert_file 'app/views/admin/posts/show.json.jbuilder' do |content|
|
|
55
|
+
assert_match %r{json\.partial! "admin/posts/post", post: @post}, content
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
test 'handles virtual attributes' do
|
|
60
|
+
run_generator %w(Message content:rich_text video:attachment photos:attachments)
|
|
61
|
+
|
|
62
|
+
assert_file 'app/views/messages/_message.json.jbuilder' do |content|
|
|
63
|
+
assert_match %r{json\.content message\.content\.to_s}, content
|
|
64
|
+
assert_match %r{json\.video url_for\(message\.video\)}, content
|
|
65
|
+
assert_match %r{json\.photos do\n json\.array!\(message\.photos\) do \|photo\|\n json\.id photo\.id\n json\.url url_for\(photo\)\n end\nend}, content
|
|
66
|
+
end
|
|
67
|
+
end
|
|
46
68
|
end
|
|
@@ -49,6 +49,17 @@ class JbuilderTemplateTest < ActiveSupport::TestCase
|
|
|
49
49
|
assert_equal "hello", result["content"]
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
+
test "partial by name with hash value omission (punning) as last statement [3.1+]" do
|
|
53
|
+
major, minor, _ = RUBY_VERSION.split(".").map(&:to_i)
|
|
54
|
+
return unless (major == 3 && minor >= 1) || major > 3
|
|
55
|
+
|
|
56
|
+
result = render(<<-JBUILDER)
|
|
57
|
+
content = "hello"
|
|
58
|
+
json.partial! "partial", content:
|
|
59
|
+
JBUILDER
|
|
60
|
+
assert_equal "hello", result["content"]
|
|
61
|
+
end
|
|
62
|
+
|
|
52
63
|
test "partial by options containing nested locals" do
|
|
53
64
|
result = render('json.partial! partial: "partial", locals: { content: "hello" }')
|
|
54
65
|
assert_equal "hello", result["content"]
|
|
@@ -73,6 +84,14 @@ class JbuilderTemplateTest < ActiveSupport::TestCase
|
|
|
73
84
|
assert_equal "Pavel", result[5]["author"]["first_name"]
|
|
74
85
|
end
|
|
75
86
|
|
|
87
|
+
test "partial collection by name with caching" do
|
|
88
|
+
result = render('json.partial! "post", collection: @posts, cached: true, as: :post', posts: POSTS)
|
|
89
|
+
assert_equal 10, result.count
|
|
90
|
+
assert_equal "Post #5", result[4]["body"]
|
|
91
|
+
assert_equal "Heinemeier Hansson", result[2]["author"]["last_name"]
|
|
92
|
+
assert_equal "Pavel", result[5]["author"]["first_name"]
|
|
93
|
+
end
|
|
94
|
+
|
|
76
95
|
test "partial collection by name with string local" do
|
|
77
96
|
result = render('json.partial! "post", collection: @posts, as: "post"', posts: POSTS)
|
|
78
97
|
assert_equal 10, result.count
|
|
@@ -90,10 +109,12 @@ class JbuilderTemplateTest < ActiveSupport::TestCase
|
|
|
90
109
|
end
|
|
91
110
|
|
|
92
111
|
test "nil partial collection by name" do
|
|
112
|
+
Jbuilder::CollectionRenderer.expects(:new).never
|
|
93
113
|
assert_equal [], render('json.partial! "post", collection: @posts, as: :post', posts: nil)
|
|
94
114
|
end
|
|
95
115
|
|
|
96
116
|
test "nil partial collection by options" do
|
|
117
|
+
Jbuilder::CollectionRenderer.expects(:new).never
|
|
97
118
|
assert_equal [], render('json.partial! partial: "post", collection: @posts, as: :post', posts: nil)
|
|
98
119
|
end
|
|
99
120
|
|
|
@@ -105,7 +126,13 @@ class JbuilderTemplateTest < ActiveSupport::TestCase
|
|
|
105
126
|
assert_equal "Pavel", result[5]["author"]["first_name"]
|
|
106
127
|
end
|
|
107
128
|
|
|
129
|
+
test "empty array of partials from empty collection" do
|
|
130
|
+
Jbuilder::CollectionRenderer.expects(:new).never
|
|
131
|
+
assert_equal [], render('json.array! @posts, partial: "post", as: :post', posts: [])
|
|
132
|
+
end
|
|
133
|
+
|
|
108
134
|
test "empty array of partials from nil collection" do
|
|
135
|
+
Jbuilder::CollectionRenderer.expects(:new).never
|
|
109
136
|
assert_equal [], render('json.array! @posts, partial: "post", as: :post', posts: nil)
|
|
110
137
|
end
|
|
111
138
|
|
|
@@ -118,10 +145,17 @@ class JbuilderTemplateTest < ActiveSupport::TestCase
|
|
|
118
145
|
end
|
|
119
146
|
|
|
120
147
|
test "empty array of partials under key from nil collection" do
|
|
148
|
+
Jbuilder::CollectionRenderer.expects(:new).never
|
|
121
149
|
result = render('json.posts @posts, partial: "post", as: :post', posts: nil)
|
|
122
150
|
assert_equal [], result["posts"]
|
|
123
151
|
end
|
|
124
152
|
|
|
153
|
+
test "empty array of partials under key from an empy collection" do
|
|
154
|
+
Jbuilder::CollectionRenderer.expects(:new).never
|
|
155
|
+
result = render('json.posts @posts, partial: "post", as: :post', posts: [])
|
|
156
|
+
assert_equal [], result["posts"]
|
|
157
|
+
end
|
|
158
|
+
|
|
125
159
|
test "object fragment caching" do
|
|
126
160
|
render(<<-JBUILDER)
|
|
127
161
|
json.cache! "cache-key" do
|
|
@@ -159,7 +193,7 @@ class JbuilderTemplateTest < ActiveSupport::TestCase
|
|
|
159
193
|
end
|
|
160
194
|
|
|
161
195
|
test "object fragment caching with expiry" do
|
|
162
|
-
travel_to "2018-05-
|
|
196
|
+
travel_to Time.iso8601("2018-05-12T11:29:00-04:00")
|
|
163
197
|
|
|
164
198
|
render <<-JBUILDER
|
|
165
199
|
json.cache! "cache-key", expires_in: 1.minute do
|
|
@@ -283,6 +317,99 @@ class JbuilderTemplateTest < ActiveSupport::TestCase
|
|
|
283
317
|
assert_equal "David", result["firstName"]
|
|
284
318
|
end
|
|
285
319
|
|
|
320
|
+
test "returns an empty array for an empty collection" do
|
|
321
|
+
Jbuilder::CollectionRenderer.expects(:new).never
|
|
322
|
+
result = render('json.array! @posts, partial: "post", as: :post, cached: true', posts: [])
|
|
323
|
+
|
|
324
|
+
# Do not use #assert_empty as it is important to ensure that the type of the JSON result is an array.
|
|
325
|
+
assert_equal [], result
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
test "works with an enumerable object" do
|
|
329
|
+
enumerable_class = Class.new do
|
|
330
|
+
include Enumerable
|
|
331
|
+
|
|
332
|
+
def each(&block)
|
|
333
|
+
[].each(&block)
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
result = render('json.array! @posts, partial: "post", as: :post, cached: true', posts: enumerable_class.new)
|
|
338
|
+
|
|
339
|
+
# Do not use #assert_empty as it is important to ensure that the type of the JSON result is an array.
|
|
340
|
+
assert_equal [], result
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
test "supports the cached: true option" do
|
|
344
|
+
result = render('json.array! @posts, partial: "post", as: :post, cached: true', posts: POSTS)
|
|
345
|
+
|
|
346
|
+
assert_equal 10, result.count
|
|
347
|
+
assert_equal "Post #5", result[4]["body"]
|
|
348
|
+
assert_equal "Heinemeier Hansson", result[2]["author"]["last_name"]
|
|
349
|
+
assert_equal "Pavel", result[5]["author"]["first_name"]
|
|
350
|
+
|
|
351
|
+
expected = {
|
|
352
|
+
"id" => 1,
|
|
353
|
+
"body" => "Post #1",
|
|
354
|
+
"author" => {
|
|
355
|
+
"first_name" => "David",
|
|
356
|
+
"last_name" => "Heinemeier Hansson"
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
assert_equal expected, Rails.cache.read("post-1")
|
|
361
|
+
|
|
362
|
+
result = render('json.array! @posts, partial: "post", as: :post, cached: true', posts: POSTS)
|
|
363
|
+
|
|
364
|
+
assert_equal 10, result.count
|
|
365
|
+
assert_equal "Post #5", result[4]["body"]
|
|
366
|
+
assert_equal "Heinemeier Hansson", result[2]["author"]["last_name"]
|
|
367
|
+
assert_equal "Pavel", result[5]["author"]["first_name"]
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
test "supports the cached: ->() {} option" do
|
|
371
|
+
result = render('json.array! @posts, partial: "post", as: :post, cached: ->(post) { [post, "foo"] }', posts: POSTS)
|
|
372
|
+
|
|
373
|
+
assert_equal 10, result.count
|
|
374
|
+
assert_equal "Post #5", result[4]["body"]
|
|
375
|
+
assert_equal "Heinemeier Hansson", result[2]["author"]["last_name"]
|
|
376
|
+
assert_equal "Pavel", result[5]["author"]["first_name"]
|
|
377
|
+
|
|
378
|
+
expected = {
|
|
379
|
+
"id" => 1,
|
|
380
|
+
"body" => "Post #1",
|
|
381
|
+
"author" => {
|
|
382
|
+
"first_name" => "David",
|
|
383
|
+
"last_name" => "Heinemeier Hansson"
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
assert_equal expected, Rails.cache.read("post-1/foo")
|
|
388
|
+
|
|
389
|
+
result = render('json.array! @posts, partial: "post", as: :post, cached: ->(post) { [post, "foo"] }', posts: POSTS)
|
|
390
|
+
|
|
391
|
+
assert_equal 10, result.count
|
|
392
|
+
assert_equal "Post #5", result[4]["body"]
|
|
393
|
+
assert_equal "Heinemeier Hansson", result[2]["author"]["last_name"]
|
|
394
|
+
assert_equal "Pavel", result[5]["author"]["first_name"]
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
test "raises an error on a render call with the :layout option" do
|
|
398
|
+
error = assert_raises NotImplementedError do
|
|
399
|
+
render('json.array! @posts, partial: "post", as: :post, layout: "layout"', posts: POSTS)
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
assert_equal "The `:layout' option is not supported in collection rendering.", error.message
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
test "raises an error on a render call with the :spacer_template option" do
|
|
406
|
+
error = assert_raises NotImplementedError do
|
|
407
|
+
render('json.array! @posts, partial: "post", as: :post, spacer_template: "template"', posts: POSTS)
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
assert_equal "The `:spacer_template' option is not supported in collection rendering.", error.message
|
|
411
|
+
end
|
|
412
|
+
|
|
286
413
|
private
|
|
287
414
|
def render(*args)
|
|
288
415
|
JSON.load render_without_parsing(*args)
|
|
@@ -290,7 +417,7 @@ class JbuilderTemplateTest < ActiveSupport::TestCase
|
|
|
290
417
|
|
|
291
418
|
def render_without_parsing(source, assigns = {})
|
|
292
419
|
view = build_view(fixtures: PARTIALS.merge("source.json.jbuilder" => source), assigns: assigns)
|
|
293
|
-
view.render(template: "source
|
|
420
|
+
view.render(template: "source")
|
|
294
421
|
end
|
|
295
422
|
|
|
296
423
|
def build_view(options = {})
|
|
@@ -298,14 +425,12 @@ class JbuilderTemplateTest < ActiveSupport::TestCase
|
|
|
298
425
|
lookup_context = ActionView::LookupContext.new([ resolver ], {}, [""])
|
|
299
426
|
controller = ActionView::TestCase::TestController.new
|
|
300
427
|
|
|
301
|
-
|
|
302
|
-
view = if ActionView::Base.respond_to?(:with_empty_template_cache)
|
|
303
|
-
ActionView::Base.with_empty_template_cache.new(lookup_context, options.fetch(:assigns, {}), controller)
|
|
304
|
-
else
|
|
305
|
-
ActionView::Base.new(lookup_context, options.fetch(:assigns, {}), controller)
|
|
306
|
-
end
|
|
428
|
+
view = ActionView::Base.with_empty_template_cache.new(lookup_context, options.fetch(:assigns, {}), controller)
|
|
307
429
|
|
|
308
430
|
def view.view_cache_dependencies; []; end
|
|
431
|
+
def view.combined_fragment_cache_key(key) [ key ] end
|
|
432
|
+
def view.cache_fragment_name(key, *) key end
|
|
433
|
+
def view.fragment_name_with_digest(key) key end
|
|
309
434
|
|
|
310
435
|
view
|
|
311
436
|
end
|