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
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