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