jbuilder 2.6.4 → 2.11.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 +5 -5
- data/.gitignore +2 -0
- data/.travis.yml +34 -48
- data/Appraisals +13 -42
- data/CHANGELOG.md +57 -0
- data/CONTRIBUTING.md +1 -3
- data/Gemfile +0 -1
- data/MIT-LICENSE +1 -1
- data/README.md +32 -16
- data/Rakefile +1 -5
- data/gemfiles/rails_5_0.gemfile +3 -6
- data/gemfiles/rails_5_1.gemfile +3 -6
- data/gemfiles/rails_5_2.gemfile +10 -0
- data/gemfiles/rails_6_0.gemfile +10 -0
- data/gemfiles/rails_head.gemfile +10 -0
- data/jbuilder.gemspec +11 -6
- data/lib/generators/rails/jbuilder_generator.rb +12 -2
- data/lib/generators/rails/scaffold_controller_generator.rb +6 -0
- data/lib/generators/rails/templates/api_controller.rb +4 -4
- data/lib/generators/rails/templates/controller.rb +14 -19
- data/lib/generators/rails/templates/index.json.jbuilder +1 -1
- data/lib/generators/rails/templates/partial.json.jbuilder +15 -1
- data/lib/jbuilder/jbuilder_template.rb +11 -10
- data/lib/jbuilder.rb +30 -18
- data/test/jbuilder_dependency_tracker_test.rb +1 -1
- data/test/jbuilder_generator_test.rb +25 -5
- data/test/jbuilder_template_test.rb +196 -325
- data/test/jbuilder_test.rb +95 -3
- data/test/scaffold_api_controller_generator_test.rb +17 -2
- data/test/scaffold_controller_generator_test.rb +21 -7
- data/test/test_helper.rb +26 -6
- metadata +10 -29
- data/gemfiles/rails_3_0.gemfile +0 -14
- data/gemfiles/rails_3_1.gemfile +0 -14
- data/gemfiles/rails_3_2.gemfile +0 -14
- data/gemfiles/rails_4_0.gemfile +0 -13
- data/gemfiles/rails_4_1.gemfile +0 -13
- data/gemfiles/rails_4_2.gemfile +0 -14
@@ -73,8 +73,8 @@ class JbuilderTemplate < Jbuilder
|
|
73
73
|
# json.cache_if! !admin?, @person, expires_in: 10.minutes do
|
74
74
|
# json.extract! @person, :name, :age
|
75
75
|
# end
|
76
|
-
def cache_if!(condition, *args)
|
77
|
-
condition ? cache!(*args,
|
76
|
+
def cache_if!(condition, *args, &block)
|
77
|
+
condition ? cache!(*args, &block) : yield
|
78
78
|
end
|
79
79
|
|
80
80
|
def target!
|
@@ -104,7 +104,7 @@ class JbuilderTemplate < Jbuilder
|
|
104
104
|
private
|
105
105
|
|
106
106
|
def _render_partial_with_options(options)
|
107
|
-
options.reverse_merge! locals:
|
107
|
+
options.reverse_merge! locals: options.except(:partial, :as, :collection)
|
108
108
|
options.reverse_merge! ::JbuilderTemplate.template_lookup_options
|
109
109
|
as = options[:as]
|
110
110
|
|
@@ -151,8 +151,8 @@ class JbuilderTemplate < Jbuilder
|
|
151
151
|
name_options = options.slice(:skip_digest, :virtual_path)
|
152
152
|
key = _fragment_name_with_digest(key, name_options)
|
153
153
|
|
154
|
-
if @context.respond_to?(:
|
155
|
-
key = @context.
|
154
|
+
if @context.respond_to?(:combined_fragment_cache_key)
|
155
|
+
key = @context.combined_fragment_cache_key(key)
|
156
156
|
else
|
157
157
|
key = url_for(key).split('://', 2).last if ::Hash === key
|
158
158
|
end
|
@@ -164,7 +164,7 @@ class JbuilderTemplate < Jbuilder
|
|
164
164
|
if @context.respond_to?(:cache_fragment_name)
|
165
165
|
# Current compatibility, fragment_name_with_digest is private again and cache_fragment_name
|
166
166
|
# should be used instead.
|
167
|
-
@context.cache_fragment_name(key, options)
|
167
|
+
@context.cache_fragment_name(key, **options)
|
168
168
|
elsif @context.respond_to?(:fragment_name_with_digest)
|
169
169
|
# Backwards compatibility for period of time when fragment_name_with_digest was made public.
|
170
170
|
@context.fragment_name_with_digest(key)
|
@@ -188,7 +188,7 @@ class JbuilderTemplate < Jbuilder
|
|
188
188
|
_scope{ _render_partial_with_options options.merge(collection: object) }
|
189
189
|
else
|
190
190
|
locals = ::Hash[options[:as], object]
|
191
|
-
_scope{
|
191
|
+
_scope{ _render_partial_with_options options.merge(locals: locals) }
|
192
192
|
end
|
193
193
|
|
194
194
|
set! name, value
|
@@ -222,11 +222,12 @@ end
|
|
222
222
|
|
223
223
|
class JbuilderHandler
|
224
224
|
cattr_accessor :default_format
|
225
|
-
self.default_format =
|
225
|
+
self.default_format = :json
|
226
226
|
|
227
|
-
def self.call(template)
|
227
|
+
def self.call(template, source = nil)
|
228
|
+
source ||= template.source
|
228
229
|
# this juggling is required to keep line numbers right in the error
|
229
|
-
%{__already_defined = defined?(json); json||=JbuilderTemplate.new(self); #{
|
230
|
+
%{__already_defined = defined?(json); json||=JbuilderTemplate.new(self); #{source}
|
230
231
|
json.target! unless (__already_defined && __already_defined != "method")}
|
231
232
|
end
|
232
233
|
end
|
data/lib/jbuilder.rb
CHANGED
@@ -2,8 +2,9 @@ require 'jbuilder/jbuilder'
|
|
2
2
|
require 'jbuilder/blank'
|
3
3
|
require 'jbuilder/key_formatter'
|
4
4
|
require 'jbuilder/errors'
|
5
|
-
require '
|
5
|
+
require 'json'
|
6
6
|
require 'ostruct'
|
7
|
+
require 'active_support/core_ext/hash/deep_merge'
|
7
8
|
|
8
9
|
class Jbuilder
|
9
10
|
@@key_formatter = nil
|
@@ -26,12 +27,12 @@ class Jbuilder
|
|
26
27
|
BLANK = Blank.new
|
27
28
|
NON_ENUMERABLES = [ ::Struct, ::OpenStruct ].to_set
|
28
29
|
|
29
|
-
def set!(key, value = BLANK, *args)
|
30
|
+
def set!(key, value = BLANK, *args, &block)
|
30
31
|
result = if ::Kernel.block_given?
|
31
32
|
if !_blank?(value)
|
32
33
|
# json.comments @post.comments { |comment| ... }
|
33
34
|
# { "comments": [ { ... }, { ... } ] }
|
34
|
-
_scope{ array! value,
|
35
|
+
_scope{ array! value, &block }
|
35
36
|
else
|
36
37
|
# json.comments { ... }
|
37
38
|
# { "comments": ... }
|
@@ -42,11 +43,11 @@ class Jbuilder
|
|
42
43
|
# json.age 32
|
43
44
|
# json.person another_jbuilder
|
44
45
|
# { "age": 32, "person": { ... }
|
45
|
-
value.attributes!
|
46
|
+
_format_keys(value.attributes!)
|
46
47
|
else
|
47
48
|
# json.age 32
|
48
49
|
# { "age": 32 }
|
49
|
-
value
|
50
|
+
_format_keys(value)
|
50
51
|
end
|
51
52
|
elsif _is_collection?(value)
|
52
53
|
# json.comments @post.comments, :content, :created_at
|
@@ -61,9 +62,9 @@ class Jbuilder
|
|
61
62
|
_set_value key, result
|
62
63
|
end
|
63
64
|
|
64
|
-
def method_missing(*args)
|
65
|
+
def method_missing(*args, &block)
|
65
66
|
if ::Kernel.block_given?
|
66
|
-
set!(*args,
|
67
|
+
set!(*args, &block)
|
67
68
|
else
|
68
69
|
set!(*args)
|
69
70
|
end
|
@@ -163,7 +164,7 @@ class Jbuilder
|
|
163
164
|
#
|
164
165
|
# [ { "name": David", "age": 32 }, { "name": Jamie", "age": 31 } ]
|
165
166
|
#
|
166
|
-
#
|
167
|
+
# You can use the call syntax instead of an explicit extract! call:
|
167
168
|
#
|
168
169
|
# json.(@people) { |person| ... }
|
169
170
|
#
|
@@ -181,11 +182,11 @@ class Jbuilder
|
|
181
182
|
# json.array! [1, 2, 3]
|
182
183
|
#
|
183
184
|
# [1,2,3]
|
184
|
-
def array!(collection = [], *attributes)
|
185
|
+
def array!(collection = [], *attributes, &block)
|
185
186
|
array = if collection.nil?
|
186
187
|
[]
|
187
188
|
elsif ::Kernel.block_given?
|
188
|
-
_map_collection(collection,
|
189
|
+
_map_collection(collection, &block)
|
189
190
|
elsif attributes.any?
|
190
191
|
_map_collection(collection) { |element| extract! element, *attributes }
|
191
192
|
else
|
@@ -220,9 +221,9 @@ class Jbuilder
|
|
220
221
|
end
|
221
222
|
end
|
222
223
|
|
223
|
-
def call(object, *attributes)
|
224
|
+
def call(object, *attributes, &block)
|
224
225
|
if ::Kernel.block_given?
|
225
|
-
array! object,
|
226
|
+
array! object, &block
|
226
227
|
else
|
227
228
|
extract! object, *attributes
|
228
229
|
end
|
@@ -240,14 +241,15 @@ class Jbuilder
|
|
240
241
|
@attributes
|
241
242
|
end
|
242
243
|
|
243
|
-
# Merges hash or
|
244
|
-
def merge!(
|
244
|
+
# Merges hash, array, or Jbuilder instance into current builder.
|
245
|
+
def merge!(object)
|
246
|
+
hash_or_array = ::Jbuilder === object ? object.attributes! : object
|
245
247
|
@attributes = _merge_values(@attributes, hash_or_array)
|
246
248
|
end
|
247
249
|
|
248
250
|
# Encodes the current builder as JSON.
|
249
251
|
def target!
|
250
|
-
|
252
|
+
@attributes.to_json
|
251
253
|
end
|
252
254
|
|
253
255
|
private
|
@@ -271,11 +273,11 @@ class Jbuilder
|
|
271
273
|
if _blank?(updates)
|
272
274
|
current_value
|
273
275
|
elsif _blank?(current_value) || updates.nil? || current_value.empty? && ::Array === updates
|
274
|
-
updates
|
276
|
+
_format_keys(updates)
|
275
277
|
elsif ::Array === current_value && ::Array === updates
|
276
|
-
current_value + updates
|
278
|
+
current_value + _format_keys(updates)
|
277
279
|
elsif ::Hash === current_value && ::Hash === updates
|
278
|
-
current_value.
|
280
|
+
current_value.deep_merge(_format_keys(updates))
|
279
281
|
else
|
280
282
|
raise MergeError.build(current_value, updates)
|
281
283
|
end
|
@@ -285,6 +287,16 @@ class Jbuilder
|
|
285
287
|
@key_formatter ? @key_formatter.format(key) : key.to_s
|
286
288
|
end
|
287
289
|
|
290
|
+
def _format_keys(hash_or_array)
|
291
|
+
if ::Array === hash_or_array
|
292
|
+
hash_or_array.map { |value| _format_keys(value) }
|
293
|
+
elsif ::Hash === hash_or_array
|
294
|
+
::Hash[hash_or_array.collect { |k, v| [_key(k), _format_keys(v)] }]
|
295
|
+
else
|
296
|
+
hash_or_array
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
288
300
|
def _set_value(key, value)
|
289
301
|
raise NullError.build(key) if @attributes.nil?
|
290
302
|
raise ArrayError.build(key) if ::Array === @attributes
|
@@ -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
|
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
|
@@ -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 %r{json
|
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 %r{json
|
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|
|
30
|
+
|
31
|
+
assert_file 'app/views/posts/_post.json.jbuilder' do |content|
|
32
32
|
assert_match %r{json\.extract! post, :id, :title, :body}, content
|
33
|
+
assert_match %r{:created_at, :updated_at}, content
|
33
34
|
assert_match %r{json\.url post_url\(post, format: :json\)}, content
|
34
35
|
end
|
35
|
-
|
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
|
44
|
+
end
|
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
|