jbuilder 2.7.0 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ruby.yml +49 -0
  3. data/.gitignore +3 -0
  4. data/Appraisals +10 -14
  5. data/CONTRIBUTING.md +5 -13
  6. data/Gemfile +0 -1
  7. data/MIT-LICENSE +1 -1
  8. data/README.md +103 -19
  9. data/Rakefile +1 -1
  10. data/bin/release +14 -0
  11. data/bin/test +6 -0
  12. data/gemfiles/{rails_4_2.gemfile → rails_7_0.gemfile} +1 -4
  13. data/gemfiles/{rails_5_0.gemfile → rails_7_1.gemfile} +1 -4
  14. data/gemfiles/{rails_5_1.gemfile → rails_head.gemfile} +1 -4
  15. data/jbuilder.gemspec +20 -4
  16. data/lib/generators/rails/jbuilder_generator.rb +16 -2
  17. data/lib/generators/rails/scaffold_controller_generator.rb +6 -0
  18. data/lib/generators/rails/templates/api_controller.rb +10 -4
  19. data/lib/generators/rails/templates/controller.rb +21 -19
  20. data/lib/generators/rails/templates/index.json.jbuilder +1 -1
  21. data/lib/generators/rails/templates/partial.json.jbuilder +15 -1
  22. data/lib/generators/rails/templates/show.json.jbuilder +1 -1
  23. data/lib/jbuilder/collection_renderer.rb +116 -0
  24. data/lib/jbuilder/jbuilder.rb +1 -7
  25. data/lib/jbuilder/jbuilder_dependency_tracker.rb +73 -0
  26. data/lib/jbuilder/jbuilder_template.rb +80 -22
  27. data/lib/jbuilder/railtie.rb +3 -3
  28. data/lib/jbuilder/version.rb +3 -0
  29. data/lib/jbuilder.rb +71 -30
  30. data/test/jbuilder_dependency_tracker_test.rb +3 -4
  31. data/test/jbuilder_generator_test.rb +37 -5
  32. data/test/jbuilder_template_test.rb +300 -324
  33. data/test/jbuilder_test.rb +229 -4
  34. data/test/scaffold_api_controller_generator_test.rb +31 -3
  35. data/test/scaffold_controller_generator_test.rb +54 -8
  36. data/test/test_helper.rb +41 -8
  37. metadata +28 -21
  38. data/.travis.yml +0 -66
  39. data/CHANGELOG.md +0 -249
  40. data/lib/jbuilder/dependency_tracker.rb +0 -61
@@ -1,457 +1,433 @@
1
1
  require "test_helper"
2
- require "mocha/setup"
3
- require "active_model"
4
- require "action_view"
5
2
  require "action_view/testing/resolvers"
6
- require "active_support/cache"
7
- require "jbuilder/jbuilder_template"
8
-
9
- BLOG_POST_PARTIAL = <<-JBUILDER
10
- json.extract! blog_post, :id, :body
11
- json.author do
12
- first_name, last_name = blog_post.author_name.split(nil, 2)
13
- json.first_name first_name
14
- json.last_name last_name
15
- end
16
- JBUILDER
17
3
 
18
- COLLECTION_PARTIAL = <<-JBUILDER
19
- json.extract! collection, :id, :name
20
- JBUILDER
4
+ class JbuilderTemplateTest < ActiveSupport::TestCase
5
+ POST_PARTIAL = <<-JBUILDER
6
+ json.extract! post, :id, :body
7
+ json.author do
8
+ first_name, last_name = post.author_name.split(nil, 2)
9
+ json.first_name first_name
10
+ json.last_name last_name
11
+ end
12
+ JBUILDER
21
13
 
22
- RACER_PARTIAL = <<-JBUILDER
23
- json.extract! racer, :id, :name
24
- JBUILDER
14
+ COLLECTION_PARTIAL = <<-JBUILDER
15
+ json.extract! collection, :id, :name
16
+ JBUILDER
25
17
 
26
- class Racer
27
- extend ActiveModel::Naming
28
- include ActiveModel::Conversion
18
+ RACER_PARTIAL = <<-JBUILDER
19
+ json.extract! racer, :id, :name
20
+ JBUILDER
29
21
 
30
- def initialize(id, name)
31
- @id, @name = id, name
32
- end
22
+ PARTIALS = {
23
+ "_partial.json.jbuilder" => "json.content content",
24
+ "_post.json.jbuilder" => POST_PARTIAL,
25
+ "racers/_racer.json.jbuilder" => RACER_PARTIAL,
26
+ "_collection.json.jbuilder" => COLLECTION_PARTIAL,
33
27
 
34
- attr_reader :id, :name
35
- end
28
+ # Ensure we find only Jbuilder partials from within Jbuilder templates.
29
+ "_post.html.erb" => "Hello world!"
30
+ }
36
31
 
32
+ AUTHORS = [ "David Heinemeier Hansson", "Pavel Pravosud" ].cycle
33
+ POSTS = (1..10).collect { |i| Post.new(i, "Post ##{i}", AUTHORS.next) }
37
34
 
38
- BlogPost = Struct.new(:id, :body, :author_name)
39
- Collection = Struct.new(:id, :name)
40
- blog_authors = [ "David Heinemeier Hansson", "Pavel Pravosud" ].cycle
41
- BLOG_POST_COLLECTION = Array.new(10){ |i| BlogPost.new(i+1, "post body #{i+1}", blog_authors.next) }
42
- COLLECTION_COLLECTION = Array.new(5){ |i| Collection.new(i+1, "collection #{i+1}") }
35
+ setup { Rails.cache.clear }
43
36
 
44
- ActionView::Template.register_template_handler :jbuilder, JbuilderHandler
37
+ test "basic template" do
38
+ result = render('json.content "hello"')
39
+ assert_equal "hello", result["content"]
40
+ end
45
41
 
46
- PARTIALS = {
47
- "_partial.json.jbuilder" => "foo ||= 'hello'; json.content foo",
48
- "_blog_post.json.jbuilder" => BLOG_POST_PARTIAL,
49
- "racers/_racer.json.jbuilder" => RACER_PARTIAL,
50
- "_collection.json.jbuilder" => COLLECTION_PARTIAL
51
- }
42
+ test "partial by name with top-level locals" do
43
+ result = render('json.partial! "partial", content: "hello"')
44
+ assert_equal "hello", result["content"]
45
+ end
52
46
 
53
- module Rails
54
- def self.cache
55
- @cache ||= ActiveSupport::Cache::MemoryStore.new
47
+ test "partial by name with nested locals" do
48
+ result = render('json.partial! "partial", locals: { content: "hello" }')
49
+ assert_equal "hello", result["content"]
56
50
  end
57
- end
58
51
 
59
- class JbuilderTemplateTest < ActionView::TestCase
60
- setup do
61
- @context = self
62
- Rails.cache.clear
52
+ test "partial by options containing nested locals" do
53
+ result = render('json.partial! partial: "partial", locals: { content: "hello" }')
54
+ assert_equal "hello", result["content"]
63
55
  end
64
56
 
65
- def jbuild(source, options = {})
66
- @rendered = []
67
- partials = options.fetch(:partials, PARTIALS).clone
68
- partials["test.json.jbuilder"] = source
69
- resolver = ActionView::FixtureResolver.new(partials)
70
- lookup_context.view_paths = [resolver]
71
- template = ActionView::Template.new(source, "test", JbuilderHandler, virtual_path: "test")
72
- json = template.render(self, {}).strip
73
- MultiJson.load(json)
57
+ test "partial by options containing top-level locals" do
58
+ result = render('json.partial! partial: "partial", content: "hello"')
59
+ assert_equal "hello", result["content"]
74
60
  end
75
61
 
76
- def undef_context_methods(*names)
77
- self.class_eval do
78
- names.each do |name|
79
- undef_method name.to_sym if method_defined?(name.to_sym)
80
- end
81
- end
62
+ test "partial for Active Model" do
63
+ result = render('json.partial! @racer', racer: Racer.new(123, "Chris Harris"))
64
+ assert_equal 123, result["id"]
65
+ assert_equal "Chris Harris", result["name"]
82
66
  end
83
67
 
84
- def assert_collection_rendered(result, context = nil)
85
- result = result.fetch(context) if context
68
+ test "partial collection by name with symbol local" do
69
+ result = render('json.partial! "post", collection: @posts, as: :post', posts: POSTS)
70
+ assert_equal 10, result.count
71
+ assert_equal "Post #5", result[4]["body"]
72
+ assert_equal "Heinemeier Hansson", result[2]["author"]["last_name"]
73
+ assert_equal "Pavel", result[5]["author"]["first_name"]
74
+ end
86
75
 
87
- assert_equal 10, result.length
88
- assert_equal Array, result.class
89
- assert_equal "post body 5", result[4]["body"]
76
+ test "partial collection by name with caching" do
77
+ result = render('json.partial! "post", collection: @posts, cached: true, as: :post', posts: POSTS)
78
+ assert_equal 10, result.count
79
+ assert_equal "Post #5", result[4]["body"]
90
80
  assert_equal "Heinemeier Hansson", result[2]["author"]["last_name"]
91
- assert_equal "Pavel", result[5]["author"]["first_name"]
81
+ assert_equal "Pavel", result[5]["author"]["first_name"]
92
82
  end
93
83
 
94
- test "rendering" do
95
- result = jbuild(<<-JBUILDER)
96
- json.content "hello"
97
- JBUILDER
84
+ test "partial collection by name with string local" do
85
+ result = render('json.partial! "post", collection: @posts, as: "post"', posts: POSTS)
86
+ assert_equal 10, result.count
87
+ assert_equal "Post #5", result[4]["body"]
88
+ assert_equal "Heinemeier Hansson", result[2]["author"]["last_name"]
89
+ assert_equal "Pavel", result[5]["author"]["first_name"]
90
+ end
98
91
 
99
- assert_equal "hello", result["content"]
92
+ test "partial collection by options" do
93
+ result = render('json.partial! partial: "post", collection: @posts, as: :post', posts: POSTS)
94
+ assert_equal 10, result.count
95
+ assert_equal "Post #5", result[4]["body"]
96
+ assert_equal "Heinemeier Hansson", result[2]["author"]["last_name"]
97
+ assert_equal "Pavel", result[5]["author"]["first_name"]
100
98
  end
101
99
 
102
- test "key_format! with parameter" do
103
- result = jbuild(<<-JBUILDER)
104
- json.key_format! camelize: [:lower]
105
- json.camel_style "for JS"
106
- JBUILDER
100
+ test "nil partial collection by name" do
101
+ Jbuilder::CollectionRenderer.expects(:new).never
102
+ assert_equal [], render('json.partial! "post", collection: @posts, as: :post', posts: nil)
103
+ end
107
104
 
108
- assert_equal ["camelStyle"], result.keys
105
+ test "nil partial collection by options" do
106
+ Jbuilder::CollectionRenderer.expects(:new).never
107
+ assert_equal [], render('json.partial! partial: "post", collection: @posts, as: :post', posts: nil)
109
108
  end
110
109
 
111
- test "key_format! propagates to child elements" do
112
- result = jbuild(<<-JBUILDER)
113
- json.key_format! :upcase
114
- json.level1 "one"
115
- json.level2 do
116
- json.value "two"
117
- end
118
- JBUILDER
110
+ test "array of partials" do
111
+ result = render('json.array! @posts, partial: "post", as: :post', posts: POSTS)
112
+ assert_equal 10, result.count
113
+ assert_equal "Post #5", result[4]["body"]
114
+ assert_equal "Heinemeier Hansson", result[2]["author"]["last_name"]
115
+ assert_equal "Pavel", result[5]["author"]["first_name"]
116
+ end
119
117
 
120
- assert_equal "one", result["LEVEL1"]
121
- assert_equal "two", result["LEVEL2"]["VALUE"]
118
+ test "empty array of partials from empty collection" do
119
+ Jbuilder::CollectionRenderer.expects(:new).never
120
+ assert_equal [], render('json.array! @posts, partial: "post", as: :post', posts: [])
122
121
  end
123
122
 
124
- test "partial! renders partial" do
125
- result = jbuild(<<-JBUILDER)
126
- json.partial! "partial"
127
- JBUILDER
123
+ test "empty array of partials from nil collection" do
124
+ Jbuilder::CollectionRenderer.expects(:new).never
125
+ assert_equal [], render('json.array! @posts, partial: "post", as: :post', posts: nil)
126
+ end
128
127
 
129
- assert_equal "hello", result["content"]
128
+ test "array of partials under key" do
129
+ result = render('json.posts @posts, partial: "post", as: :post', posts: POSTS)
130
+ assert_equal 10, result["posts"].count
131
+ assert_equal "Post #5", result["posts"][4]["body"]
132
+ assert_equal "Heinemeier Hansson", result["posts"][2]["author"]["last_name"]
133
+ assert_equal "Pavel", result["posts"][5]["author"]["first_name"]
130
134
  end
131
135
 
132
- test "partial! + locals via :locals option" do
133
- result = jbuild(<<-JBUILDER)
134
- json.partial! "partial", locals: { foo: "howdy" }
135
- JBUILDER
136
+ test "empty array of partials under key from nil collection" do
137
+ Jbuilder::CollectionRenderer.expects(:new).never
138
+ result = render('json.posts @posts, partial: "post", as: :post', posts: nil)
139
+ assert_equal [], result["posts"]
140
+ end
136
141
 
137
- assert_equal "howdy", result["content"]
142
+ test "empty array of partials under key from an empy collection" do
143
+ Jbuilder::CollectionRenderer.expects(:new).never
144
+ result = render('json.posts @posts, partial: "post", as: :post', posts: [])
145
+ assert_equal [], result["posts"]
138
146
  end
139
147
 
140
- test "partial! + locals without :locals key" do
141
- result = jbuild(<<-JBUILDER)
142
- json.partial! "partial", foo: "goodbye"
148
+ test "object fragment caching" do
149
+ render(<<-JBUILDER)
150
+ json.cache! "cache-key" do
151
+ json.name "Hit"
152
+ end
143
153
  JBUILDER
144
154
 
145
- assert_equal "goodbye", result["content"]
155
+ hit = render('json.cache! "cache-key" do; end')
156
+ assert_equal "Hit", hit["name"]
146
157
  end
147
158
 
148
- test "partial! renders collections" do
149
- result = jbuild(<<-JBUILDER)
150
- json.partial! "blog_post", collection: BLOG_POST_COLLECTION, as: :blog_post
159
+ test "conditional object fragment caching" do
160
+ render(<<-JBUILDER)
161
+ json.cache_if! true, "cache-key" do
162
+ json.a "Hit"
163
+ end
164
+
165
+ json.cache_if! false, "cache-key" do
166
+ json.b "Hit"
167
+ end
151
168
  JBUILDER
152
169
 
153
- assert_collection_rendered result
154
- end
170
+ result = render(<<-JBUILDER)
171
+ json.cache_if! true, "cache-key" do
172
+ json.a "Miss"
173
+ end
155
174
 
156
- test "partial! renders collections when as argument is a string" do
157
- result = jbuild(<<-JBUILDER)
158
- json.partial! "blog_post", collection: BLOG_POST_COLLECTION, as: "blog_post"
175
+ json.cache_if! false, "cache-key" do
176
+ json.b "Miss"
177
+ end
159
178
  JBUILDER
160
179
 
161
- assert_collection_rendered result
180
+ assert_equal "Hit", result["a"]
181
+ assert_equal "Miss", result["b"]
162
182
  end
163
183
 
164
- test "partial! renders collections as collections" do
165
- result = jbuild(<<-JBUILDER)
166
- json.partial! "collection", collection: COLLECTION_COLLECTION, as: :collection
184
+ test "object fragment caching with expiry" do
185
+ travel_to Time.iso8601("2018-05-12T11:29:00-04:00")
186
+
187
+ render <<-JBUILDER
188
+ json.cache! "cache-key", expires_in: 1.minute do
189
+ json.name "Hit"
190
+ end
167
191
  JBUILDER
168
192
 
169
- assert_equal 5, result.length
170
- end
193
+ travel 30.seconds
171
194
 
172
- test "partial! renders as empty array for nil-collection" do
173
- result = jbuild(<<-JBUILDER)
174
- json.partial! "blog_post", collection: nil, as: :blog_post
195
+ result = render(<<-JBUILDER)
196
+ json.cache! "cache-key", expires_in: 1.minute do
197
+ json.name "Miss"
198
+ end
175
199
  JBUILDER
176
200
 
177
- assert_equal [], result
178
- end
201
+ assert_equal "Hit", result["name"]
202
+
203
+ travel 31.seconds
179
204
 
180
- test "partial! renders collection (alt. syntax)" do
181
- result = jbuild(<<-JBUILDER)
182
- json.partial! partial: "blog_post", collection: BLOG_POST_COLLECTION, as: :blog_post
205
+ result = render(<<-JBUILDER)
206
+ json.cache! "cache-key", expires_in: 1.minute do
207
+ json.name "Miss"
208
+ end
183
209
  JBUILDER
184
210
 
185
- assert_collection_rendered result
211
+ assert_equal "Miss", result["name"]
186
212
  end
187
213
 
188
- test "partial! renders as empty array for nil-collection (alt. syntax)" do
189
- result = jbuild(<<-JBUILDER)
190
- json.partial! partial: "blog_post", collection: nil, as: :blog_post
214
+ test "object root caching" do
215
+ render <<-JBUILDER
216
+ json.cache_root! "cache-key" do
217
+ json.name "Hit"
218
+ end
191
219
  JBUILDER
192
220
 
193
- assert_equal [], result
194
- end
221
+ assert_equal JSON.dump(name: "Hit"), Rails.cache.read("jbuilder/root/cache-key")
195
222
 
196
- test "render array of partials" do
197
- result = jbuild(<<-JBUILDER)
198
- json.array! BLOG_POST_COLLECTION, partial: "blog_post", as: :blog_post
223
+ result = render(<<-JBUILDER)
224
+ json.cache_root! "cache-key" do
225
+ json.name "Miss"
226
+ end
199
227
  JBUILDER
200
228
 
201
- assert_collection_rendered result
229
+ assert_equal "Hit", result["name"]
202
230
  end
203
231
 
204
- test "render array of partials as empty array with nil-collection" do
205
- result = jbuild(<<-JBUILDER)
206
- json.array! nil, partial: "blog_post", as: :blog_post
232
+ test "array fragment caching" do
233
+ render <<-JBUILDER
234
+ json.cache! "cache-key" do
235
+ json.array! %w[ a b c ]
236
+ end
207
237
  JBUILDER
208
238
 
209
- assert_equal [], result
239
+ assert_equal %w[ a b c ], render('json.cache! "cache-key" do; end')
210
240
  end
211
241
 
212
- test "render array of partials as a value" do
213
- result = jbuild(<<-JBUILDER)
214
- json.posts BLOG_POST_COLLECTION, partial: "blog_post", as: :blog_post
242
+ test "array root caching" do
243
+ render <<-JBUILDER
244
+ json.cache_root! "cache-key" do
245
+ json.array! %w[ a b c ]
246
+ end
215
247
  JBUILDER
216
248
 
217
- assert_collection_rendered result, "posts"
218
- end
249
+ assert_equal JSON.dump(%w[ a b c ]), Rails.cache.read("jbuilder/root/cache-key")
219
250
 
220
- test "render as empty array if partials as a nil value" do
221
- result = jbuild <<-JBUILDER
222
- json.posts nil, partial: "blog_post", as: :blog_post
251
+ assert_equal %w[ a b c ], render(<<-JBUILDER)
252
+ json.cache_root! "cache-key" do
253
+ json.array! %w[ d e f ]
254
+ end
223
255
  JBUILDER
224
-
225
- assert_equal [], result["posts"]
226
256
  end
227
257
 
228
- test "cache an empty block" do
229
- undef_context_methods :fragment_name_with_digest, :cache_fragment_name
258
+ test "failing to cache root after JSON structures have been defined" do
259
+ assert_raises ActionView::Template::Error, "cache_root! can't be used after JSON structures have been defined" do
260
+ render <<-JBUILDER
261
+ json.name "Kaboom"
262
+ json.cache_root! "cache-key" do
263
+ json.name "Miss"
264
+ end
265
+ JBUILDER
266
+ end
267
+ end
230
268
 
231
- jbuild <<-JBUILDER
232
- json.cache! "nothing" do
233
- end
234
- JBUILDER
269
+ test "empty fragment caching" do
270
+ render 'json.cache! "nothing" do; end'
235
271
 
236
272
  result = nil
237
273
 
238
274
  assert_nothing_raised do
239
- result = jbuild(<<-JBUILDER)
275
+ result = render(<<-JBUILDER)
240
276
  json.foo "bar"
241
- json.cache! "nothing" do
242
- end
277
+ json.cache! "nothing" do; end
243
278
  JBUILDER
244
279
  end
245
280
 
246
281
  assert_equal "bar", result["foo"]
247
282
  end
248
283
 
249
- test "fragment caching a JSON object" do
250
- undef_context_methods :fragment_name_with_digest, :cache_fragment_name
284
+ test "cache instrumentation" do
285
+ payloads = {}
251
286
 
252
- jbuild <<-JBUILDER
253
- json.cache! "cachekey" do
254
- json.name "Cache"
255
- end
256
- JBUILDER
287
+ ActiveSupport::Notifications.subscribe("read_fragment.action_controller") { |*args| payloads[:read] = args.last }
288
+ ActiveSupport::Notifications.subscribe("write_fragment.action_controller") { |*args| payloads[:write] = args.last }
257
289
 
258
- result = jbuild(<<-JBUILDER)
259
- json.cache! "cachekey" do
260
- json.name "Miss"
290
+ render <<-JBUILDER
291
+ json.cache! "cache-key" do
292
+ json.name "Cache"
261
293
  end
262
294
  JBUILDER
263
295
 
264
- assert_equal "Cache", result["name"]
296
+ assert_equal "jbuilder/cache-key", payloads[:read][:key]
297
+ assert_equal "jbuilder/cache-key", payloads[:write][:key]
265
298
  end
266
299
 
267
- test "conditionally fragment caching a JSON object" do
268
- undef_context_methods :fragment_name_with_digest, :cache_fragment_name
269
-
270
- jbuild <<-JBUILDER
271
- json.cache_if! true, "cachekey" do
272
- json.test1 "Cache"
273
- end
274
- json.cache_if! false, "cachekey" do
275
- json.test2 "Cache"
276
- end
277
- JBUILDER
278
-
279
- result = jbuild(<<-JBUILDER)
280
- json.cache_if! true, "cachekey" do
281
- json.test1 "Miss"
282
- end
283
- json.cache_if! false, "cachekey" do
284
- json.test2 "Miss"
285
- end
300
+ test "camelized keys" do
301
+ result = render(<<-JBUILDER)
302
+ json.key_format! camelize: [:lower]
303
+ json.first_name "David"
286
304
  JBUILDER
287
305
 
288
- assert_equal "Cache", result["test1"]
289
- assert_equal "Miss", result["test2"]
306
+ assert_equal "David", result["firstName"]
290
307
  end
291
308
 
292
- test "fragment caching deserializes an array" do
293
- undef_context_methods :fragment_name_with_digest, :cache_fragment_name
294
-
295
- jbuild <<-JBUILDER
296
- json.cache! "cachekey" do
297
- json.array! %w[a b c]
298
- end
299
- JBUILDER
300
-
301
- result = jbuild(<<-JBUILDER)
302
- json.cache! "cachekey" do
303
- json.array! %w[1 2 3]
304
- end
305
- JBUILDER
309
+ if JbuilderTemplate::CollectionRenderer.supported?
310
+ test "returns an empty array for an empty collection" do
311
+ Jbuilder::CollectionRenderer.expects(:new).never
312
+ result = render('json.array! @posts, partial: "post", as: :post, cached: true', posts: [])
306
313
 
307
- assert_equal %w[a b c], result
308
- end
309
-
310
- test "fragment caching works with current cache digests" do
311
- undef_context_methods :fragment_name_with_digest
314
+ # Do not use #assert_empty as it is important to ensure that the type of the JSON result is an array.
315
+ assert_equal [], result
316
+ end
312
317
 
313
- @context.expects :cache_fragment_name
314
- ActiveSupport::Cache.expects :expand_cache_key
318
+ test "works with an enumerable object" do
319
+ enumerable_class = Class.new do
320
+ include Enumerable
315
321
 
316
- jbuild <<-JBUILDER
317
- json.cache! "cachekey" do
318
- json.name "Cache"
322
+ def each(&block)
323
+ [].each(&block)
324
+ end
319
325
  end
320
- JBUILDER
321
- end
322
326
 
323
- test "fragment caching uses fragment_cache_key" do
324
- undef_context_methods :fragment_name_with_digest, :cache_fragment_name
327
+ result = render('json.array! @posts, partial: "post", as: :post, cached: true', posts: enumerable_class.new)
325
328
 
326
- @context.expects(:fragment_cache_key).with("cachekey")
329
+ # Do not use #assert_empty as it is important to ensure that the type of the JSON result is an array.
330
+ assert_equal [], result
331
+ end
327
332
 
328
- jbuild <<-JBUILDER
329
- json.cache! "cachekey" do
330
- json.name "Cache"
331
- end
332
- JBUILDER
333
- end
333
+ test "supports the cached: true option" do
334
+ result = render('json.array! @posts, partial: "post", as: :post, cached: true', posts: POSTS)
334
335
 
335
- test "fragment caching instrumentation" do
336
- undef_context_methods :fragment_name_with_digest, :cache_fragment_name
336
+ assert_equal 10, result.count
337
+ assert_equal "Post #5", result[4]["body"]
338
+ assert_equal "Heinemeier Hansson", result[2]["author"]["last_name"]
339
+ assert_equal "Pavel", result[5]["author"]["first_name"]
337
340
 
338
- payloads = {}
339
- ActiveSupport::Notifications.subscribe("read_fragment.action_controller") { |*args| payloads[:read_fragment] = args.last }
340
- ActiveSupport::Notifications.subscribe("write_fragment.action_controller") { |*args| payloads[:write_fragment] = args.last }
341
+ expected = {
342
+ "id" => 1,
343
+ "body" => "Post #1",
344
+ "author" => {
345
+ "first_name" => "David",
346
+ "last_name" => "Heinemeier Hansson"
347
+ }
348
+ }
341
349
 
342
- jbuild <<-JBUILDER
343
- json.cache! "cachekey" do
344
- json.name "Cache"
345
- end
346
- JBUILDER
347
-
348
- assert_equal "jbuilder/cachekey", payloads[:read_fragment][:key]
349
- assert_equal "jbuilder/cachekey", payloads[:write_fragment][:key]
350
- end
350
+ assert_equal expected, Rails.cache.read("post-1")
351
351
 
352
- test "current cache digest option accepts options" do
353
- undef_context_methods :fragment_name_with_digest
352
+ result = render('json.array! @posts, partial: "post", as: :post, cached: true', posts: POSTS)
354
353
 
355
- @context.expects(:cache_fragment_name).with("cachekey", skip_digest: true)
356
- ActiveSupport::Cache.expects :expand_cache_key
354
+ assert_equal 10, result.count
355
+ assert_equal "Post #5", result[4]["body"]
356
+ assert_equal "Heinemeier Hansson", result[2]["author"]["last_name"]
357
+ assert_equal "Pavel", result[5]["author"]["first_name"]
358
+ end
357
359
 
358
- jbuild <<-JBUILDER
359
- json.cache! "cachekey", skip_digest: true do
360
- json.name "Cache"
361
- end
362
- JBUILDER
363
- end
360
+ test "supports the cached: ->() {} option" do
361
+ result = render('json.array! @posts, partial: "post", as: :post, cached: ->(post) { [post, "foo"] }', posts: POSTS)
364
362
 
365
- test "fragment caching accepts expires_in option" do
366
- undef_context_methods :fragment_name_with_digest
363
+ assert_equal 10, result.count
364
+ assert_equal "Post #5", result[4]["body"]
365
+ assert_equal "Heinemeier Hansson", result[2]["author"]["last_name"]
366
+ assert_equal "Pavel", result[5]["author"]["first_name"]
367
367
 
368
- @context.expects(:cache_fragment_name).with("cachekey", {})
368
+ expected = {
369
+ "id" => 1,
370
+ "body" => "Post #1",
371
+ "author" => {
372
+ "first_name" => "David",
373
+ "last_name" => "Heinemeier Hansson"
374
+ }
375
+ }
369
376
 
370
- jbuild <<-JBUILDER
371
- json.cache! "cachekey", expires_in: 1.minute do
372
- json.name "Cache"
373
- end
374
- JBUILDER
375
- end
377
+ assert_equal expected, Rails.cache.read("post-1/foo")
376
378
 
377
- test "caching root structure" do
378
- undef_context_methods :fragment_name_with_digest, :cache_fragment_name
379
+ result = render('json.array! @posts, partial: "post", as: :post, cached: ->(post) { [post, "foo"] }', posts: POSTS)
379
380
 
380
- cache_miss_result = jbuild <<-JBUILDER
381
- json.cache_root! "cachekey" do
382
- json.name "Miss"
383
- end
384
- JBUILDER
381
+ assert_equal 10, result.count
382
+ assert_equal "Post #5", result[4]["body"]
383
+ assert_equal "Heinemeier Hansson", result[2]["author"]["last_name"]
384
+ assert_equal "Pavel", result[5]["author"]["first_name"]
385
+ end
385
386
 
386
- cache_hit_result = jbuild <<-JBUILDER
387
- json.cache_root! "cachekey" do
388
- json.name "Hit"
387
+ test "raises an error on a render call with the :layout option" do
388
+ error = assert_raises NotImplementedError do
389
+ render('json.array! @posts, partial: "post", as: :post, layout: "layout"', posts: POSTS)
389
390
  end
390
- JBUILDER
391
-
392
- assert_equal cache_miss_result, cache_hit_result
393
- end
394
391
 
395
- test "failing to cache root after attributes have been defined" do
396
- assert_raises ActionView::Template::Error, "cache_root! can't be used after JSON structures have been defined" do
397
- jbuild <<-JBUILDER
398
- json.name "Kaboom"
399
- json.cache_root! "cachekey" do
400
- json.name "Miss"
401
- end
402
- JBUILDER
392
+ assert_equal "The `:layout' option is not supported in collection rendering.", error.message
403
393
  end
404
- end
405
-
406
- test "does not perform caching when controller.perform_caching is false" do
407
- controller.perform_caching = false
408
394
 
409
- jbuild <<-JBUILDER
410
- json.cache! "cachekey" do
411
- json.name "Cache"
395
+ test "raises an error on a render call with the :spacer_template option" do
396
+ error = assert_raises NotImplementedError do
397
+ render('json.array! @posts, partial: "post", as: :post, spacer_template: "template"', posts: POSTS)
412
398
  end
413
- JBUILDER
414
-
415
- assert_equal Rails.cache.inspect[/entries=(\d+)/, 1], "0"
416
- end
417
-
418
- test "invokes templates via params via set!" do
419
- @post = BLOG_POST_COLLECTION.first
420
399
 
421
- result = jbuild(<<-JBUILDER)
422
- json.post @post, partial: "blog_post", as: :blog_post
423
- JBUILDER
424
-
425
- assert_equal 1, result["post"]["id"]
426
- assert_equal "post body 1", result["post"]["body"]
427
- assert_equal "David", result["post"]["author"]["first_name"]
400
+ assert_equal "The `:spacer_template' option is not supported in collection rendering.", error.message
401
+ end
428
402
  end
429
403
 
430
- test "invokes templates implicitly for ActiveModel objects" do
431
- @racer = Racer.new(123, "Chris Harris")
432
-
433
- result = jbuild(<<-JBUILDER)
434
- json.partial! @racer
435
- JBUILDER
404
+ private
405
+ def render(*args)
406
+ JSON.load render_without_parsing(*args)
407
+ end
436
408
 
437
- assert_equal %w[id name], result.keys
438
- assert_equal 123, result["id"]
439
- assert_equal "Chris Harris", result["name"]
440
- end
409
+ def render_without_parsing(source, assigns = {})
410
+ view = build_view(fixtures: PARTIALS.merge("source.json.jbuilder" => source), assigns: assigns)
411
+ view.render(template: "source")
412
+ end
441
413
 
442
- test "renders partial via set! with same name as HTML partial" do
443
- partials = {
444
- "_blog_post.html.erb" => "Hello!",
445
- "_blog_post.json.jbuilder" => BLOG_POST_PARTIAL
446
- }
414
+ def build_view(options = {})
415
+ resolver = ActionView::FixtureResolver.new(options.fetch(:fixtures))
416
+ lookup_context = ActionView::LookupContext.new([ resolver ], {}, [""])
417
+ controller = ActionView::TestCase::TestController.new
447
418
 
448
- @post = BLOG_POST_COLLECTION.first
419
+ # TODO: Use with_empty_template_cache unconditionally after dropping support for Rails <6.0.
420
+ view = if ActionView::Base.respond_to?(:with_empty_template_cache)
421
+ ActionView::Base.with_empty_template_cache.new(lookup_context, options.fetch(:assigns, {}), controller)
422
+ else
423
+ ActionView::Base.new(lookup_context, options.fetch(:assigns, {}), controller)
424
+ end
449
425
 
450
- result = jbuild(<<-JBUILDER, partials: partials)
451
- json.post @post, partial: "blog_post", as: :blog_post
452
- JBUILDER
426
+ def view.view_cache_dependencies; []; end
427
+ def view.combined_fragment_cache_key(key) [ key ] end
428
+ def view.cache_fragment_name(key, *) key end
429
+ def view.fragment_name_with_digest(key) key end
453
430
 
454
- assert_not_nil result["post"]
455
- assert_equal 1, result["post"]["id"]
456
- end
431
+ view
432
+ end
457
433
  end