jbuilder 2.6.0 → 2.11.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/ruby.yml +108 -0
- data/.gitignore +2 -0
- data/Appraisals +16 -35
- data/CONTRIBUTING.md +9 -10
- data/Gemfile +0 -1
- data/MIT-LICENSE +1 -1
- data/README.md +100 -20
- data/Rakefile +2 -6
- data/gemfiles/rails_5_0.gemfile +3 -6
- 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 +12 -2
- data/lib/generators/rails/scaffold_controller_generator.rb +7 -1
- data/lib/generators/rails/templates/api_controller.rb +4 -4
- data/lib/generators/rails/templates/controller.rb +15 -19
- data/lib/generators/rails/templates/index.json.jbuilder +1 -1
- data/lib/generators/rails/templates/partial.json.jbuilder +16 -2
- data/lib/generators/rails/templates/show.json.jbuilder +1 -1
- data/lib/jbuilder/collection_renderer.rb +109 -0
- data/lib/jbuilder/errors.rb +7 -0
- data/lib/jbuilder/jbuilder_template.rb +90 -16
- data/lib/jbuilder/key_formatter.rb +2 -2
- data/lib/jbuilder/railtie.rb +1 -1
- data/lib/jbuilder.rb +70 -28
- data/test/jbuilder_dependency_tracker_test.rb +2 -2
- data/test/jbuilder_generator_test.rb +27 -7
- data/test/jbuilder_template_test.rb +281 -295
- data/test/jbuilder_test.rb +256 -4
- data/test/scaffold_api_controller_generator_test.rb +29 -14
- data/test/scaffold_controller_generator_test.rb +54 -21
- data/test/test_helper.rb +30 -8
- metadata +25 -31
- data/.travis.yml +0 -44
- data/CHANGELOG.md +0 -229
- 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 -13
@@ -1,10 +1,10 @@
|
|
1
1
|
<% if namespaced? -%>
|
2
|
-
require_dependency "<%=
|
2
|
+
require_dependency "<%= namespaced_path %>/application_controller"
|
3
3
|
|
4
4
|
<% end -%>
|
5
5
|
<% module_namespacing do -%>
|
6
6
|
class <%= controller_class_name %>Controller < ApplicationController
|
7
|
-
before_action :set_<%= singular_table_name %>, only: [
|
7
|
+
before_action :set_<%= singular_table_name %>, only: %i[ show update destroy ]
|
8
8
|
|
9
9
|
# GET <%= route_url %>
|
10
10
|
# GET <%= route_url %>.json
|
@@ -51,12 +51,12 @@ class <%= controller_class_name %>Controller < ApplicationController
|
|
51
51
|
@<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
|
52
52
|
end
|
53
53
|
|
54
|
-
#
|
54
|
+
# Only allow a list of trusted parameters through.
|
55
55
|
def <%= "#{singular_table_name}_params" %>
|
56
56
|
<%- if attributes_names.empty? -%>
|
57
57
|
params.fetch(<%= ":#{singular_table_name}" %>, {})
|
58
58
|
<%- else -%>
|
59
|
-
params.require(<%= ":#{singular_table_name}" %>).permit(<%=
|
59
|
+
params.require(<%= ":#{singular_table_name}" %>).permit(<%= permitted_params %>)
|
60
60
|
<%- end -%>
|
61
61
|
end
|
62
62
|
end
|
@@ -1,19 +1,17 @@
|
|
1
1
|
<% if namespaced? -%>
|
2
|
-
require_dependency "<%=
|
2
|
+
require_dependency "<%= namespaced_path %>/application_controller"
|
3
3
|
|
4
4
|
<% end -%>
|
5
5
|
<% module_namespacing do -%>
|
6
6
|
class <%= controller_class_name %>Controller < ApplicationController
|
7
|
-
before_action :set_<%= singular_table_name %>, only: [
|
7
|
+
before_action :set_<%= singular_table_name %>, only: %i[ show edit update destroy ]
|
8
8
|
|
9
|
-
# GET <%= route_url %>
|
10
|
-
# GET <%= route_url %>.json
|
9
|
+
# GET <%= route_url %> or <%= route_url %>.json
|
11
10
|
def index
|
12
11
|
@<%= plural_table_name %> = <%= orm_class.all(class_name) %>
|
13
12
|
end
|
14
13
|
|
15
|
-
# GET <%= route_url %>/1
|
16
|
-
# GET <%= route_url %>/1.json
|
14
|
+
# GET <%= route_url %>/1 or <%= route_url %>/1.json
|
17
15
|
def show
|
18
16
|
end
|
19
17
|
|
@@ -26,42 +24,40 @@ class <%= controller_class_name %>Controller < ApplicationController
|
|
26
24
|
def edit
|
27
25
|
end
|
28
26
|
|
29
|
-
# POST <%= route_url %>
|
30
|
-
# POST <%= route_url %>.json
|
27
|
+
# POST <%= route_url %> or <%= route_url %>.json
|
31
28
|
def create
|
32
29
|
@<%= singular_table_name %> = <%= orm_class.build(class_name, "#{singular_table_name}_params") %>
|
33
30
|
|
34
31
|
respond_to do |format|
|
35
32
|
if @<%= orm_instance.save %>
|
36
|
-
format.html { redirect_to
|
33
|
+
format.html { redirect_to <%= show_helper %>, notice: <%= %("#{human_name} was successfully created.") %> }
|
37
34
|
format.json { render :show, status: :created, location: <%= "@#{singular_table_name}" %> }
|
38
35
|
else
|
39
|
-
format.html { render :new }
|
36
|
+
format.html { render :new, status: :unprocessable_entity }
|
40
37
|
format.json { render json: <%= "@#{orm_instance.errors}" %>, status: :unprocessable_entity }
|
41
38
|
end
|
42
39
|
end
|
43
40
|
end
|
44
41
|
|
45
|
-
# PATCH/PUT <%= route_url %>/1
|
46
|
-
# PATCH/PUT <%= route_url %>/1.json
|
42
|
+
# PATCH/PUT <%= route_url %>/1 or <%= route_url %>/1.json
|
47
43
|
def update
|
48
44
|
respond_to do |format|
|
49
45
|
if @<%= orm_instance.update("#{singular_table_name}_params") %>
|
50
|
-
format.html { redirect_to
|
46
|
+
format.html { redirect_to <%= show_helper %>, notice: <%= %("#{human_name} was successfully updated.") %> }
|
51
47
|
format.json { render :show, status: :ok, location: <%= "@#{singular_table_name}" %> }
|
52
48
|
else
|
53
|
-
format.html { render :edit }
|
49
|
+
format.html { render :edit, status: :unprocessable_entity }
|
54
50
|
format.json { render json: <%= "@#{orm_instance.errors}" %>, status: :unprocessable_entity }
|
55
51
|
end
|
56
52
|
end
|
57
53
|
end
|
58
54
|
|
59
|
-
# DELETE <%= route_url %>/1
|
60
|
-
# DELETE <%= route_url %>/1.json
|
55
|
+
# DELETE <%= route_url %>/1 or <%= route_url %>/1.json
|
61
56
|
def destroy
|
62
57
|
@<%= orm_instance.destroy %>
|
58
|
+
|
63
59
|
respond_to do |format|
|
64
|
-
format.html { redirect_to <%= index_helper %>_url, notice: <%= "
|
60
|
+
format.html { redirect_to <%= index_helper %>_url, notice: <%= %("#{human_name} was successfully destroyed.") %> }
|
65
61
|
format.json { head :no_content }
|
66
62
|
end
|
67
63
|
end
|
@@ -72,12 +68,12 @@ class <%= controller_class_name %>Controller < ApplicationController
|
|
72
68
|
@<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
|
73
69
|
end
|
74
70
|
|
75
|
-
#
|
71
|
+
# Only allow a list of trusted parameters through.
|
76
72
|
def <%= "#{singular_table_name}_params" %>
|
77
73
|
<%- if attributes_names.empty? -%>
|
78
74
|
params.fetch(<%= ":#{singular_table_name}" %>, {})
|
79
75
|
<%- else -%>
|
80
|
-
params.require(<%= ":#{singular_table_name}" %>).permit(<%=
|
76
|
+
params.require(<%= ":#{singular_table_name}" %>).permit(<%= permitted_params %>)
|
81
77
|
<%- end -%>
|
82
78
|
end
|
83
79
|
end
|
@@ -1 +1 @@
|
|
1
|
-
json.array! @<%= plural_table_name %>, partial:
|
1
|
+
json.array! @<%= plural_table_name %>, partial: "<%= plural_table_name %>/<%= singular_table_name %>", as: :<%= singular_table_name %>
|
@@ -1,2 +1,16 @@
|
|
1
|
-
json.extract! <%= singular_table_name %>, <%=
|
2
|
-
json.url <%= singular_table_name %>_url(<%= singular_table_name %>, format: :json)
|
1
|
+
json.extract! <%= singular_table_name %>, <%= full_attributes_list %>
|
2
|
+
json.url <%= singular_table_name %>_url(<%= singular_table_name %>, format: :json)
|
3
|
+
<%- virtual_attributes.each do |attribute| -%>
|
4
|
+
<%- if attribute.type == :rich_text -%>
|
5
|
+
json.<%= attribute.name %> <%= singular_table_name %>.<%= attribute.name %>.to_s
|
6
|
+
<%- elsif attribute.type == :attachment -%>
|
7
|
+
json.<%= attribute.name %> url_for(<%= singular_table_name %>.<%= attribute.name %>)
|
8
|
+
<%- elsif attribute.type == :attachments -%>
|
9
|
+
json.<%= attribute.name %> do
|
10
|
+
json.array!(<%= singular_table_name %>.<%= attribute.name %>) do |<%= attribute.singular_name %>|
|
11
|
+
json.id <%= attribute.singular_name %>.id
|
12
|
+
json.url url_for(<%= attribute.singular_name %>)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
<%- end -%>
|
16
|
+
<%- end -%>
|
@@ -1 +1 @@
|
|
1
|
-
json.partial! "<%= plural_table_name %>/<%= singular_table_name %>", <%= singular_table_name %>: @<%= singular_table_name %>
|
1
|
+
json.partial! "<%= plural_table_name %>/<%= singular_table_name %>", <%= singular_table_name %>: @<%= singular_table_name %>
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
require 'active_support/concern'
|
3
|
+
require 'action_view'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'action_view/renderer/collection_renderer'
|
7
|
+
rescue LoadError
|
8
|
+
require 'action_view/renderer/partial_renderer'
|
9
|
+
end
|
10
|
+
|
11
|
+
class Jbuilder
|
12
|
+
module CollectionRenderable # :nodoc:
|
13
|
+
extend ActiveSupport::Concern
|
14
|
+
|
15
|
+
class_methods do
|
16
|
+
def supported?
|
17
|
+
superclass.private_method_defined?(:build_rendered_template) && self.superclass.private_method_defined?(:build_rendered_collection)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def build_rendered_template(content, template, layout = nil)
|
24
|
+
super(content || json.attributes!, template)
|
25
|
+
end
|
26
|
+
|
27
|
+
def build_rendered_collection(templates, _spacer)
|
28
|
+
json.merge!(templates.map(&:body))
|
29
|
+
end
|
30
|
+
|
31
|
+
def json
|
32
|
+
@options[:locals].fetch(:json)
|
33
|
+
end
|
34
|
+
|
35
|
+
class ScopedIterator < ::SimpleDelegator # :nodoc:
|
36
|
+
include Enumerable
|
37
|
+
|
38
|
+
def initialize(obj, scope)
|
39
|
+
super(obj)
|
40
|
+
@scope = scope
|
41
|
+
end
|
42
|
+
|
43
|
+
# Rails 6.0 support:
|
44
|
+
def each
|
45
|
+
return enum_for(:each) unless block_given?
|
46
|
+
|
47
|
+
__getobj__.each do |object|
|
48
|
+
@scope.call { yield(object) }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Rails 6.1 support:
|
53
|
+
def each_with_info
|
54
|
+
return enum_for(:each_with_info) unless block_given?
|
55
|
+
|
56
|
+
__getobj__.each_with_info do |object, info|
|
57
|
+
@scope.call { yield(object, info) }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
private_constant :ScopedIterator
|
63
|
+
end
|
64
|
+
|
65
|
+
if defined?(::ActionView::CollectionRenderer)
|
66
|
+
# Rails 6.1 support:
|
67
|
+
class CollectionRenderer < ::ActionView::CollectionRenderer # :nodoc:
|
68
|
+
include CollectionRenderable
|
69
|
+
|
70
|
+
def initialize(lookup_context, options, &scope)
|
71
|
+
super(lookup_context, options)
|
72
|
+
@scope = scope
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
def collection_with_template(view, template, layout, collection)
|
77
|
+
super(view, template, layout, ScopedIterator.new(collection, @scope))
|
78
|
+
end
|
79
|
+
end
|
80
|
+
else
|
81
|
+
# Rails 6.0 support:
|
82
|
+
class CollectionRenderer < ::ActionView::PartialRenderer # :nodoc:
|
83
|
+
include CollectionRenderable
|
84
|
+
|
85
|
+
def initialize(lookup_context, options, &scope)
|
86
|
+
super(lookup_context)
|
87
|
+
@options = options
|
88
|
+
@scope = scope
|
89
|
+
end
|
90
|
+
|
91
|
+
def render_collection_with_partial(collection, partial, context, block)
|
92
|
+
render(context, @options.merge(collection: collection, partial: partial), block)
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
def collection_without_template(view)
|
97
|
+
@collection = ScopedIterator.new(@collection, @scope)
|
98
|
+
|
99
|
+
super(view)
|
100
|
+
end
|
101
|
+
|
102
|
+
def collection_with_template(view, template)
|
103
|
+
@collection = ScopedIterator.new(@collection, @scope)
|
104
|
+
|
105
|
+
super(view, template)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
data/lib/jbuilder/errors.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
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
|
|
@@ -11,9 +12,42 @@ class JbuilderTemplate < Jbuilder
|
|
11
12
|
|
12
13
|
def initialize(context, *args)
|
13
14
|
@context = context
|
15
|
+
@cached_root = nil
|
14
16
|
super(*args)
|
15
17
|
end
|
16
18
|
|
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
|
+
#
|
17
51
|
def partial!(*args)
|
18
52
|
if args.one? && _is_active_model?(args.first)
|
19
53
|
_render_active_model_partial args.first
|
@@ -42,6 +76,27 @@ class JbuilderTemplate < Jbuilder
|
|
42
76
|
end
|
43
77
|
end
|
44
78
|
|
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
|
+
|
45
100
|
# Conditionally caches the json depending in the condition given as first parameter. Has the same
|
46
101
|
# signature as the `cache` helper method in `ActionView::Helpers::CacheHelper` and so can be used in
|
47
102
|
# the same way.
|
@@ -51,8 +106,12 @@ class JbuilderTemplate < Jbuilder
|
|
51
106
|
# json.cache_if! !admin?, @person, expires_in: 10.minutes do
|
52
107
|
# json.extract! @person, :name, :age
|
53
108
|
# end
|
54
|
-
def cache_if!(condition, *args)
|
55
|
-
condition ? cache!(*args,
|
109
|
+
def cache_if!(condition, *args, &block)
|
110
|
+
condition ? cache!(*args, &block) : yield
|
111
|
+
end
|
112
|
+
|
113
|
+
def target!
|
114
|
+
@cached_root || super
|
56
115
|
end
|
57
116
|
|
58
117
|
def array!(collection = [], *args)
|
@@ -78,11 +137,30 @@ class JbuilderTemplate < Jbuilder
|
|
78
137
|
private
|
79
138
|
|
80
139
|
def _render_partial_with_options(options)
|
81
|
-
options.reverse_merge! locals:
|
140
|
+
options.reverse_merge! locals: options.except(:partial, :as, :collection, :cached)
|
82
141
|
options.reverse_merge! ::JbuilderTemplate.template_lookup_options
|
83
142
|
as = options[:as]
|
84
143
|
|
85
|
-
if as && options.key?(:collection)
|
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:
|
86
164
|
as = as.to_sym
|
87
165
|
collection = options.delete(:collection)
|
88
166
|
locals = options.delete(:locals)
|
@@ -125,8 +203,8 @@ class JbuilderTemplate < Jbuilder
|
|
125
203
|
name_options = options.slice(:skip_digest, :virtual_path)
|
126
204
|
key = _fragment_name_with_digest(key, name_options)
|
127
205
|
|
128
|
-
if @context.respond_to?(:
|
129
|
-
key = @context.
|
206
|
+
if @context.respond_to?(:combined_fragment_cache_key)
|
207
|
+
key = @context.combined_fragment_cache_key(key)
|
130
208
|
else
|
131
209
|
key = url_for(key).split('://', 2).last if ::Hash === key
|
132
210
|
end
|
@@ -136,12 +214,7 @@ class JbuilderTemplate < Jbuilder
|
|
136
214
|
|
137
215
|
def _fragment_name_with_digest(key, options)
|
138
216
|
if @context.respond_to?(:cache_fragment_name)
|
139
|
-
|
140
|
-
# should be used instead.
|
141
|
-
@context.cache_fragment_name(key, options)
|
142
|
-
elsif @context.respond_to?(:fragment_name_with_digest)
|
143
|
-
# Backwards compatibility for period of time when fragment_name_with_digest was made public.
|
144
|
-
@context.fragment_name_with_digest(key)
|
217
|
+
@context.cache_fragment_name(key, **options)
|
145
218
|
else
|
146
219
|
key
|
147
220
|
end
|
@@ -162,7 +235,7 @@ class JbuilderTemplate < Jbuilder
|
|
162
235
|
_scope{ _render_partial_with_options options.merge(collection: object) }
|
163
236
|
else
|
164
237
|
locals = ::Hash[options[:as], object]
|
165
|
-
_scope{
|
238
|
+
_scope{ _render_partial_with_options options.merge(locals: locals) }
|
166
239
|
end
|
167
240
|
|
168
241
|
set! name, value
|
@@ -196,11 +269,12 @@ end
|
|
196
269
|
|
197
270
|
class JbuilderHandler
|
198
271
|
cattr_accessor :default_format
|
199
|
-
self.default_format =
|
272
|
+
self.default_format = :json
|
200
273
|
|
201
|
-
def self.call(template)
|
274
|
+
def self.call(template, source = nil)
|
275
|
+
source ||= template.source
|
202
276
|
# this juggling is required to keep line numbers right in the error
|
203
|
-
%{__already_defined = defined?(json); json||=JbuilderTemplate.new(self); #{
|
277
|
+
%{__already_defined = defined?(json); json||=JbuilderTemplate.new(self); #{source}
|
204
278
|
json.target! unless (__already_defined && __already_defined != "method")}
|
205
279
|
end
|
206
280
|
end
|
data/lib/jbuilder/railtie.rb
CHANGED