jbuilder 2.6.0 → 2.11.5

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