jbuilder 2.0.6 → 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.
- checksums.yaml +5 -5
- data/.github/workflows/ruby.yml +108 -0
- data/.gitignore +4 -1
- data/Appraisals +25 -0
- data/CONTRIBUTING.md +106 -0
- data/Gemfile +4 -12
- data/MIT-LICENSE +1 -1
- data/README.md +171 -45
- data/Rakefile +15 -10
- data/gemfiles/rails_5_0.gemfile +10 -0
- data/gemfiles/rails_5_1.gemfile +10 -0
- data/gemfiles/rails_5_2.gemfile +10 -0
- data/gemfiles/rails_6_0.gemfile +10 -0
- data/gemfiles/rails_6_1.gemfile +10 -0
- data/gemfiles/rails_head.gemfile +10 -0
- data/jbuilder.gemspec +20 -6
- data/lib/generators/rails/jbuilder_generator.rb +13 -2
- data/lib/generators/rails/scaffold_controller_generator.rb +9 -3
- data/lib/generators/rails/templates/api_controller.rb +63 -0
- data/lib/generators/rails/templates/controller.rb +16 -20
- data/lib/generators/rails/templates/index.json.jbuilder +1 -4
- data/lib/generators/rails/templates/partial.json.jbuilder +16 -0
- data/lib/generators/rails/templates/show.json.jbuilder +1 -1
- data/lib/jbuilder/blank.rb +11 -0
- data/lib/jbuilder/collection_renderer.rb +109 -0
- data/lib/jbuilder/dependency_tracker.rb +1 -1
- data/lib/jbuilder/errors.rb +24 -0
- data/lib/jbuilder/jbuilder.rb +7 -0
- data/lib/jbuilder/jbuilder_template.rb +213 -65
- data/lib/jbuilder/key_formatter.rb +34 -0
- data/lib/jbuilder/railtie.rb +31 -6
- data/lib/jbuilder.rb +148 -114
- data/test/jbuilder_dependency_tracker_test.rb +3 -4
- data/test/jbuilder_generator_test.rb +31 -4
- data/test/jbuilder_template_test.rb +313 -195
- data/test/jbuilder_test.rb +615 -219
- data/test/scaffold_api_controller_generator_test.rb +70 -0
- data/test/scaffold_controller_generator_test.rb +62 -19
- data/test/test_helper.rb +36 -0
- metadata +38 -23
- data/.travis.yml +0 -21
- data/CHANGELOG.md +0 -89
- data/Gemfile.old +0 -14
@@ -1,4 +1,5 @@
|
|
1
|
-
require 'jbuilder'
|
1
|
+
require 'jbuilder/jbuilder'
|
2
|
+
require 'jbuilder/collection_renderer'
|
2
3
|
require 'action_dispatch/http/mime_type'
|
3
4
|
require 'active_support/cache'
|
4
5
|
|
@@ -9,34 +10,49 @@ class JbuilderTemplate < Jbuilder
|
|
9
10
|
|
10
11
|
self.template_lookup_options = { handlers: [:jbuilder] }
|
11
12
|
|
12
|
-
def initialize(context, *args
|
13
|
+
def initialize(context, *args)
|
13
14
|
@context = context
|
14
|
-
|
15
|
+
@cached_root = nil
|
16
|
+
super(*args)
|
15
17
|
end
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
19
|
+
# Generates JSON using the template specified with the `:partial` option. For example, the code below will render
|
20
|
+
# the file `views/comments/_comments.json.jbuilder`, and set a local variable comments with all this message's
|
21
|
+
# comments, which can be used inside the partial.
|
22
|
+
#
|
23
|
+
# Example:
|
24
|
+
#
|
25
|
+
# json.partial! 'comments/comments', comments: @message.comments
|
26
|
+
#
|
27
|
+
# There are multiple ways to generate a collection of elements as JSON, as ilustrated below:
|
28
|
+
#
|
29
|
+
# Example:
|
30
|
+
#
|
31
|
+
# json.array! @posts, partial: 'posts/post', as: :post
|
32
|
+
#
|
33
|
+
# # or:
|
34
|
+
# json.partial! 'posts/post', collection: @posts, as: :post
|
35
|
+
#
|
36
|
+
# # or:
|
37
|
+
# json.partial! partial: 'posts/post', collection: @posts, as: :post
|
38
|
+
#
|
39
|
+
# # or:
|
40
|
+
# json.comments @post.comments, partial: 'comments/comment', as: :comment
|
41
|
+
#
|
42
|
+
# Aside from that, the `:cached` options is available on Rails >= 6.0. This will cache the rendered results
|
43
|
+
# effectively using the multi fetch feature.
|
44
|
+
#
|
45
|
+
# Example:
|
46
|
+
#
|
47
|
+
# json.array! @posts, partial: "posts/post", as: :post, cached: true
|
48
|
+
#
|
49
|
+
# json.comments @post.comments, partial: "comments/comment", as: :comment, cached: true
|
50
|
+
#
|
51
|
+
def partial!(*args)
|
52
|
+
if args.one? && _is_active_model?(args.first)
|
53
|
+
_render_active_model_partial args.first
|
38
54
|
else
|
39
|
-
|
55
|
+
_render_explicit_partial(*args)
|
40
56
|
end
|
41
57
|
end
|
42
58
|
|
@@ -48,9 +64,9 @@ class JbuilderTemplate < Jbuilder
|
|
48
64
|
# json.cache! ['v1', @person], expires_in: 10.minutes do
|
49
65
|
# json.extract! @person, :name, :age
|
50
66
|
# end
|
51
|
-
def cache!(key=nil, options={}
|
67
|
+
def cache!(key=nil, options={})
|
52
68
|
if @context.controller.perform_caching
|
53
|
-
value =
|
69
|
+
value = _cache_fragment_for(key, options) do
|
54
70
|
_scope { yield self }
|
55
71
|
end
|
56
72
|
|
@@ -60,7 +76,28 @@ class JbuilderTemplate < Jbuilder
|
|
60
76
|
end
|
61
77
|
end
|
62
78
|
|
63
|
-
#
|
79
|
+
# Caches the json structure at the root using a string rather than the hash structure. This is considerably
|
80
|
+
# faster, but the drawback is that it only works, as the name hints, at the root. So you cannot
|
81
|
+
# use this approach to cache deeper inside the hierarchy, like in partials or such. Continue to use #cache! there.
|
82
|
+
#
|
83
|
+
# Example:
|
84
|
+
#
|
85
|
+
# json.cache_root! @person do
|
86
|
+
# json.extract! @person, :name, :age
|
87
|
+
# end
|
88
|
+
#
|
89
|
+
# # json.extra 'This will not work either, the root must be exclusive'
|
90
|
+
def cache_root!(key=nil, options={})
|
91
|
+
if @context.controller.perform_caching
|
92
|
+
raise "cache_root! can't be used after JSON structures have been defined" if @attributes.present?
|
93
|
+
|
94
|
+
@cached_root = _cache_fragment_for([ :root, key ], options) { yield; target! }
|
95
|
+
else
|
96
|
+
yield
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Conditionally caches the json depending in the condition given as first parameter. Has the same
|
64
101
|
# signature as the `cache` helper method in `ActionView::Helpers::CacheHelper` and so can be used in
|
65
102
|
# the same way.
|
66
103
|
#
|
@@ -73,60 +110,171 @@ class JbuilderTemplate < Jbuilder
|
|
73
110
|
condition ? cache!(*args, &block) : yield
|
74
111
|
end
|
75
112
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
113
|
+
def target!
|
114
|
+
@cached_root || super
|
115
|
+
end
|
116
|
+
|
117
|
+
def array!(collection = [], *args)
|
118
|
+
options = args.first
|
81
119
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
120
|
+
if args.one? && _partial_options?(options)
|
121
|
+
partial! options.merge(collection: collection)
|
122
|
+
else
|
123
|
+
super
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def set!(name, object = BLANK, *args)
|
128
|
+
options = args.first
|
129
|
+
|
130
|
+
if args.one? && _partial_options?(options)
|
131
|
+
_set_inline_partial name, object, options
|
132
|
+
else
|
133
|
+
super
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
|
139
|
+
def _render_partial_with_options(options)
|
140
|
+
options.reverse_merge! locals: options.except(:partial, :as, :collection, :cached)
|
141
|
+
options.reverse_merge! ::JbuilderTemplate.template_lookup_options
|
142
|
+
as = options[:as]
|
143
|
+
|
144
|
+
if as && options.key?(:collection) && CollectionRenderer.supported?
|
145
|
+
collection = options.delete(:collection) || []
|
146
|
+
partial = options.delete(:partial)
|
147
|
+
options[:locals].merge!(json: self)
|
148
|
+
|
149
|
+
if options.has_key?(:layout)
|
150
|
+
raise ::NotImplementedError, "The `:layout' option is not supported in collection rendering."
|
151
|
+
end
|
152
|
+
|
153
|
+
if options.has_key?(:spacer_template)
|
154
|
+
raise ::NotImplementedError, "The `:spacer_template' option is not supported in collection rendering."
|
155
|
+
end
|
156
|
+
|
157
|
+
results = CollectionRenderer
|
158
|
+
.new(@context.lookup_context, options) { |&block| _scope(&block) }
|
159
|
+
.render_collection_with_partial(collection, partial, @context, nil)
|
160
|
+
|
161
|
+
array! if results.respond_to?(:body) && results.body.nil?
|
162
|
+
elsif as && options.key?(:collection) && !CollectionRenderer.supported?
|
163
|
+
# For Rails <= 5.2:
|
164
|
+
as = as.to_sym
|
165
|
+
collection = options.delete(:collection)
|
166
|
+
locals = options.delete(:locals)
|
167
|
+
array! collection do |member|
|
168
|
+
member_locals = locals.clone
|
169
|
+
member_locals.merge! collection: collection
|
170
|
+
member_locals.merge! as => member
|
171
|
+
_render_partial options.merge(locals: member_locals)
|
91
172
|
end
|
173
|
+
else
|
174
|
+
_render_partial options
|
92
175
|
end
|
176
|
+
end
|
93
177
|
|
94
|
-
|
95
|
-
|
96
|
-
|
178
|
+
def _render_partial(options)
|
179
|
+
options[:locals].merge! json: self
|
180
|
+
@context.render options
|
181
|
+
end
|
182
|
+
|
183
|
+
def _cache_fragment_for(key, options, &block)
|
184
|
+
key = _cache_key(key, options)
|
185
|
+
_read_fragment_cache(key, options) || _write_fragment_cache(key, options, &block)
|
186
|
+
end
|
187
|
+
|
188
|
+
def _read_fragment_cache(key, options = nil)
|
189
|
+
@context.controller.instrument_fragment_cache :read_fragment, key do
|
190
|
+
::Rails.cache.read(key, options)
|
97
191
|
end
|
192
|
+
end
|
98
193
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
@context.cache_fragment_name(key, options)
|
104
|
-
elsif @context.respond_to?(:fragment_name_with_digest)
|
105
|
-
# Backwards compatibility for period of time when fragment_name_with_digest was made public.
|
106
|
-
@context.fragment_name_with_digest(key)
|
107
|
-
else
|
108
|
-
::ActiveSupport::Cache.expand_cache_key(key.is_a?(::Hash) ? url_for(key).split('://').last : key, :jbuilder)
|
194
|
+
def _write_fragment_cache(key, options = nil)
|
195
|
+
@context.controller.instrument_fragment_cache :write_fragment, key do
|
196
|
+
yield.tap do |value|
|
197
|
+
::Rails.cache.write(key, value, options)
|
109
198
|
end
|
110
199
|
end
|
200
|
+
end
|
111
201
|
|
112
|
-
|
202
|
+
def _cache_key(key, options)
|
203
|
+
name_options = options.slice(:skip_digest, :virtual_path)
|
204
|
+
key = _fragment_name_with_digest(key, name_options)
|
205
|
+
|
206
|
+
if @context.respond_to?(:combined_fragment_cache_key)
|
207
|
+
key = @context.combined_fragment_cache_key(key)
|
208
|
+
else
|
209
|
+
key = url_for(key).split('://', 2).last if ::Hash === key
|
210
|
+
end
|
211
|
+
|
212
|
+
::ActiveSupport::Cache.expand_cache_key(key, :jbuilder)
|
213
|
+
end
|
214
|
+
|
215
|
+
def _fragment_name_with_digest(key, options)
|
216
|
+
if @context.respond_to?(:cache_fragment_name)
|
217
|
+
@context.cache_fragment_name(key, **options)
|
218
|
+
else
|
219
|
+
key
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def _partial_options?(options)
|
224
|
+
::Hash === options && options.key?(:as) && options.key?(:partial)
|
225
|
+
end
|
226
|
+
|
227
|
+
def _is_active_model?(object)
|
228
|
+
object.class.respond_to?(:model_name) && object.respond_to?(:to_partial_path)
|
229
|
+
end
|
113
230
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
231
|
+
def _set_inline_partial(name, object, options)
|
232
|
+
value = if object.nil?
|
233
|
+
[]
|
234
|
+
elsif _is_collection?(object)
|
235
|
+
_scope{ _render_partial_with_options options.merge(collection: object) }
|
236
|
+
else
|
237
|
+
locals = ::Hash[options[:as], object]
|
238
|
+
_scope{ _render_partial_with_options options.merge(locals: locals) }
|
118
239
|
end
|
240
|
+
|
241
|
+
set! name, value
|
242
|
+
end
|
243
|
+
|
244
|
+
def _render_explicit_partial(name_or_options, locals = {})
|
245
|
+
case name_or_options
|
246
|
+
when ::Hash
|
247
|
+
# partial! partial: 'name', foo: 'bar'
|
248
|
+
options = name_or_options
|
249
|
+
else
|
250
|
+
# partial! 'name', locals: {foo: 'bar'}
|
251
|
+
if locals.one? && (locals.keys.first == :locals)
|
252
|
+
options = locals.merge(partial: name_or_options)
|
253
|
+
else
|
254
|
+
options = { partial: name_or_options, locals: locals }
|
255
|
+
end
|
256
|
+
# partial! 'name', foo: 'bar'
|
257
|
+
as = locals.delete(:as)
|
258
|
+
options[:as] = as if as.present?
|
259
|
+
options[:collection] = locals[:collection] if locals.key?(:collection)
|
260
|
+
end
|
261
|
+
|
262
|
+
_render_partial_with_options options
|
263
|
+
end
|
264
|
+
|
265
|
+
def _render_active_model_partial(object)
|
266
|
+
@context.render object, json: self
|
267
|
+
end
|
119
268
|
end
|
120
269
|
|
121
270
|
class JbuilderHandler
|
122
271
|
cattr_accessor :default_format
|
123
|
-
self.default_format =
|
272
|
+
self.default_format = :json
|
124
273
|
|
125
|
-
def self.call(template)
|
274
|
+
def self.call(template, source = nil)
|
275
|
+
source ||= template.source
|
126
276
|
# this juggling is required to keep line numbers right in the error
|
127
|
-
%{__already_defined = defined?(json); json||=JbuilderTemplate.new(self); #{
|
277
|
+
%{__already_defined = defined?(json); json||=JbuilderTemplate.new(self); #{source}
|
128
278
|
json.target! unless (__already_defined && __already_defined != "method")}
|
129
279
|
end
|
130
280
|
end
|
131
|
-
|
132
|
-
ActionView::Template.register_template_handler :jbuilder, JbuilderHandler
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'jbuilder/jbuilder'
|
2
|
+
require 'active_support/core_ext/array'
|
3
|
+
|
4
|
+
class Jbuilder
|
5
|
+
class KeyFormatter
|
6
|
+
def initialize(*args)
|
7
|
+
@format = {}
|
8
|
+
@cache = {}
|
9
|
+
|
10
|
+
options = args.extract_options!
|
11
|
+
args.each do |name|
|
12
|
+
@format[name] = []
|
13
|
+
end
|
14
|
+
options.each do |name, parameters|
|
15
|
+
@format[name] = parameters
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize_copy(original)
|
20
|
+
@cache = {}
|
21
|
+
end
|
22
|
+
|
23
|
+
def format(key)
|
24
|
+
@cache[key] ||= @format.inject(key.to_s) do |result, args|
|
25
|
+
func, args = args
|
26
|
+
if ::Proc === func
|
27
|
+
func.call result, *args
|
28
|
+
else
|
29
|
+
result.send func, *args
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/jbuilder/railtie.rb
CHANGED
@@ -1,11 +1,36 @@
|
|
1
|
-
require 'rails
|
1
|
+
require 'rails'
|
2
|
+
require 'jbuilder/jbuilder_template'
|
2
3
|
|
3
4
|
class Jbuilder
|
4
5
|
class Railtie < ::Rails::Railtie
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
initializer :jbuilder do
|
7
|
+
ActiveSupport.on_load :action_view do
|
8
|
+
ActionView::Template.register_template_handler :jbuilder, JbuilderHandler
|
9
|
+
require 'jbuilder/dependency_tracker'
|
10
|
+
end
|
11
|
+
|
12
|
+
if Rails::VERSION::MAJOR >= 5
|
13
|
+
module ::ActionController
|
14
|
+
module ApiRendering
|
15
|
+
include ActionView::Rendering
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
ActiveSupport.on_load :action_controller do
|
20
|
+
if self == ActionController::API
|
21
|
+
include ActionController::Helpers
|
22
|
+
include ActionController::ImplicitRender
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
if Rails::VERSION::MAJOR >= 4
|
29
|
+
generators do |app|
|
30
|
+
Rails::Generators.configure! app.config.generators
|
31
|
+
Rails::Generators.hidden_namespaces.uniq!
|
32
|
+
require 'generators/rails/scaffold_controller_generator'
|
33
|
+
end
|
9
34
|
end
|
10
35
|
end
|
11
|
-
end
|
36
|
+
end
|