jbuilder 2.3.2 → 2.9.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +1 -0
- data/.travis.yml +49 -25
- data/Appraisals +19 -36
- data/CHANGELOG.md +72 -3
- data/CONTRIBUTING.md +14 -8
- data/Gemfile +0 -1
- data/MIT-LICENSE +1 -1
- data/README.md +47 -22
- data/Rakefile +1 -5
- data/gemfiles/rails_4_2.gemfile +3 -6
- 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_head.gemfile +10 -0
- data/jbuilder.gemspec +4 -5
- data/lib/generators/rails/jbuilder_generator.rb +9 -2
- data/lib/generators/rails/scaffold_controller_generator.rb +7 -1
- data/lib/generators/rails/templates/api_controller.rb +2 -2
- data/lib/generators/rails/templates/controller.rb +2 -2
- 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.rb +7 -7
- data/lib/jbuilder/errors.rb +7 -0
- data/lib/jbuilder/jbuilder_template.rb +61 -8
- data/lib/jbuilder/key_formatter.rb +2 -2
- data/lib/jbuilder/railtie.rb +12 -5
- data/test/jbuilder_dependency_tracker_test.rb +1 -1
- data/test/jbuilder_generator_test.rb +18 -4
- data/test/jbuilder_template_test.rb +204 -275
- data/test/jbuilder_test.rb +38 -2
- data/test/scaffold_api_controller_generator_test.rb +24 -11
- data/test/scaffold_controller_generator_test.rb +31 -18
- data/test/test_helper.rb +26 -6
- metadata +12 -36
- 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_edge.gemfile +0 -13
data/Rakefile
CHANGED
@@ -12,11 +12,7 @@ else
|
|
12
12
|
|
13
13
|
test.libs << "test"
|
14
14
|
|
15
|
-
|
16
|
-
test.test_files = %w[test/jbuilder_template_test.rb test/jbuilder_test.rb]
|
17
|
-
else
|
18
|
-
test.test_files = FileList["test/*_test.rb"]
|
19
|
-
end
|
15
|
+
test.test_files = FileList["test/*_test.rb"]
|
20
16
|
end
|
21
17
|
|
22
18
|
task default: :test
|
data/gemfiles/rails_4_2.gemfile
CHANGED
@@ -3,11 +3,8 @@
|
|
3
3
|
source "https://rubygems.org"
|
4
4
|
|
5
5
|
gem "rake"
|
6
|
-
gem "mocha", :
|
6
|
+
gem "mocha", require: false
|
7
7
|
gem "appraisal"
|
8
|
-
gem "
|
9
|
-
gem "railties", "~> 4.2.0"
|
10
|
-
gem "actionpack", "~> 4.2.0"
|
11
|
-
gem "activemodel", "~> 4.2.0"
|
8
|
+
gem "rails", "~> 4.2.0"
|
12
9
|
|
13
|
-
gemspec :
|
10
|
+
gemspec path: "../"
|
data/jbuilder.gemspec
CHANGED
@@ -1,16 +1,15 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'jbuilder'
|
3
|
-
s.version = '2.
|
4
|
-
s.authors =
|
5
|
-
s.email =
|
3
|
+
s.version = '2.9.1'
|
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
10
|
s.required_ruby_version = '>= 1.9.3'
|
11
11
|
|
12
|
-
s.add_dependency 'activesupport', '>=
|
13
|
-
s.add_dependency 'multi_json', '~> 1.2'
|
12
|
+
s.add_dependency 'activesupport', '>= 4.2.0'
|
14
13
|
|
15
14
|
s.files = `git ls-files`.split("\n")
|
16
15
|
s.test_files = `git ls-files -- test/*`.split("\n")
|
@@ -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)
|
@@ -6,7 +6,13 @@ module Rails
|
|
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
18
|
end
|
@@ -54,9 +54,9 @@ class <%= controller_class_name %>Controller < ApplicationController
|
|
54
54
|
# Never trust parameters from the scary internet, only allow the white list through.
|
55
55
|
def <%= "#{singular_table_name}_params" %>
|
56
56
|
<%- if attributes_names.empty? -%>
|
57
|
-
params
|
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
|
@@ -75,9 +75,9 @@ class <%= controller_class_name %>Controller < ApplicationController
|
|
75
75
|
# Never trust parameters from the scary internet, only allow the white list through.
|
76
76
|
def <%= "#{singular_table_name}_params" %>
|
77
77
|
<%- if attributes_names.empty? -%>
|
78
|
-
params
|
78
|
+
params.fetch(<%= ":#{singular_table_name}" %>, {})
|
79
79
|
<%- else -%>
|
80
|
-
params.require(<%= ":#{singular_table_name}" %>).permit(<%=
|
80
|
+
params.require(<%= ":#{singular_table_name}" %>).permit(<%= permitted_params %>)
|
81
81
|
<%- end -%>
|
82
82
|
end
|
83
83
|
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 %>
|
@@ -1 +1 @@
|
|
1
|
-
json.
|
1
|
+
json.partial! "<%= plural_table_name %>/<%= singular_table_name %>", <%= singular_table_name %>: @<%= singular_table_name %>
|
data/lib/jbuilder.rb
CHANGED
@@ -2,7 +2,7 @@ require 'jbuilder/jbuilder'
|
|
2
2
|
require 'jbuilder/blank'
|
3
3
|
require 'jbuilder/key_formatter'
|
4
4
|
require 'jbuilder/errors'
|
5
|
-
require '
|
5
|
+
require 'json'
|
6
6
|
require 'ostruct'
|
7
7
|
|
8
8
|
class Jbuilder
|
@@ -247,7 +247,7 @@ class Jbuilder
|
|
247
247
|
|
248
248
|
# Encodes the current builder as JSON.
|
249
249
|
def target!
|
250
|
-
|
250
|
+
@attributes.to_json
|
251
251
|
end
|
252
252
|
|
253
253
|
private
|
@@ -270,14 +270,14 @@ class Jbuilder
|
|
270
270
|
def _merge_values(current_value, updates)
|
271
271
|
if _blank?(updates)
|
272
272
|
current_value
|
273
|
-
elsif _blank?(current_value) || updates.nil?
|
273
|
+
elsif _blank?(current_value) || updates.nil? || current_value.empty? && ::Array === updates
|
274
274
|
updates
|
275
|
-
elsif ::Array === updates
|
276
|
-
|
277
|
-
elsif ::Hash === current_value
|
275
|
+
elsif ::Array === current_value && ::Array === updates
|
276
|
+
current_value + updates
|
277
|
+
elsif ::Hash === current_value && ::Hash === updates
|
278
278
|
current_value.merge(updates)
|
279
279
|
else
|
280
|
-
raise
|
280
|
+
raise MergeError.build(current_value, updates)
|
281
281
|
end
|
282
282
|
end
|
283
283
|
|
data/lib/jbuilder/errors.rb
CHANGED
@@ -11,6 +11,7 @@ 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
|
|
@@ -32,7 +33,7 @@ class JbuilderTemplate < Jbuilder
|
|
32
33
|
# end
|
33
34
|
def cache!(key=nil, options={})
|
34
35
|
if @context.controller.perform_caching
|
35
|
-
value =
|
36
|
+
value = _cache_fragment_for(key, options) do
|
36
37
|
_scope { yield self }
|
37
38
|
end
|
38
39
|
|
@@ -42,6 +43,27 @@ class JbuilderTemplate < Jbuilder
|
|
42
43
|
end
|
43
44
|
end
|
44
45
|
|
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
|
+
|
45
67
|
# Conditionally caches the json depending in the condition given as first parameter. Has the same
|
46
68
|
# signature as the `cache` helper method in `ActionView::Helpers::CacheHelper` and so can be used in
|
47
69
|
# the same way.
|
@@ -55,6 +77,10 @@ class JbuilderTemplate < Jbuilder
|
|
55
77
|
condition ? cache!(*args, &::Proc.new) : yield
|
56
78
|
end
|
57
79
|
|
80
|
+
def target!
|
81
|
+
@cached_root || super
|
82
|
+
end
|
83
|
+
|
58
84
|
def array!(collection = [], *args)
|
59
85
|
options = args.first
|
60
86
|
|
@@ -78,7 +104,7 @@ class JbuilderTemplate < Jbuilder
|
|
78
104
|
private
|
79
105
|
|
80
106
|
def _render_partial_with_options(options)
|
81
|
-
options.reverse_merge! locals:
|
107
|
+
options.reverse_merge! locals: options.except(:partial, :as, :collection)
|
82
108
|
options.reverse_merge! ::JbuilderTemplate.template_lookup_options
|
83
109
|
as = options[:as]
|
84
110
|
|
@@ -102,9 +128,35 @@ class JbuilderTemplate < Jbuilder
|
|
102
128
|
@context.render options
|
103
129
|
end
|
104
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
|
+
|
105
150
|
def _cache_key(key, options)
|
106
|
-
|
107
|
-
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?(:combined_fragment_cache_key)
|
155
|
+
key = @context.combined_fragment_cache_key(key)
|
156
|
+
else
|
157
|
+
key = url_for(key).split('://', 2).last if ::Hash === key
|
158
|
+
end
|
159
|
+
|
108
160
|
::ActiveSupport::Cache.expand_cache_key(key, :jbuilder)
|
109
161
|
end
|
110
162
|
|
@@ -136,7 +188,7 @@ class JbuilderTemplate < Jbuilder
|
|
136
188
|
_scope{ _render_partial_with_options options.merge(collection: object) }
|
137
189
|
else
|
138
190
|
locals = ::Hash[options[:as], object]
|
139
|
-
_scope{
|
191
|
+
_scope{ _render_partial_with_options options.merge(locals: locals) }
|
140
192
|
end
|
141
193
|
|
142
194
|
set! name, value
|
@@ -170,11 +222,12 @@ end
|
|
170
222
|
|
171
223
|
class JbuilderHandler
|
172
224
|
cattr_accessor :default_format
|
173
|
-
self.default_format =
|
225
|
+
self.default_format = :json
|
174
226
|
|
175
|
-
def self.call(template)
|
227
|
+
def self.call(template, source = nil)
|
228
|
+
source ||= template.source
|
176
229
|
# this juggling is required to keep line numbers right in the error
|
177
|
-
%{__already_defined = defined?(json); json||=JbuilderTemplate.new(self); #{
|
230
|
+
%{__already_defined = defined?(json); json||=JbuilderTemplate.new(self); #{source}
|
178
231
|
json.target! unless (__already_defined && __already_defined != "method")}
|
179
232
|
end
|
180
233
|
end
|
data/lib/jbuilder/railtie.rb
CHANGED
@@ -3,17 +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
|
-
|
15
|
-
|
16
|
-
|
20
|
+
if self == ActionController::API
|
21
|
+
include ActionController::Helpers
|
22
|
+
include ActionController::ImplicitRender
|
23
|
+
end
|
17
24
|
end
|
18
25
|
end
|
19
26
|
end
|
@@ -53,7 +53,7 @@ class JbuilderDependencyTrackerTest < ActiveSupport::TestCase
|
|
53
53
|
assert_equal %w[path/to/partial], dependencies
|
54
54
|
end
|
55
55
|
|
56
|
-
test 'detects partial in indirect
|
56
|
+
test 'detects partial in indirect collection calls' do
|
57
57
|
dependencies = track_dependencies <<-RUBY
|
58
58
|
json.comments @post.comments, partial: 'comments/comment', as: :comment
|
59
59
|
RUBY
|
@@ -14,19 +14,33 @@ 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
|
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{:created_at, :updated_at}, content
|
34
|
+
assert_match %r{json\.url post_url\(post, format: :json\)}, content
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
test 'timestamps are not generated in partial with --no-timestamps' do
|
39
|
+
run_generator %w(Post title body:text --no-timestamps)
|
40
|
+
|
41
|
+
assert_file 'app/views/posts/_post.json.jbuilder' do |content|
|
42
|
+
assert_match %r{json\.extract! post, :id, :title, :body$}, content
|
43
|
+
assert_no_match %r{:created_at, :updated_at}, content
|
30
44
|
end
|
31
45
|
end
|
32
46
|
end
|