jbuilder 2.0.6 → 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 +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
data/jbuilder.gemspec
CHANGED
@@ -1,17 +1,31 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'jbuilder'
|
3
|
-
s.version = '2.
|
4
|
-
s.
|
5
|
-
s.email = 'david@
|
3
|
+
s.version = '2.11.5'
|
4
|
+
s.authors = 'David Heinemeier Hansson'
|
5
|
+
s.email = 'david@basecamp.com'
|
6
6
|
s.summary = 'Create JSON structures via a Builder-style DSL'
|
7
7
|
s.homepage = 'https://github.com/rails/jbuilder'
|
8
8
|
s.license = 'MIT'
|
9
9
|
|
10
|
-
s.required_ruby_version = '>=
|
10
|
+
s.required_ruby_version = '>= 2.2.2'
|
11
11
|
|
12
|
-
s.add_dependency 'activesupport', '>=
|
13
|
-
s.add_dependency '
|
12
|
+
s.add_dependency 'activesupport', '>= 5.0.0'
|
13
|
+
s.add_dependency 'actionview', '>= 5.0.0'
|
14
|
+
|
15
|
+
if RUBY_ENGINE == 'rbx'
|
16
|
+
s.add_development_dependency('racc')
|
17
|
+
s.add_development_dependency('json')
|
18
|
+
s.add_development_dependency('rubysl')
|
19
|
+
end
|
14
20
|
|
15
21
|
s.files = `git ls-files`.split("\n")
|
16
22
|
s.test_files = `git ls-files -- test/*`.split("\n")
|
23
|
+
|
24
|
+
s.metadata = {
|
25
|
+
"bug_tracker_uri" => "https://github.com/rails/jbuilder/issues",
|
26
|
+
"changelog_uri" => "https://github.com/rails/jbuilder/releases/tag/v#{s.version}",
|
27
|
+
"mailing_list_uri" => "https://discuss.rubyonrails.org/c/rubyonrails-talk",
|
28
|
+
"source_code_uri" => "https://github.com/rails/jbuilder/tree/v#{s.version}",
|
29
|
+
"rubygems_mfa_required" => "true",
|
30
|
+
}
|
17
31
|
end
|
@@ -10,6 +10,8 @@ module Rails
|
|
10
10
|
|
11
11
|
argument :attributes, type: :array, default: [], banner: 'field:type field:type'
|
12
12
|
|
13
|
+
class_option :timestamps, type: :boolean, default: true
|
14
|
+
|
13
15
|
def create_root_folder
|
14
16
|
path = File.join('app/views', controller_file_path)
|
15
17
|
empty_directory path unless File.directory?(path)
|
@@ -20,6 +22,7 @@ module Rails
|
|
20
22
|
filename = filename_with_extensions(view)
|
21
23
|
template filename, File.join('app/views', controller_file_path, filename)
|
22
24
|
end
|
25
|
+
template filename_with_extensions('partial'), File.join('app/views', controller_file_path, filename_with_extensions("_#{singular_table_name}"))
|
23
26
|
end
|
24
27
|
|
25
28
|
|
@@ -32,8 +35,12 @@ module Rails
|
|
32
35
|
[name, :json, :jbuilder] * '.'
|
33
36
|
end
|
34
37
|
|
35
|
-
def
|
36
|
-
|
38
|
+
def full_attributes_list
|
39
|
+
if options[:timestamps]
|
40
|
+
attributes_list(attributes_names + %w(created_at updated_at))
|
41
|
+
else
|
42
|
+
attributes_list(attributes_names)
|
43
|
+
end
|
37
44
|
end
|
38
45
|
|
39
46
|
def attributes_list(attributes = attributes_names)
|
@@ -43,6 +50,10 @@ module Rails
|
|
43
50
|
|
44
51
|
attributes.map { |a| ":#{a}"} * ', '
|
45
52
|
end
|
53
|
+
|
54
|
+
def virtual_attributes
|
55
|
+
attributes.select {|name| name.respond_to?(:virtual?) && name.virtual? }
|
56
|
+
end
|
46
57
|
end
|
47
58
|
end
|
48
59
|
end
|
@@ -4,9 +4,15 @@ require 'rails/generators/rails/scaffold_controller/scaffold_controller_generato
|
|
4
4
|
module Rails
|
5
5
|
module Generators
|
6
6
|
class ScaffoldControllerGenerator
|
7
|
-
|
7
|
+
source_paths << File.expand_path('../templates', __FILE__)
|
8
8
|
|
9
|
-
hook_for :jbuilder, default: true
|
9
|
+
hook_for :jbuilder, type: :boolean, default: true
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def permitted_params
|
14
|
+
attributes_names.map { |name| ":#{name}" }.join(', ')
|
15
|
+
end unless private_method_defined? :permitted_params
|
10
16
|
end
|
11
17
|
end
|
12
|
-
end
|
18
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
<% if namespaced? -%>
|
2
|
+
require_dependency "<%= namespaced_path %>/application_controller"
|
3
|
+
|
4
|
+
<% end -%>
|
5
|
+
<% module_namespacing do -%>
|
6
|
+
class <%= controller_class_name %>Controller < ApplicationController
|
7
|
+
before_action :set_<%= singular_table_name %>, only: %i[ show update destroy ]
|
8
|
+
|
9
|
+
# GET <%= route_url %>
|
10
|
+
# GET <%= route_url %>.json
|
11
|
+
def index
|
12
|
+
@<%= plural_table_name %> = <%= orm_class.all(class_name) %>
|
13
|
+
end
|
14
|
+
|
15
|
+
# GET <%= route_url %>/1
|
16
|
+
# GET <%= route_url %>/1.json
|
17
|
+
def show
|
18
|
+
end
|
19
|
+
|
20
|
+
# POST <%= route_url %>
|
21
|
+
# POST <%= route_url %>.json
|
22
|
+
def create
|
23
|
+
@<%= singular_table_name %> = <%= orm_class.build(class_name, "#{singular_table_name}_params") %>
|
24
|
+
|
25
|
+
if @<%= orm_instance.save %>
|
26
|
+
render :show, status: :created, location: <%= "@#{singular_table_name}" %>
|
27
|
+
else
|
28
|
+
render json: <%= "@#{orm_instance.errors}" %>, status: :unprocessable_entity
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# PATCH/PUT <%= route_url %>/1
|
33
|
+
# PATCH/PUT <%= route_url %>/1.json
|
34
|
+
def update
|
35
|
+
if @<%= orm_instance.update("#{singular_table_name}_params") %>
|
36
|
+
render :show, status: :ok, location: <%= "@#{singular_table_name}" %>
|
37
|
+
else
|
38
|
+
render json: <%= "@#{orm_instance.errors}" %>, status: :unprocessable_entity
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# DELETE <%= route_url %>/1
|
43
|
+
# DELETE <%= route_url %>/1.json
|
44
|
+
def destroy
|
45
|
+
@<%= orm_instance.destroy %>
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
# Use callbacks to share common setup or constraints between actions.
|
50
|
+
def set_<%= singular_table_name %>
|
51
|
+
@<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
|
52
|
+
end
|
53
|
+
|
54
|
+
# Only allow a list of trusted parameters through.
|
55
|
+
def <%= "#{singular_table_name}_params" %>
|
56
|
+
<%- if attributes_names.empty? -%>
|
57
|
+
params.fetch(<%= ":#{singular_table_name}" %>, {})
|
58
|
+
<%- else -%>
|
59
|
+
params.require(<%= ":#{singular_table_name}" %>).permit(<%= permitted_params %>)
|
60
|
+
<%- end -%>
|
61
|
+
end
|
62
|
+
end
|
63
|
+
<% 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 }
|
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
|
-
params
|
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,4 +1 @@
|
|
1
|
-
json.array!
|
2
|
-
json.extract! <%= singular_table_name %>, <%= attributes_list %>
|
3
|
-
json.url <%= singular_table_name %>_url(<%= singular_table_name %>, format: :json)
|
4
|
-
end
|
1
|
+
json.array! @<%= plural_table_name %>, partial: "<%= plural_table_name %>/<%= singular_table_name %>", as: :<%= singular_table_name %>
|
@@ -0,0 +1,16 @@
|
|
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.
|
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
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'jbuilder/jbuilder'
|
2
|
+
|
3
|
+
class Jbuilder
|
4
|
+
class NullError < ::NoMethodError
|
5
|
+
def self.build(key)
|
6
|
+
message = "Failed to add #{key.to_s.inspect} property to null object"
|
7
|
+
new(message)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class ArrayError < ::StandardError
|
12
|
+
def self.build(key)
|
13
|
+
message = "Failed to add #{key.to_s.inspect} property to an array"
|
14
|
+
new(message)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class MergeError < ::StandardError
|
19
|
+
def self.build(current_value, updates)
|
20
|
+
message = "Can't merge #{updates.inspect} into #{current_value.inspect}"
|
21
|
+
new(message)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|