jbuilder 2.6.0 → 2.11.5

Sign up to get free protection for your applications and to get access to all the features.
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
data/lib/jbuilder.rb CHANGED
@@ -1,19 +1,23 @@
1
+ require 'active_support'
1
2
  require 'jbuilder/jbuilder'
2
3
  require 'jbuilder/blank'
3
4
  require 'jbuilder/key_formatter'
4
5
  require 'jbuilder/errors'
5
- require 'multi_json'
6
+ require 'json'
6
7
  require 'ostruct'
8
+ require 'active_support/core_ext/hash/deep_merge'
7
9
 
8
10
  class Jbuilder
9
11
  @@key_formatter = nil
10
12
  @@ignore_nil = false
13
+ @@deep_format_keys = false
11
14
 
12
15
  def initialize(options = {})
13
16
  @attributes = {}
14
17
 
15
18
  @key_formatter = options.fetch(:key_formatter){ @@key_formatter ? @@key_formatter.clone : nil}
16
19
  @ignore_nil = options.fetch(:ignore_nil, @@ignore_nil)
20
+ @deep_format_keys = options.fetch(:deep_format_keys, @@deep_format_keys)
17
21
 
18
22
  yield self if ::Kernel.block_given?
19
23
  end
@@ -26,12 +30,12 @@ class Jbuilder
26
30
  BLANK = Blank.new
27
31
  NON_ENUMERABLES = [ ::Struct, ::OpenStruct ].to_set
28
32
 
29
- def set!(key, value = BLANK, *args)
33
+ def set!(key, value = BLANK, *args, &block)
30
34
  result = if ::Kernel.block_given?
31
35
  if !_blank?(value)
32
36
  # json.comments @post.comments { |comment| ... }
33
37
  # { "comments": [ { ... }, { ... } ] }
34
- _scope{ array! value, &::Proc.new }
38
+ _scope{ array! value, &block }
35
39
  else
36
40
  # json.comments { ... }
37
41
  # { "comments": ... }
@@ -42,11 +46,11 @@ class Jbuilder
42
46
  # json.age 32
43
47
  # json.person another_jbuilder
44
48
  # { "age": 32, "person": { ... }
45
- value.attributes!
49
+ _format_keys(value.attributes!)
46
50
  else
47
51
  # json.age 32
48
52
  # { "age": 32 }
49
- value
53
+ _format_keys(value)
50
54
  end
51
55
  elsif _is_collection?(value)
52
56
  # json.comments @post.comments, :content, :created_at
@@ -61,9 +65,9 @@ class Jbuilder
61
65
  _set_value key, result
62
66
  end
63
67
 
64
- def method_missing(*args)
68
+ def method_missing(*args, &block)
65
69
  if ::Kernel.block_given?
66
- set!(*args, &::Proc.new)
70
+ set!(*args, &block)
67
71
  else
68
72
  set!(*args)
69
73
  end
@@ -130,6 +134,31 @@ class Jbuilder
130
134
  @@ignore_nil = value
131
135
  end
132
136
 
137
+ # Deeply apply key format to nested hashes and arrays passed to
138
+ # methods like set!, merge! or array!.
139
+ #
140
+ # Example:
141
+ #
142
+ # json.key_format! camelize: :lower
143
+ # json.settings({some_value: "abc"})
144
+ #
145
+ # { "settings": { "some_value": "abc" }}
146
+ #
147
+ # json.key_format! camelize: :lower
148
+ # json.deep_format_keys!
149
+ # json.settings({some_value: "abc"})
150
+ #
151
+ # { "settings": { "someValue": "abc" }}
152
+ #
153
+ def deep_format_keys!(value = true)
154
+ @deep_format_keys = value
155
+ end
156
+
157
+ # Same as instance method deep_format_keys! except sets the default.
158
+ def self.deep_format_keys(value = true)
159
+ @@deep_format_keys = value
160
+ end
161
+
133
162
  # Turns the current element into an array and yields a builder to add a hash.
134
163
  #
135
164
  # Example:
@@ -163,7 +192,7 @@ class Jbuilder
163
192
  #
164
193
  # [ { "name": David", "age": 32 }, { "name": Jamie", "age": 31 } ]
165
194
  #
166
- # If you are using Ruby 1.9+, you can use the call syntax instead of an explicit extract! call:
195
+ # You can use the call syntax instead of an explicit extract! call:
167
196
  #
168
197
  # json.(@people) { |person| ... }
169
198
  #
@@ -181,18 +210,18 @@ class Jbuilder
181
210
  # json.array! [1, 2, 3]
182
211
  #
183
212
  # [1,2,3]
184
- def array!(collection = [], *attributes)
213
+ def array!(collection = [], *attributes, &block)
185
214
  array = if collection.nil?
186
215
  []
187
216
  elsif ::Kernel.block_given?
188
- _map_collection(collection, &::Proc.new)
217
+ _map_collection(collection, &block)
189
218
  elsif attributes.any?
190
219
  _map_collection(collection) { |element| extract! element, *attributes }
191
220
  else
192
- collection.to_a
221
+ _format_keys(collection.to_a)
193
222
  end
194
223
 
195
- merge! array
224
+ @attributes = _merge_values(@attributes, array)
196
225
  end
197
226
 
198
227
  # Extracts the mentioned attributes or hash elements from the passed object and turns them into attributes of the JSON.
@@ -220,9 +249,9 @@ class Jbuilder
220
249
  end
221
250
  end
222
251
 
223
- def call(object, *attributes)
252
+ def call(object, *attributes, &block)
224
253
  if ::Kernel.block_given?
225
- array! object, &::Proc.new
254
+ array! object, &block
226
255
  else
227
256
  extract! object, *attributes
228
257
  end
@@ -240,24 +269,25 @@ class Jbuilder
240
269
  @attributes
241
270
  end
242
271
 
243
- # Merges hash or array into current builder.
244
- def merge!(hash_or_array)
245
- @attributes = _merge_values(@attributes, hash_or_array)
272
+ # Merges hash, array, or Jbuilder instance into current builder.
273
+ def merge!(object)
274
+ hash_or_array = ::Jbuilder === object ? object.attributes! : object
275
+ @attributes = _merge_values(@attributes, _format_keys(hash_or_array))
246
276
  end
247
277
 
248
278
  # Encodes the current builder as JSON.
249
279
  def target!
250
- ::MultiJson.dump(@attributes)
280
+ @attributes.to_json
251
281
  end
252
282
 
253
283
  private
254
284
 
255
285
  def _extract_hash_values(object, attributes)
256
- attributes.each{ |key| _set_value key, object.fetch(key) }
286
+ attributes.each{ |key| _set_value key, _format_keys(object.fetch(key)) }
257
287
  end
258
288
 
259
289
  def _extract_method_values(object, attributes)
260
- attributes.each{ |key| _set_value key, object.public_send(key) }
290
+ attributes.each{ |key| _set_value key, _format_keys(object.public_send(key)) }
261
291
  end
262
292
 
263
293
  def _merge_block(key)
@@ -270,14 +300,14 @@ class Jbuilder
270
300
  def _merge_values(current_value, updates)
271
301
  if _blank?(updates)
272
302
  current_value
273
- elsif _blank?(current_value) || updates.nil?
303
+ elsif _blank?(current_value) || updates.nil? || current_value.empty? && ::Array === updates
274
304
  updates
275
- elsif ::Array === updates
276
- ::Array === current_value ? current_value + updates : updates
277
- elsif ::Hash === current_value
278
- current_value.merge(updates)
305
+ elsif ::Array === current_value && ::Array === updates
306
+ current_value + updates
307
+ elsif ::Hash === current_value && ::Hash === updates
308
+ current_value.deep_merge(updates)
279
309
  else
280
- raise "Can't merge #{updates.inspect} with #{current_value.inspect}"
310
+ raise MergeError.build(current_value, updates)
281
311
  end
282
312
  end
283
313
 
@@ -285,6 +315,18 @@ class Jbuilder
285
315
  @key_formatter ? @key_formatter.format(key) : key.to_s
286
316
  end
287
317
 
318
+ def _format_keys(hash_or_array)
319
+ return hash_or_array unless @deep_format_keys
320
+
321
+ if ::Array === hash_or_array
322
+ hash_or_array.map { |value| _format_keys(value) }
323
+ elsif ::Hash === hash_or_array
324
+ ::Hash[hash_or_array.collect { |k, v| [_key(k), _format_keys(v)] }]
325
+ else
326
+ hash_or_array
327
+ end
328
+ end
329
+
288
330
  def _set_value(key, value)
289
331
  raise NullError.build(key) if @attributes.nil?
290
332
  raise ArrayError.build(key) if ::Array === @attributes
@@ -300,12 +342,12 @@ class Jbuilder
300
342
  end
301
343
 
302
344
  def _scope
303
- parent_attributes, parent_formatter = @attributes, @key_formatter
345
+ parent_attributes, parent_formatter, parent_deep_format_keys = @attributes, @key_formatter, @deep_format_keys
304
346
  @attributes = BLANK
305
347
  yield
306
348
  @attributes
307
349
  ensure
308
- @attributes, @key_formatter = parent_attributes, parent_formatter
350
+ @attributes, @key_formatter, @deep_format_keys = parent_attributes, parent_formatter, parent_deep_format_keys
309
351
  end
310
352
 
311
353
  def _is_collection?(object)
@@ -53,7 +53,7 @@ class JbuilderDependencyTrackerTest < ActiveSupport::TestCase
53
53
  assert_equal %w[path/to/partial], dependencies
54
54
  end
55
55
 
56
- test 'detects partial in indirect collecton calls' do
56
+ test 'detects partial in indirect collection calls' do
57
57
  dependencies = track_dependencies <<-RUBY
58
58
  json.comments @post.comments, partial: 'comments/comment', as: :comment
59
59
  RUBY
@@ -61,7 +61,7 @@ class JbuilderDependencyTrackerTest < ActiveSupport::TestCase
61
61
  assert_equal %w[comments/comment], dependencies
62
62
  end
63
63
 
64
- test 'detects explicit depedency' do
64
+ test 'detects explicit dependency' do
65
65
  dependencies = track_dependencies <<-RUBY
66
66
  # Template Dependency: path/to/partial
67
67
  json.foo 'bar'
@@ -21,18 +21,38 @@ class JbuilderGeneratorTest < Rails::Generators::TestCase
21
21
  run_generator
22
22
 
23
23
  assert_file 'app/views/posts/index.json.jbuilder' do |content|
24
- assert_match /json.array! @posts, partial: 'posts\/post', as: :post/, content
24
+ assert_match %r{json\.array! @posts, partial: "posts/post", as: :post}, content
25
25
  end
26
26
 
27
27
  assert_file 'app/views/posts/show.json.jbuilder' do |content|
28
- assert_match /json.partial! \"posts\/post\", post: @post/, content
28
+ assert_match %r{json\.partial! "posts/post", post: @post}, content
29
29
  end
30
-
31
- assert_file 'app/views/posts/_post.json.jbuilder' do |content|
32
- assert_match /json\.extract! post, :id, :title, :body/, content
33
- assert_match /json\.url post_url\(post, format: :json\)/, content
30
+
31
+ assert_file 'app/views/posts/_post.json.jbuilder' do |content|
32
+ assert_match %r{json\.extract! post, :id, :title, :body}, content
33
+ assert_match %r{:created_at, :updated_at}, content
34
+ assert_match %r{json\.url post_url\(post, format: :json\)}, content
35
+ end
36
+ end
37
+
38
+ test 'timestamps are not generated in partial with --no-timestamps' do
39
+ run_generator %w(Post title body:text --no-timestamps)
40
+
41
+ assert_file 'app/views/posts/_post.json.jbuilder' do |content|
42
+ assert_match %r{json\.extract! post, :id, :title, :body$}, content
43
+ assert_no_match %r{:created_at, :updated_at}, content
34
44
  end
35
-
45
+ end
36
46
 
47
+ if Rails::VERSION::MAJOR >= 6
48
+ test 'handles virtual attributes' do
49
+ run_generator %w(Message content:rich_text video:attachment photos:attachments)
50
+
51
+ assert_file 'app/views/messages/_message.json.jbuilder' do |content|
52
+ assert_match %r{json\.content message\.content\.to_s}, content
53
+ assert_match %r{json\.video url_for\(message\.video\)}, content
54
+ 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
55
+ end
56
+ end
37
57
  end
38
58
  end