jbuilder 2.3.2 → 2.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.travis.yml +49 -25
  4. data/Appraisals +19 -36
  5. data/CHANGELOG.md +72 -3
  6. data/CONTRIBUTING.md +14 -8
  7. data/Gemfile +0 -1
  8. data/MIT-LICENSE +1 -1
  9. data/README.md +47 -22
  10. data/Rakefile +1 -5
  11. data/gemfiles/rails_4_2.gemfile +3 -6
  12. data/gemfiles/rails_5_0.gemfile +10 -0
  13. data/gemfiles/rails_5_1.gemfile +10 -0
  14. data/gemfiles/rails_5_2.gemfile +10 -0
  15. data/gemfiles/rails_6_0.gemfile +10 -0
  16. data/gemfiles/rails_head.gemfile +10 -0
  17. data/jbuilder.gemspec +4 -5
  18. data/lib/generators/rails/jbuilder_generator.rb +9 -2
  19. data/lib/generators/rails/scaffold_controller_generator.rb +7 -1
  20. data/lib/generators/rails/templates/api_controller.rb +2 -2
  21. data/lib/generators/rails/templates/controller.rb +2 -2
  22. data/lib/generators/rails/templates/index.json.jbuilder +1 -4
  23. data/lib/generators/rails/templates/partial.json.jbuilder +2 -0
  24. data/lib/generators/rails/templates/show.json.jbuilder +1 -1
  25. data/lib/jbuilder.rb +7 -7
  26. data/lib/jbuilder/errors.rb +7 -0
  27. data/lib/jbuilder/jbuilder_template.rb +61 -8
  28. data/lib/jbuilder/key_formatter.rb +2 -2
  29. data/lib/jbuilder/railtie.rb +12 -5
  30. data/test/jbuilder_dependency_tracker_test.rb +1 -1
  31. data/test/jbuilder_generator_test.rb +18 -4
  32. data/test/jbuilder_template_test.rb +204 -275
  33. data/test/jbuilder_test.rb +38 -2
  34. data/test/scaffold_api_controller_generator_test.rb +24 -11
  35. data/test/scaffold_controller_generator_test.rb +31 -18
  36. data/test/test_helper.rb +26 -6
  37. metadata +12 -36
  38. data/gemfiles/rails_3_0.gemfile +0 -14
  39. data/gemfiles/rails_3_1.gemfile +0 -14
  40. data/gemfiles/rails_3_2.gemfile +0 -14
  41. data/gemfiles/rails_4_0.gemfile +0 -13
  42. data/gemfiles/rails_4_1.gemfile +0 -13
  43. 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
- if Rails::VERSION::MAJOR == 3
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
@@ -3,11 +3,8 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gem "rake"
6
- gem "mocha", :require => false
6
+ gem "mocha", require: false
7
7
  gem "appraisal"
8
- gem "pry"
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 :path => "../"
10
+ gemspec path: "../"
@@ -0,0 +1,10 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rake"
6
+ gem "mocha", require: false
7
+ gem "appraisal"
8
+ gem "rails", "~> 5.0.0"
9
+
10
+ gemspec path: "../"
@@ -0,0 +1,10 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rake"
6
+ gem "mocha", require: false
7
+ gem "appraisal"
8
+ gem "rails", "~> 5.1.0"
9
+
10
+ gemspec path: "../"
@@ -0,0 +1,10 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rake"
6
+ gem "mocha", require: false
7
+ gem "appraisal"
8
+ gem "rails", "~> 5.2.0"
9
+
10
+ gemspec path: "../"
@@ -0,0 +1,10 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rake"
6
+ gem "mocha", require: false
7
+ gem "appraisal"
8
+ gem "rails", "~> 6.0.0.rc1"
9
+
10
+ gemspec path: "../"
@@ -0,0 +1,10 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rake"
6
+ gem "mocha", require: false
7
+ gem "appraisal"
8
+ gem "rails", github: "rails/rails"
9
+
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.3.2'
4
- s.authors = ['David Heinemeier Hansson', 'Pavel Pravosud']
5
- s.email = ['david@37signals.com', 'pavel@pravosud.com']
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', '>= 3.0.0', '< 5'
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 attributes_list_with_timestamps
36
- attributes_list(attributes_names + %w(created_at updated_at))
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[<%= ":#{singular_table_name}" %>]
57
+ params.fetch(<%= ":#{singular_table_name}" %>, {})
58
58
  <%- else -%>
59
- params.require(<%= ":#{singular_table_name}" %>).permit(<%= attributes_names.map { |name| ":#{name}" }.join(', ') %>)
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[<%= ":#{singular_table_name}" %>]
78
+ params.fetch(<%= ":#{singular_table_name}" %>, {})
79
79
  <%- else -%>
80
- params.require(<%= ":#{singular_table_name}" %>).permit(<%= attributes_names.map { |name| ":#{name}" }.join(', ') %>)
80
+ params.require(<%= ":#{singular_table_name}" %>).permit(<%= permitted_params %>)
81
81
  <%- end -%>
82
82
  end
83
83
  end
@@ -1,4 +1 @@
1
- json.array!(@<%= plural_table_name %>) do |<%= singular_table_name %>|
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,2 @@
1
+ json.extract! <%= singular_table_name %>, <%= full_attributes_list %>
2
+ json.url <%= singular_table_name %>_url(<%= singular_table_name %>, format: :json)
@@ -1 +1 @@
1
- json.extract! @<%= singular_table_name %>, <%= attributes_list_with_timestamps %>
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 'multi_json'
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
- ::MultiJson.dump(@attributes)
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
- ::Array === current_value ? current_value + updates : updates
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 "Can't merge #{updates.inspect} with #{current_value.inspect}"
280
+ raise MergeError.build(current_value, updates)
281
281
  end
282
282
  end
283
283
 
@@ -14,4 +14,11 @@ class Jbuilder
14
14
  new(message)
15
15
  end
16
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
17
24
  end
@@ -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 = ::Rails.cache.fetch(_cache_key(key, options), options) do
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
- key = _fragment_name_with_digest(key, options)
107
- key = url_for(key).split('://', 2).last if ::Hash === 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{ _render_partial options.merge(locals: locals) }
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 = Mime[:json]
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); #{template.source}
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
@@ -11,8 +11,8 @@ class Jbuilder
11
11
  args.each do |name|
12
12
  @format[name] = []
13
13
  end
14
- options.each do |name, paramaters|
15
- @format[name] = paramaters
14
+ options.each do |name, parameters|
15
+ @format[name] = parameters
16
16
  end
17
17
  end
18
18
 
@@ -3,17 +3,24 @@ require 'jbuilder/jbuilder_template'
3
3
 
4
4
  class Jbuilder
5
5
  class Railtie < ::Rails::Railtie
6
- initializer :jbuilder do |app|
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 app.config.respond_to?(:api_only) && app.config.api_only
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
- include ActionView::Rendering
15
- include ActionController::Helpers
16
- include ActionController::ImplicitRender
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 collecton calls' do
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 /json\.array!\(@posts\) do \|post\|/, content
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 /json\.extract! @post, :id, :title, :body, :created_at, :updated_at/, content
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