jbuilder 2.2.16 → 2.6.4
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 +4 -4
- data/.travis.yml +39 -1
- data/Appraisals +30 -12
- data/CHANGELOG.md +64 -4
- data/CONTRIBUTING.md +108 -0
- data/Gemfile +1 -0
- data/MIT-LICENSE +1 -1
- data/README.md +24 -15
- data/gemfiles/rails_3_0.gemfile +2 -0
- data/gemfiles/rails_3_1.gemfile +2 -0
- data/gemfiles/rails_3_2.gemfile +2 -0
- data/gemfiles/rails_4_0.gemfile +2 -0
- data/gemfiles/rails_4_1.gemfile +2 -0
- data/gemfiles/rails_4_2.gemfile +3 -0
- data/gemfiles/rails_5_0.gemfile +13 -0
- data/gemfiles/rails_5_1.gemfile +13 -0
- data/jbuilder.gemspec +5 -5
- data/lib/generators/rails/jbuilder_generator.rb +1 -0
- data/lib/generators/rails/scaffold_controller_generator.rb +2 -2
- data/lib/generators/rails/templates/api_controller.rb +63 -0
- data/lib/generators/rails/templates/controller.rb +1 -1
- data/lib/generators/rails/templates/index.json.jbuilder +1 -4
- data/lib/generators/rails/templates/partial.json.jbuilder +2 -0
- data/lib/generators/rails/templates/show.json.jbuilder +1 -1
- data/lib/jbuilder/errors.rb +7 -0
- data/lib/jbuilder/jbuilder_template.rb +126 -39
- data/lib/jbuilder/key_formatter.rb +2 -2
- data/lib/jbuilder/railtie.rb +12 -3
- data/lib/jbuilder.rb +21 -10
- data/test/jbuilder_generator_test.rb +10 -4
- data/test/jbuilder_template_test.rb +254 -156
- data/test/jbuilder_test.rb +28 -3
- data/test/scaffold_api_controller_generator_test.rb +55 -0
- data/test/scaffold_controller_generator_test.rb +26 -17
- metadata +12 -13
@@ -1 +1 @@
|
|
1
|
-
json.
|
1
|
+
json.partial! "<%= plural_table_name %>/<%= singular_table_name %>", <%= singular_table_name %>: @<%= singular_table_name %>
|
data/lib/jbuilder/errors.rb
CHANGED
@@ -11,37 +11,15 @@ class JbuilderTemplate < Jbuilder
|
|
11
11
|
|
12
12
|
def initialize(context, *args)
|
13
13
|
@context = context
|
14
|
+
@cached_root = nil
|
14
15
|
super(*args)
|
15
16
|
end
|
16
17
|
|
17
|
-
def partial!(
|
18
|
-
|
19
|
-
|
20
|
-
# partial! partial: 'name', foo: 'bar'
|
21
|
-
options = name_or_options
|
22
|
-
else
|
23
|
-
# partial! 'name', locals: {foo: 'bar'}
|
24
|
-
if locals.one? && (locals.keys.first == :locals)
|
25
|
-
options = locals.merge(partial: name_or_options)
|
26
|
-
else
|
27
|
-
options = { partial: name_or_options, locals: locals }
|
28
|
-
end
|
29
|
-
# partial! 'name', foo: 'bar'
|
30
|
-
as = locals.delete(:as)
|
31
|
-
options[:as] = as if as.present?
|
32
|
-
options[:collection] = locals[:collection] if locals.key?(:collection)
|
33
|
-
end
|
34
|
-
|
35
|
-
_render_partial_with_options options
|
36
|
-
end
|
37
|
-
|
38
|
-
def array!(collection = [], *attributes)
|
39
|
-
options = attributes.extract_options!
|
40
|
-
|
41
|
-
if options.key?(:partial)
|
42
|
-
partial! options[:partial], options.merge(collection: collection)
|
18
|
+
def partial!(*args)
|
19
|
+
if args.one? && _is_active_model?(args.first)
|
20
|
+
_render_active_model_partial args.first
|
43
21
|
else
|
44
|
-
|
22
|
+
_render_explicit_partial(*args)
|
45
23
|
end
|
46
24
|
end
|
47
25
|
|
@@ -55,7 +33,7 @@ class JbuilderTemplate < Jbuilder
|
|
55
33
|
# end
|
56
34
|
def cache!(key=nil, options={})
|
57
35
|
if @context.controller.perform_caching
|
58
|
-
value =
|
36
|
+
value = _cache_fragment_for(key, options) do
|
59
37
|
_scope { yield self }
|
60
38
|
end
|
61
39
|
|
@@ -65,7 +43,28 @@ class JbuilderTemplate < Jbuilder
|
|
65
43
|
end
|
66
44
|
end
|
67
45
|
|
68
|
-
#
|
46
|
+
# Caches the json structure at the root using a string rather than the hash structure. This is considerably
|
47
|
+
# faster, but the drawback is that it only works, as the name hints, at the root. So you cannot
|
48
|
+
# use this approach to cache deeper inside the hierarchy, like in partials or such. Continue to use #cache! there.
|
49
|
+
#
|
50
|
+
# Example:
|
51
|
+
#
|
52
|
+
# json.cache_root! @person do
|
53
|
+
# json.extract! @person, :name, :age
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
# # json.extra 'This will not work either, the root must be exclusive'
|
57
|
+
def cache_root!(key=nil, options={})
|
58
|
+
if @context.controller.perform_caching
|
59
|
+
raise "cache_root! can't be used after JSON structures have been defined" if @attributes.present?
|
60
|
+
|
61
|
+
@cached_root = _cache_fragment_for([ :root, key ], options) { yield; target! }
|
62
|
+
else
|
63
|
+
yield
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Conditionally caches the json depending in the condition given as first parameter. Has the same
|
69
68
|
# signature as the `cache` helper method in `ActionView::Helpers::CacheHelper` and so can be used in
|
70
69
|
# the same way.
|
71
70
|
#
|
@@ -78,7 +77,31 @@ class JbuilderTemplate < Jbuilder
|
|
78
77
|
condition ? cache!(*args, &::Proc.new) : yield
|
79
78
|
end
|
80
79
|
|
81
|
-
|
80
|
+
def target!
|
81
|
+
@cached_root || super
|
82
|
+
end
|
83
|
+
|
84
|
+
def array!(collection = [], *args)
|
85
|
+
options = args.first
|
86
|
+
|
87
|
+
if args.one? && _partial_options?(options)
|
88
|
+
partial! options.merge(collection: collection)
|
89
|
+
else
|
90
|
+
super
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def set!(name, object = BLANK, *args)
|
95
|
+
options = args.first
|
96
|
+
|
97
|
+
if args.one? && _partial_options?(options)
|
98
|
+
_set_inline_partial name, object, options
|
99
|
+
else
|
100
|
+
super
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
82
105
|
|
83
106
|
def _render_partial_with_options(options)
|
84
107
|
options.reverse_merge! locals: {}
|
@@ -105,14 +128,38 @@ class JbuilderTemplate < Jbuilder
|
|
105
128
|
@context.render options
|
106
129
|
end
|
107
130
|
|
131
|
+
def _cache_fragment_for(key, options, &block)
|
132
|
+
key = _cache_key(key, options)
|
133
|
+
_read_fragment_cache(key, options) || _write_fragment_cache(key, options, &block)
|
134
|
+
end
|
135
|
+
|
136
|
+
def _read_fragment_cache(key, options = nil)
|
137
|
+
@context.controller.instrument_fragment_cache :read_fragment, key do
|
138
|
+
::Rails.cache.read(key, options)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def _write_fragment_cache(key, options = nil)
|
143
|
+
@context.controller.instrument_fragment_cache :write_fragment, key do
|
144
|
+
yield.tap do |value|
|
145
|
+
::Rails.cache.write(key, value, options)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
108
150
|
def _cache_key(key, options)
|
109
|
-
|
110
|
-
key =
|
151
|
+
name_options = options.slice(:skip_digest, :virtual_path)
|
152
|
+
key = _fragment_name_with_digest(key, name_options)
|
153
|
+
|
154
|
+
if @context.respond_to?(:fragment_cache_key)
|
155
|
+
key = @context.fragment_cache_key(key)
|
156
|
+
else
|
157
|
+
key = url_for(key).split('://', 2).last if ::Hash === key
|
158
|
+
end
|
159
|
+
|
111
160
|
::ActiveSupport::Cache.expand_cache_key(key, :jbuilder)
|
112
161
|
end
|
113
162
|
|
114
|
-
private
|
115
|
-
|
116
163
|
def _fragment_name_with_digest(key, options)
|
117
164
|
if @context.respond_to?(:cache_fragment_name)
|
118
165
|
# Current compatibility, fragment_name_with_digest is private again and cache_fragment_name
|
@@ -126,16 +173,56 @@ class JbuilderTemplate < Jbuilder
|
|
126
173
|
end
|
127
174
|
end
|
128
175
|
|
129
|
-
def
|
130
|
-
|
131
|
-
|
132
|
-
|
176
|
+
def _partial_options?(options)
|
177
|
+
::Hash === options && options.key?(:as) && options.key?(:partial)
|
178
|
+
end
|
179
|
+
|
180
|
+
def _is_active_model?(object)
|
181
|
+
object.class.respond_to?(:model_name) && object.respond_to?(:to_partial_path)
|
182
|
+
end
|
183
|
+
|
184
|
+
def _set_inline_partial(name, object, options)
|
185
|
+
value = if object.nil?
|
186
|
+
[]
|
187
|
+
elsif _is_collection?(object)
|
188
|
+
_scope{ _render_partial_with_options options.merge(collection: object) }
|
189
|
+
else
|
190
|
+
locals = ::Hash[options[:as], object]
|
191
|
+
_scope{ _render_partial options.merge(locals: locals) }
|
192
|
+
end
|
193
|
+
|
194
|
+
set! name, value
|
195
|
+
end
|
196
|
+
|
197
|
+
def _render_explicit_partial(name_or_options, locals = {})
|
198
|
+
case name_or_options
|
199
|
+
when ::Hash
|
200
|
+
# partial! partial: 'name', foo: 'bar'
|
201
|
+
options = name_or_options
|
202
|
+
else
|
203
|
+
# partial! 'name', locals: {foo: 'bar'}
|
204
|
+
if locals.one? && (locals.keys.first == :locals)
|
205
|
+
options = locals.merge(partial: name_or_options)
|
206
|
+
else
|
207
|
+
options = { partial: name_or_options, locals: locals }
|
208
|
+
end
|
209
|
+
# partial! 'name', foo: 'bar'
|
210
|
+
as = locals.delete(:as)
|
211
|
+
options[:as] = as if as.present?
|
212
|
+
options[:collection] = locals[:collection] if locals.key?(:collection)
|
213
|
+
end
|
214
|
+
|
215
|
+
_render_partial_with_options options
|
216
|
+
end
|
217
|
+
|
218
|
+
def _render_active_model_partial(object)
|
219
|
+
@context.render object, json: self
|
133
220
|
end
|
134
221
|
end
|
135
222
|
|
136
223
|
class JbuilderHandler
|
137
224
|
cattr_accessor :default_format
|
138
|
-
self.default_format = Mime
|
225
|
+
self.default_format = Mime[:json]
|
139
226
|
|
140
227
|
def self.call(template)
|
141
228
|
# this juggling is required to keep line numbers right in the error
|
data/lib/jbuilder/railtie.rb
CHANGED
@@ -3,15 +3,24 @@ require 'jbuilder/jbuilder_template'
|
|
3
3
|
|
4
4
|
class Jbuilder
|
5
5
|
class Railtie < ::Rails::Railtie
|
6
|
-
initializer :jbuilder do
|
6
|
+
initializer :jbuilder do
|
7
7
|
ActiveSupport.on_load :action_view do
|
8
8
|
ActionView::Template.register_template_handler :jbuilder, JbuilderHandler
|
9
9
|
require 'jbuilder/dependency_tracker'
|
10
10
|
end
|
11
11
|
|
12
|
-
if
|
12
|
+
if Rails::VERSION::MAJOR >= 5
|
13
|
+
module ::ActionController
|
14
|
+
module ApiRendering
|
15
|
+
include ActionView::Rendering
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
13
19
|
ActiveSupport.on_load :action_controller do
|
14
|
-
|
20
|
+
if self == ActionController::API
|
21
|
+
include ActionController::Helpers
|
22
|
+
include ActionController::ImplicitRender
|
23
|
+
end
|
15
24
|
end
|
16
25
|
end
|
17
26
|
end
|
data/lib/jbuilder.rb
CHANGED
@@ -3,6 +3,7 @@ require 'jbuilder/blank'
|
|
3
3
|
require 'jbuilder/key_formatter'
|
4
4
|
require 'jbuilder/errors'
|
5
5
|
require 'multi_json'
|
6
|
+
require 'ostruct'
|
6
7
|
|
7
8
|
class Jbuilder
|
8
9
|
@@key_formatter = nil
|
@@ -23,6 +24,7 @@ class Jbuilder
|
|
23
24
|
end
|
24
25
|
|
25
26
|
BLANK = Blank.new
|
27
|
+
NON_ENUMERABLES = [ ::Struct, ::OpenStruct ].to_set
|
26
28
|
|
27
29
|
def set!(key, value = BLANK, *args)
|
28
30
|
result = if ::Kernel.block_given?
|
@@ -46,7 +48,7 @@ class Jbuilder
|
|
46
48
|
# { "age": 32 }
|
47
49
|
value
|
48
50
|
end
|
49
|
-
elsif
|
51
|
+
elsif _is_collection?(value)
|
50
52
|
# json.comments @post.comments, :content, :created_at
|
51
53
|
# { "comments": [ { "content": "hello", "created_at": "..." }, { "content": "world", "created_at": "..." } ] }
|
52
54
|
_scope{ array! value, *args }
|
@@ -59,8 +61,13 @@ class Jbuilder
|
|
59
61
|
_set_value key, result
|
60
62
|
end
|
61
63
|
|
62
|
-
|
63
|
-
|
64
|
+
def method_missing(*args)
|
65
|
+
if ::Kernel.block_given?
|
66
|
+
set!(*args, &::Proc.new)
|
67
|
+
else
|
68
|
+
set!(*args)
|
69
|
+
end
|
70
|
+
end
|
64
71
|
|
65
72
|
# Specifies formatting to be applied to the key. Passing in a name of a function
|
66
73
|
# will cause that function to be called on the key. So :upcase will upper case
|
@@ -263,14 +270,14 @@ class Jbuilder
|
|
263
270
|
def _merge_values(current_value, updates)
|
264
271
|
if _blank?(updates)
|
265
272
|
current_value
|
266
|
-
elsif _blank?(current_value) || updates.nil?
|
273
|
+
elsif _blank?(current_value) || updates.nil? || current_value.empty? && ::Array === updates
|
267
274
|
updates
|
268
|
-
elsif ::Array === updates
|
269
|
-
|
270
|
-
elsif ::Hash === current_value
|
275
|
+
elsif ::Array === current_value && ::Array === updates
|
276
|
+
current_value + updates
|
277
|
+
elsif ::Hash === current_value && ::Hash === updates
|
271
278
|
current_value.merge(updates)
|
272
279
|
else
|
273
|
-
raise
|
280
|
+
raise MergeError.build(current_value, updates)
|
274
281
|
end
|
275
282
|
end
|
276
283
|
|
@@ -301,13 +308,17 @@ class Jbuilder
|
|
301
308
|
@attributes, @key_formatter = parent_attributes, parent_formatter
|
302
309
|
end
|
303
310
|
|
304
|
-
def
|
305
|
-
|
311
|
+
def _is_collection?(object)
|
312
|
+
_object_respond_to?(object, :map, :count) && NON_ENUMERABLES.none?{ |klass| klass === object }
|
306
313
|
end
|
307
314
|
|
308
315
|
def _blank?(value=@attributes)
|
309
316
|
BLANK == value
|
310
317
|
end
|
318
|
+
|
319
|
+
def _object_respond_to?(object, *methods)
|
320
|
+
methods.all?{ |m| object.respond_to?(m) }
|
321
|
+
end
|
311
322
|
end
|
312
323
|
|
313
324
|
require 'jbuilder/railtie' if defined?(Rails)
|
@@ -14,19 +14,25 @@ class JbuilderGeneratorTest < Rails::Generators::TestCase
|
|
14
14
|
%w(index show).each do |view|
|
15
15
|
assert_file "app/views/posts/#{view}.json.jbuilder"
|
16
16
|
end
|
17
|
+
assert_file "app/views/posts/_post.json.jbuilder"
|
17
18
|
end
|
18
19
|
|
19
20
|
test 'index content' do
|
20
21
|
run_generator
|
21
22
|
|
22
23
|
assert_file 'app/views/posts/index.json.jbuilder' do |content|
|
23
|
-
assert_match
|
24
|
-
assert_match /json\.extract! post, :id, :title, :body/, content
|
25
|
-
assert_match /json\.url post_url\(post, format: :json\)/, content
|
24
|
+
assert_match %r{json.array! @posts, partial: 'posts/post', as: :post}, content
|
26
25
|
end
|
27
26
|
|
28
27
|
assert_file 'app/views/posts/show.json.jbuilder' do |content|
|
29
|
-
assert_match
|
28
|
+
assert_match %r{json.partial! \"posts/post\", post: @post}, content
|
30
29
|
end
|
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{json\.url post_url\(post, format: :json\)}, content
|
34
|
+
end
|
35
|
+
|
36
|
+
|
31
37
|
end
|
32
38
|
end
|