jbuilder 2.9.0 → 2.11.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 780a2d0395698145f0e1c9e96a7a2079b70def4be27846b74537ec90786b9495
4
- data.tar.gz: da6e2704a1060b023c7a78d4539ad39da447dba6e3d7c657834a81badcd47afa
3
+ metadata.gz: '039b14d174dd7bfb3a5e00d5a315aee89f1efd46a570244191ab985bb44fc328'
4
+ data.tar.gz: 4eda58d10150903687b7940b858fa02c6f0a2cbca8858cd2ae9866c5be5a4b62
5
5
  SHA512:
6
- metadata.gz: c088b1a55e41c650bccd49901ab964ac9b4b715bdab5d51d68a5b34b6a3bb18adcb37ade32548fa1f69ecd4d2cb29c8f34f747dd4af80daa4c8115e99bd80489
7
- data.tar.gz: 23e2b31830276ba589c941a484ac6d9b80dc0e3a0f4c4767f8d534f1bdfae5d9e76d1e75a31127103a045afba127c6eb2a7e87f35327860729ef7f08aafdd766
6
+ metadata.gz: 1d20f2794455635cbb3272bd4b6e8f91781e2c55aa40c7228d5347b4b6ad049cd3cc22ebc9ef708b8bbe4a05811774de07770b175035342123458a4637b9240f
7
+ data.tar.gz: 383aca1c7489eda135339e4538fa8a9dec80900c7c8bab786729a7c323d63f3a75657889ee6dad6bf91b769ced3eb507e41ef7daa15faa8bb3c5d0261c0c0394
data/.gitignore CHANGED
@@ -1,5 +1,7 @@
1
1
  tmp
2
+ gemfiles/.bundle
2
3
  gemfiles/*.lock
3
4
  Gemfile.lock
4
5
  .ruby-version
5
6
  pkg
7
+ *.gem
data/.travis.yml CHANGED
@@ -3,55 +3,47 @@ language: ruby
3
3
  cache: bundler
4
4
 
5
5
  before_install:
6
- - "gem update --system 2.7.9"
7
6
  - "gem install bundler -v '<2'"
8
7
 
9
8
  rvm:
10
9
  - 2.2.10
11
10
  - 2.3.8
12
- - 2.4.6
13
- - 2.5.5
14
- - 2.6.2
11
+ - 2.4.10
12
+ - 2.5.8
13
+ - 2.6.6
14
+ - 2.7.1
15
15
  - ruby-head
16
- - rbx-3.107
17
16
 
18
17
  gemfile:
19
- - gemfiles/rails_4_2.gemfile
20
18
  - gemfiles/rails_5_0.gemfile
21
19
  - gemfiles/rails_5_1.gemfile
22
20
  - gemfiles/rails_5_2.gemfile
21
+ - gemfiles/rails_6_0.gemfile
23
22
  - gemfiles/rails_head.gemfile
24
23
 
25
24
  matrix:
26
- include:
27
- - rvm: 1.9
28
- gemfile: gemfiles/rails_4_2.gemfile
29
- - rvm: 2.0
30
- gemfile: gemfiles/rails_4_2.gemfile
31
- - rvm: 2.1
32
- gemfile: gemfiles/rails_4_2.gemfile
33
- - rvm: 2.3
34
- gemfile: gemfiles/rails_4_2.gemfile
35
- - rvm: jruby-19mode
36
- gemfile: gemfiles/rails_4_2.gemfile
37
- - rvm: rbx-3.107
38
- gemfile: gemfiles/rails_4_2.gemfile
39
25
  exclude:
40
- - rvm: 2.4.6
41
- gemfile: gemfiles/rails_4_2.gemfile
42
- - rvm: 2.5.5
43
- gemfile: gemfiles/rails_4_2.gemfile
44
- - rvm: 2.6.2
45
- gemfile: gemfiles/rails_4_2.gemfile
26
+ - rvm: 2.7.1
27
+ gemfile: gemfiles/rails_5_0.gemfile
28
+ - rvm: 2.7.1
29
+ gemfile: gemfiles/rails_5_1.gemfile
30
+ - rvm: 2.2.10
31
+ gemfile: gemfiles/rails_5_2.gemfile
32
+ - rvm: 2.7.1
33
+ gemfile: gemfiles/rails_5_2.gemfile
34
+ - rvm: 2.2.10
35
+ gemfile: gemfiles/rails_6_0.gemfile
36
+ - rvm: 2.3.8
37
+ gemfile: gemfiles/rails_6_0.gemfile
38
+ - rvm: 2.4.10
39
+ gemfile: gemfiles/rails_6_0.gemfile
46
40
  - rvm: 2.2.10
47
41
  gemfile: gemfiles/rails_head.gemfile
48
42
  - rvm: 2.3.8
49
43
  gemfile: gemfiles/rails_head.gemfile
50
- - rvm: 2.4.6
44
+ - rvm: 2.4.10
51
45
  gemfile: gemfiles/rails_head.gemfile
52
46
  allow_failures:
53
- - rvm: jruby-19mode
54
- - rvm: rbx-3.107
55
47
  - rvm: ruby-head
56
48
  - gemfile: gemfiles/rails_head.gemfile
57
49
  fast_finish: true
data/Appraisals CHANGED
@@ -1,22 +1,20 @@
1
- appraise "rails-4-2" do
2
- gem "rails", "~> 4.2.0"
1
+ appraise "rails-5-0" do
2
+ gem "rails", "~> 5.0.0"
3
3
  end
4
4
 
5
- if RUBY_VERSION >= "2.2.2"
6
- appraise "rails-5-0" do
7
- gem "rails", "~> 5.0.0"
8
- end
9
-
10
- appraise "rails-5-1" do
11
- gem "rails", "~> 5.1.0"
12
- end
5
+ appraise "rails-5-1" do
6
+ gem "rails", "~> 5.1.0"
7
+ end
13
8
 
14
- appraise "rails-5-2" do
15
- gem "rails", "~> 5.2.0"
16
- end
9
+ appraise "rails-5-2" do
10
+ gem "rails", "~> 5.2.0"
17
11
  end
18
12
 
19
13
  if RUBY_VERSION >= "2.5.0"
14
+ appraise "rails-6-0" do
15
+ gem "rails", "~> 6.0.0"
16
+ end
17
+
20
18
  appraise "rails-head" do
21
19
  gem "rails", github: "rails/rails"
22
20
  end
data/CHANGELOG.md CHANGED
@@ -1,5 +1,55 @@
1
1
  # Changelog
2
2
 
3
+ 2.11.2
4
+ ------
5
+
6
+ * [Improve key formatting for nested hashes and disable by default](https://github.com/rails/jbuilder/pull/497)
7
+
8
+ 2.11.1
9
+ ------
10
+
11
+ * Use symbols instead of strings for before_action filters [DHH]
12
+ * Slim down comments in generated scaffold code [DHH]
13
+
14
+ 2.11.0
15
+ ------
16
+
17
+ * [Allow jbuilder instance to be passed to #merge!](https://github.com/rails/jbuilder/pull/485)
18
+ * [Fix for key_format! when using nested hashes](https://github.com/rails/jbuilder/pull/486)
19
+ * [Include rich_text, attachment, and attachments fields in json partial](https://github.com/rails/jbuilder/pull/459)
20
+
21
+ 2.10.2
22
+ ------
23
+
24
+ * Update scaffold generator to use double quotes, 422 form error responds, and modern string-of-arrays syntax [DHH]
25
+
26
+ 2.10.1
27
+ ------
28
+
29
+ * Fix keyword arguments warning on Ruby 2.7
30
+
31
+ 2.10.0
32
+ ------
33
+
34
+ * Requires Rails 5+ and Ruby 2.2+
35
+ * Nested hashes are deep-merged
36
+
37
+ 2.9.1
38
+ -----
39
+
40
+ * [Respect JSON encoding customizations](https://github.com/rails/jbuilder/commit/e2e8623b08078ad6a2323ce8ecaf642b7afe1166)
41
+
42
+ 2.9.0
43
+ -----
44
+
45
+ * [Fix passing object with partial without locals](https://github.com/rails/jbuilder/pull/435)
46
+ * [Fix deprecation warning in Rails 6.0](https://github.com/rails/jbuilder/pull/453)
47
+ * [Use quotes consistently in generated templates](https://github.com/rails/jbuilder/pull/455)
48
+ * [Allow omitting timestamps from generated partials](https://github.com/rails/jbuilder/pull/448)
49
+ * [Respect changing scaffold generator](https://github.com/rails/jbuilder/pull/458)
50
+ * [Use a symbolic default format for Rails 6.0 forward compatibility](https://github.com/rails/jbuilder/commit/3895a7243f3db292b0bf15513fc05494e6e50576)
51
+ * [Drop MultiJSON in favor of Ruby standard library JSON](https://github.com/rails/jbuilder/commit/b952ae096eb1828b0fcfde06e6ba62311494ec49)
52
+
3
53
  2.8.0
4
54
  -----
5
55
 
data/CONTRIBUTING.md CHANGED
@@ -4,18 +4,16 @@ Contributing to Jbuilder
4
4
  [![Build Status](https://api.travis-ci.org/rails/jbuilder.svg?branch=master)][travis]
5
5
  [![Gem Version](https://badge.fury.io/rb/jbuilder.svg)][gem]
6
6
  [![Code Climate](https://codeclimate.com/github/rails/jbuilder/badges/gpa.svg)][codeclimate]
7
- [![Dependencies Status](https://gemnasium.com/rails/jbuilder.svg)][gemnasium]
8
7
 
9
8
  [travis]: https://travis-ci.org/rails/jbuilder
10
9
  [gem]: https://rubygems.org/gems/jbuilder
11
10
  [codeclimate]: https://codeclimate.com/github/rails/jbuilder
12
- [gemnasium]: https://gemnasium.com/rails/jbuilder
13
11
 
14
12
  Jbuilder is work of [many contributors](https://github.com/rails/jbuilder/graphs/contributors). You're encouraged to submit [pull requests](https://github.com/rails/jbuilder/pulls), [propose features and discuss issues](https://github.com/rails/jbuilder/issues).
15
13
 
16
14
  #### Fork the Project
17
15
 
18
- Fork the [project on Github](https://github.com/rails/jbuilder) and check out your copy.
16
+ Fork the [project on GitHub](https://github.com/rails/jbuilder) and check out your copy.
19
17
 
20
18
  ```
21
19
  git clone https://github.com/contributor/jbuilder.git
data/README.md CHANGED
@@ -274,6 +274,25 @@ environment.rb for example):
274
274
  Jbuilder.key_format camelize: :lower
275
275
  ```
276
276
 
277
+ By default, key format is not applied to keys of hashes that are
278
+ passed to methods like `set!`, `array!` or `merge!`. You can opt into
279
+ deeply transforming these as well:
280
+
281
+ ``` ruby
282
+ json.key_format! camelize: :lower
283
+ json.deep_format_keys!
284
+ json.settings([{some_value: "abc"}])
285
+
286
+ # => { "settings": [{ "someValue": "abc" }]}
287
+ ```
288
+
289
+ You can set this globally with the class method `deep_format_keys` (from inside your
290
+ environment.rb for example):
291
+
292
+ ``` ruby
293
+ Jbuilder.deep_format_keys true
294
+ ```
295
+
277
296
  ## Contributing to Jbuilder
278
297
 
279
298
  Jbuilder is the work of many contributors. You're encouraged to submit pull requests, propose
@@ -5,6 +5,6 @@ source "https://rubygems.org"
5
5
  gem "rake"
6
6
  gem "mocha", require: false
7
7
  gem "appraisal"
8
- gem "rails", "~> 4.2.0"
8
+ gem "rails", "~> 6.0.0"
9
9
 
10
10
  gemspec path: "../"
data/jbuilder.gemspec CHANGED
@@ -1,15 +1,21 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'jbuilder'
3
- s.version = '2.9.0'
3
+ s.version = '2.11.2'
4
4
  s.authors = 'David Heinemeier Hansson'
5
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 = '>= 1.9.3'
10
+ s.required_ruby_version = '>= 2.2.2'
11
11
 
12
- s.add_dependency 'activesupport', '>= 4.2.0'
12
+ s.add_dependency 'activesupport', '>= 5.0.0'
13
+
14
+ if RUBY_ENGINE == 'rbx'
15
+ s.add_development_dependency('racc')
16
+ s.add_development_dependency('json')
17
+ s.add_development_dependency('rubysl')
18
+ end
13
19
 
14
20
  s.files = `git ls-files`.split("\n")
15
21
  s.test_files = `git ls-files -- test/*`.split("\n")
@@ -50,6 +50,10 @@ module Rails
50
50
 
51
51
  attributes.map { |a| ":#{a}"} * ', '
52
52
  end
53
+
54
+ def virtual_attributes
55
+ attributes.select {|name| name.respond_to?(:virtual?) && name.virtual? }
56
+ end
53
57
  end
54
58
  end
55
59
  end
@@ -1,10 +1,10 @@
1
1
  <% if namespaced? -%>
2
- require_dependency "<%= namespaced_file_path %>/application_controller"
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: [:show, :update, :destroy]
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,7 +51,7 @@ class <%= controller_class_name %>Controller < ApplicationController
51
51
  @<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
52
52
  end
53
53
 
54
- # Never trust parameters from the scary internet, only allow the white list through.
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}" %>, {})
@@ -1,19 +1,17 @@
1
1
  <% if namespaced? -%>
2
- require_dependency "<%= namespaced_file_path %>/application_controller"
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: [:show, :edit, :update, :destroy]
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,39 @@ 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 @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully created.'" %> }
33
+ format.html { redirect_to @<%= singular_table_name %>, 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 @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully updated.'" %> }
46
+ format.html { redirect_to @<%= singular_table_name %>, 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 %>
63
58
  respond_to do |format|
64
- format.html { redirect_to <%= index_helper %>_url, notice: <%= "'#{human_name} was successfully destroyed.'" %> }
59
+ format.html { redirect_to <%= index_helper %>_url, notice: <%= %("#{human_name} was successfully destroyed.") %> }
65
60
  format.json { head :no_content }
66
61
  end
67
62
  end
@@ -72,7 +67,7 @@ class <%= controller_class_name %>Controller < ApplicationController
72
67
  @<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
73
68
  end
74
69
 
75
- # Never trust parameters from the scary internet, only allow the white list through.
70
+ # Only allow a list of trusted parameters through.
76
71
  def <%= "#{singular_table_name}_params" %>
77
72
  <%- if attributes_names.empty? -%>
78
73
  params.fetch(<%= ":#{singular_table_name}" %>, {})
@@ -1,2 +1,16 @@
1
1
  json.extract! <%= singular_table_name %>, <%= full_attributes_list %>
2
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 -%>
data/lib/jbuilder.rb CHANGED
@@ -4,16 +4,19 @@ require 'jbuilder/key_formatter'
4
4
  require 'jbuilder/errors'
5
5
  require 'json'
6
6
  require 'ostruct'
7
+ require 'active_support/core_ext/hash/deep_merge'
7
8
 
8
9
  class Jbuilder
9
10
  @@key_formatter = nil
10
11
  @@ignore_nil = false
12
+ @@deep_format_keys = false
11
13
 
12
14
  def initialize(options = {})
13
15
  @attributes = {}
14
16
 
15
17
  @key_formatter = options.fetch(:key_formatter){ @@key_formatter ? @@key_formatter.clone : nil}
16
18
  @ignore_nil = options.fetch(:ignore_nil, @@ignore_nil)
19
+ @deep_format_keys = options.fetch(:deep_format_keys, @@deep_format_keys)
17
20
 
18
21
  yield self if ::Kernel.block_given?
19
22
  end
@@ -26,12 +29,12 @@ class Jbuilder
26
29
  BLANK = Blank.new
27
30
  NON_ENUMERABLES = [ ::Struct, ::OpenStruct ].to_set
28
31
 
29
- def set!(key, value = BLANK, *args)
32
+ def set!(key, value = BLANK, *args, &block)
30
33
  result = if ::Kernel.block_given?
31
34
  if !_blank?(value)
32
35
  # json.comments @post.comments { |comment| ... }
33
36
  # { "comments": [ { ... }, { ... } ] }
34
- _scope{ array! value, &::Proc.new }
37
+ _scope{ array! value, &block }
35
38
  else
36
39
  # json.comments { ... }
37
40
  # { "comments": ... }
@@ -42,11 +45,11 @@ class Jbuilder
42
45
  # json.age 32
43
46
  # json.person another_jbuilder
44
47
  # { "age": 32, "person": { ... }
45
- value.attributes!
48
+ _format_keys(value.attributes!)
46
49
  else
47
50
  # json.age 32
48
51
  # { "age": 32 }
49
- value
52
+ _format_keys(value)
50
53
  end
51
54
  elsif _is_collection?(value)
52
55
  # json.comments @post.comments, :content, :created_at
@@ -61,9 +64,9 @@ class Jbuilder
61
64
  _set_value key, result
62
65
  end
63
66
 
64
- def method_missing(*args)
67
+ def method_missing(*args, &block)
65
68
  if ::Kernel.block_given?
66
- set!(*args, &::Proc.new)
69
+ set!(*args, &block)
67
70
  else
68
71
  set!(*args)
69
72
  end
@@ -130,6 +133,31 @@ class Jbuilder
130
133
  @@ignore_nil = value
131
134
  end
132
135
 
136
+ # Deeply apply key format to nested hashes and arrays passed to
137
+ # methods like set!, merge! or array!.
138
+ #
139
+ # Example:
140
+ #
141
+ # json.key_format! camelize: :lower
142
+ # json.settings({some_value: "abc"})
143
+ #
144
+ # { "settings": { "some_value": "abc" }}
145
+ #
146
+ # json.key_format! camelize: :lower
147
+ # json.deep_format_keys!
148
+ # json.settings({some_value: "abc"})
149
+ #
150
+ # { "settings": { "someValue": "abc" }}
151
+ #
152
+ def deep_format_keys!(value = true)
153
+ @deep_format_keys = value
154
+ end
155
+
156
+ # Same as instance method deep_format_keys! except sets the default.
157
+ def self.deep_format_keys(value = true)
158
+ @@deep_format_keys = value
159
+ end
160
+
133
161
  # Turns the current element into an array and yields a builder to add a hash.
134
162
  #
135
163
  # Example:
@@ -163,7 +191,7 @@ class Jbuilder
163
191
  #
164
192
  # [ { "name": David", "age": 32 }, { "name": Jamie", "age": 31 } ]
165
193
  #
166
- # If you are using Ruby 1.9+, you can use the call syntax instead of an explicit extract! call:
194
+ # You can use the call syntax instead of an explicit extract! call:
167
195
  #
168
196
  # json.(@people) { |person| ... }
169
197
  #
@@ -181,18 +209,18 @@ class Jbuilder
181
209
  # json.array! [1, 2, 3]
182
210
  #
183
211
  # [1,2,3]
184
- def array!(collection = [], *attributes)
212
+ def array!(collection = [], *attributes, &block)
185
213
  array = if collection.nil?
186
214
  []
187
215
  elsif ::Kernel.block_given?
188
- _map_collection(collection, &::Proc.new)
216
+ _map_collection(collection, &block)
189
217
  elsif attributes.any?
190
218
  _map_collection(collection) { |element| extract! element, *attributes }
191
219
  else
192
- collection.to_a
220
+ _format_keys(collection.to_a)
193
221
  end
194
222
 
195
- merge! array
223
+ @attributes = _merge_values(@attributes, array)
196
224
  end
197
225
 
198
226
  # Extracts the mentioned attributes or hash elements from the passed object and turns them into attributes of the JSON.
@@ -220,9 +248,9 @@ class Jbuilder
220
248
  end
221
249
  end
222
250
 
223
- def call(object, *attributes)
251
+ def call(object, *attributes, &block)
224
252
  if ::Kernel.block_given?
225
- array! object, &::Proc.new
253
+ array! object, &block
226
254
  else
227
255
  extract! object, *attributes
228
256
  end
@@ -240,24 +268,25 @@ class Jbuilder
240
268
  @attributes
241
269
  end
242
270
 
243
- # Merges hash or array into current builder.
244
- def merge!(hash_or_array)
245
- @attributes = _merge_values(@attributes, hash_or_array)
271
+ # Merges hash, array, or Jbuilder instance into current builder.
272
+ def merge!(object)
273
+ hash_or_array = ::Jbuilder === object ? object.attributes! : object
274
+ @attributes = _merge_values(@attributes, _format_keys(hash_or_array))
246
275
  end
247
276
 
248
277
  # Encodes the current builder as JSON.
249
278
  def target!
250
- ::JSON.dump(@attributes)
279
+ @attributes.to_json
251
280
  end
252
281
 
253
282
  private
254
283
 
255
284
  def _extract_hash_values(object, attributes)
256
- attributes.each{ |key| _set_value key, object.fetch(key) }
285
+ attributes.each{ |key| _set_value key, _format_keys(object.fetch(key)) }
257
286
  end
258
287
 
259
288
  def _extract_method_values(object, attributes)
260
- attributes.each{ |key| _set_value key, object.public_send(key) }
289
+ attributes.each{ |key| _set_value key, _format_keys(object.public_send(key)) }
261
290
  end
262
291
 
263
292
  def _merge_block(key)
@@ -275,7 +304,7 @@ class Jbuilder
275
304
  elsif ::Array === current_value && ::Array === updates
276
305
  current_value + updates
277
306
  elsif ::Hash === current_value && ::Hash === updates
278
- current_value.merge(updates)
307
+ current_value.deep_merge(updates)
279
308
  else
280
309
  raise MergeError.build(current_value, updates)
281
310
  end
@@ -285,6 +314,18 @@ class Jbuilder
285
314
  @key_formatter ? @key_formatter.format(key) : key.to_s
286
315
  end
287
316
 
317
+ def _format_keys(hash_or_array)
318
+ return hash_or_array unless @deep_format_keys
319
+
320
+ if ::Array === hash_or_array
321
+ hash_or_array.map { |value| _format_keys(value) }
322
+ elsif ::Hash === hash_or_array
323
+ ::Hash[hash_or_array.collect { |k, v| [_key(k), _format_keys(v)] }]
324
+ else
325
+ hash_or_array
326
+ end
327
+ end
328
+
288
329
  def _set_value(key, value)
289
330
  raise NullError.build(key) if @attributes.nil?
290
331
  raise ArrayError.build(key) if ::Array === @attributes
@@ -300,12 +341,12 @@ class Jbuilder
300
341
  end
301
342
 
302
343
  def _scope
303
- parent_attributes, parent_formatter = @attributes, @key_formatter
344
+ parent_attributes, parent_formatter, parent_deep_format_keys = @attributes, @key_formatter, @deep_format_keys
304
345
  @attributes = BLANK
305
346
  yield
306
347
  @attributes
307
348
  ensure
308
- @attributes, @key_formatter = parent_attributes, parent_formatter
349
+ @attributes, @key_formatter, @deep_format_keys = parent_attributes, parent_formatter, parent_deep_format_keys
309
350
  end
310
351
 
311
352
  def _is_collection?(object)
@@ -73,8 +73,8 @@ class JbuilderTemplate < Jbuilder
73
73
  # json.cache_if! !admin?, @person, expires_in: 10.minutes do
74
74
  # json.extract! @person, :name, :age
75
75
  # end
76
- def cache_if!(condition, *args)
77
- condition ? cache!(*args, &::Proc.new) : yield
76
+ def cache_if!(condition, *args, &block)
77
+ condition ? cache!(*args, &block) : yield
78
78
  end
79
79
 
80
80
  def target!
@@ -164,7 +164,7 @@ class JbuilderTemplate < Jbuilder
164
164
  if @context.respond_to?(:cache_fragment_name)
165
165
  # Current compatibility, fragment_name_with_digest is private again and cache_fragment_name
166
166
  # should be used instead.
167
- @context.cache_fragment_name(key, options)
167
+ @context.cache_fragment_name(key, **options)
168
168
  elsif @context.respond_to?(:fragment_name_with_digest)
169
169
  # Backwards compatibility for period of time when fragment_name_with_digest was made public.
170
170
  @context.fragment_name_with_digest(key)
@@ -43,4 +43,16 @@ class JbuilderGeneratorTest < Rails::Generators::TestCase
43
43
  assert_no_match %r{:created_at, :updated_at}, content
44
44
  end
45
45
  end
46
+
47
+ if Rails::VERSION::MAJOR >= 6
48
+ test 'handles virtual attributes' do
49
+ run_generator %w(Message content:rich_text video:attachment photos:attachments)
50
+
51
+ assert_file 'app/views/messages/_message.json.jbuilder' do |content|
52
+ assert_match %r{json\.content message\.content\.to_s}, content
53
+ assert_match %r{json\.video url_for\(message\.video\)}, content
54
+ assert_match %r{json\.photos do\n json\.array!\(message\.photos\) do \|photo\|\n json\.id photo\.id\n json\.url url_for\(photo\)\n end\nend}, content
55
+ end
56
+ end
57
+ end
46
58
  end
@@ -99,7 +99,7 @@ class JbuilderTest < ActiveSupport::TestCase
99
99
  assert_equal 32, result['age']
100
100
  end
101
101
 
102
- test 'extracting from object using call style for 1.9' do
102
+ test 'extracting from object using call style' do
103
103
  person = Struct.new(:name, :age).new('David', 32)
104
104
 
105
105
  result = jbuild do |json|
@@ -159,6 +159,25 @@ class JbuilderTest < ActiveSupport::TestCase
159
159
  assert_equal 32, result['author']['age']
160
160
  end
161
161
 
162
+ test 'nested blocks are additive' do
163
+ result = jbuild do |json|
164
+ json.author do
165
+ json.name do
166
+ json.first 'David'
167
+ end
168
+ end
169
+
170
+ json.author do
171
+ json.name do
172
+ json.last 'Heinemeier Hansson'
173
+ end
174
+ end
175
+ end
176
+
177
+ assert_equal 'David', result['author']['name']['first']
178
+ assert_equal 'Heinemeier Hansson', result['author']['name']['last']
179
+ end
180
+
162
181
  test 'support merge! method' do
163
182
  result = jbuild do |json|
164
183
  json.merge! 'foo' => 'bar'
@@ -177,6 +196,18 @@ class JbuilderTest < ActiveSupport::TestCase
177
196
  assert_equal 'Pavel', result['author']['name']
178
197
  end
179
198
 
199
+ test 'support merge! method with Jbuilder instance' do
200
+ obj = jbuild do |json|
201
+ json.foo 'bar'
202
+ end
203
+
204
+ result = jbuild do |json|
205
+ json.merge! obj
206
+ end
207
+
208
+ assert_equal 'bar', result['foo']
209
+ end
210
+
180
211
  test 'blocks are additive via extract syntax' do
181
212
  person = Person.new('Pavel', 27)
182
213
 
@@ -535,6 +566,36 @@ class JbuilderTest < ActiveSupport::TestCase
535
566
  assert_equal 'one', result['level1']
536
567
  end
537
568
 
569
+ test 'key_format! can be changed in child elements' do
570
+ result = jbuild do |json|
571
+ json.key_format! camelize: :lower
572
+
573
+ json.level_one do
574
+ json.key_format! :upcase
575
+ json.value 'two'
576
+ end
577
+ end
578
+
579
+ assert_equal ['levelOne'], result.keys
580
+ assert_equal ['VALUE'], result['levelOne'].keys
581
+ end
582
+
583
+ test 'key_format! can be changed in array!' do
584
+ result = jbuild do |json|
585
+ json.key_format! camelize: :lower
586
+
587
+ json.level_one do
588
+ json.array! [{value: 'two'}] do |object|
589
+ json.key_format! :upcase
590
+ json.value object[:value]
591
+ end
592
+ end
593
+ end
594
+
595
+ assert_equal ['levelOne'], result.keys
596
+ assert_equal ['VALUE'], result['levelOne'][0].keys
597
+ end
598
+
538
599
  test 'key_format! with no parameter' do
539
600
  result = jbuild do |json|
540
601
  json.key_format! :upcase
@@ -562,6 +623,161 @@ class JbuilderTest < ActiveSupport::TestCase
562
623
  assert_equal ['oats and friends'], result.keys
563
624
  end
564
625
 
626
+ test 'key_format! is not applied deeply by default' do
627
+ names = { first_name: 'camel', last_name: 'case' }
628
+ result = jbuild do |json|
629
+ json.key_format! camelize: :lower
630
+ json.set! :all_names, names
631
+ end
632
+
633
+ assert_equal %i[first_name last_name], result['allNames'].keys
634
+ end
635
+
636
+ test 'applying key_format! deeply can be enabled per scope' do
637
+ names = { first_name: 'camel', last_name: 'case' }
638
+ result = jbuild do |json|
639
+ json.key_format! camelize: :lower
640
+ json.scope do
641
+ json.deep_format_keys!
642
+ json.set! :all_names, names
643
+ end
644
+ json.set! :all_names, names
645
+ end
646
+
647
+ assert_equal %w[firstName lastName], result['scope']['allNames'].keys
648
+ assert_equal %i[first_name last_name], result['allNames'].keys
649
+ end
650
+
651
+ test 'applying key_format! deeply can be disabled per scope' do
652
+ names = { first_name: 'camel', last_name: 'case' }
653
+ result = jbuild do |json|
654
+ json.key_format! camelize: :lower
655
+ json.deep_format_keys!
656
+ json.set! :all_names, names
657
+ json.scope do
658
+ json.deep_format_keys! false
659
+ json.set! :all_names, names
660
+ end
661
+ end
662
+
663
+ assert_equal %w[firstName lastName], result['allNames'].keys
664
+ assert_equal %i[first_name last_name], result['scope']['allNames'].keys
665
+ end
666
+
667
+ test 'applying key_format! deeply can be enabled globally' do
668
+ names = { first_name: 'camel', last_name: 'case' }
669
+
670
+ Jbuilder.deep_format_keys true
671
+ result = jbuild do |json|
672
+ json.key_format! camelize: :lower
673
+ json.set! :all_names, names
674
+ end
675
+
676
+ assert_equal %w[firstName lastName], result['allNames'].keys
677
+ Jbuilder.send(:class_variable_set, '@@deep_format_keys', false)
678
+ end
679
+
680
+ test 'deep key_format! with merge!' do
681
+ hash = { camel_style: 'for JS' }
682
+ result = jbuild do |json|
683
+ json.key_format! camelize: :lower
684
+ json.deep_format_keys!
685
+ json.merge! hash
686
+ end
687
+
688
+ assert_equal ['camelStyle'], result.keys
689
+ end
690
+
691
+ test 'deep key_format! with merge! deep' do
692
+ hash = { camel_style: { sub_attr: 'for JS' } }
693
+ result = jbuild do |json|
694
+ json.key_format! camelize: :lower
695
+ json.deep_format_keys!
696
+ json.merge! hash
697
+ end
698
+
699
+ assert_equal ['subAttr'], result['camelStyle'].keys
700
+ end
701
+
702
+ test 'deep key_format! with set! array of hashes' do
703
+ names = [{ first_name: 'camel', last_name: 'case' }]
704
+ result = jbuild do |json|
705
+ json.key_format! camelize: :lower
706
+ json.deep_format_keys!
707
+ json.set! :names, names
708
+ end
709
+
710
+ assert_equal %w[firstName lastName], result['names'][0].keys
711
+ end
712
+
713
+ test 'deep key_format! with set! extracting hash from object' do
714
+ comment = Struct.new(:author).new({ first_name: 'camel', last_name: 'case' })
715
+ result = jbuild do |json|
716
+ json.key_format! camelize: :lower
717
+ json.deep_format_keys!
718
+ json.set! :comment, comment, :author
719
+ end
720
+
721
+ assert_equal %w[firstName lastName], result['comment']['author'].keys
722
+ end
723
+
724
+ test 'deep key_format! with array! of hashes' do
725
+ names = [{ first_name: 'camel', last_name: 'case' }]
726
+ result = jbuild do |json|
727
+ json.key_format! camelize: :lower
728
+ json.deep_format_keys!
729
+ json.array! names
730
+ end
731
+
732
+ assert_equal %w[firstName lastName], result[0].keys
733
+ end
734
+
735
+ test 'deep key_format! with merge! array of hashes' do
736
+ names = [{ first_name: 'camel', last_name: 'case' }]
737
+ new_names = [{ first_name: 'snake', last_name: 'case' }]
738
+ result = jbuild do |json|
739
+ json.key_format! camelize: :lower
740
+ json.deep_format_keys!
741
+ json.array! names
742
+ json.merge! new_names
743
+ end
744
+
745
+ assert_equal %w[firstName lastName], result[1].keys
746
+ end
747
+
748
+ test 'deep key_format! is applied to hash extracted from object' do
749
+ comment = Struct.new(:author).new({ first_name: 'camel', last_name: 'case' })
750
+ result = jbuild do |json|
751
+ json.key_format! camelize: :lower
752
+ json.deep_format_keys!
753
+ json.extract! comment, :author
754
+ end
755
+
756
+ assert_equal %w[firstName lastName], result['author'].keys
757
+ end
758
+
759
+ test 'deep key_format! is applied to hash extracted from hash' do
760
+ comment = {author: { first_name: 'camel', last_name: 'case' }}
761
+ result = jbuild do |json|
762
+ json.key_format! camelize: :lower
763
+ json.deep_format_keys!
764
+ json.extract! comment, :author
765
+ end
766
+
767
+ assert_equal %w[firstName lastName], result['author'].keys
768
+ end
769
+
770
+ test 'deep key_format! is applied to hash extracted directly from array' do
771
+ comments = [Struct.new(:author).new({ first_name: 'camel', last_name: 'case' })]
772
+ result = jbuild do |json|
773
+ json.key_format! camelize: :lower
774
+ json.deep_format_keys!
775
+ json.array! comments, :author
776
+ end
777
+
778
+ assert_equal %w[firstName lastName], result[0]['author'].keys
779
+ end
780
+
565
781
  test 'default key_format!' do
566
782
  Jbuilder.key_format camelize: :lower
567
783
  result = jbuild{ |json| json.camel_style 'for JS' }
@@ -713,4 +929,13 @@ class JbuilderTest < ActiveSupport::TestCase
713
929
  end
714
930
  end
715
931
  end
932
+
933
+ if RUBY_VERSION >= "2.2.10"
934
+ test "respects JSON encoding customizations" do
935
+ # Active Support overrides Time#as_json for custom formatting.
936
+ # Ensure we call #to_json on the final attributes instead of JSON.dump.
937
+ result = JSON.load(Jbuilder.encode { |json| json.time Time.parse("2018-05-13 11:51:00.485 -0400") })
938
+ assert_equal "2018-05-13T11:51:00.485-04:00", result["time"]
939
+ end
940
+ end
716
941
  end
@@ -55,5 +55,16 @@ if Rails::VERSION::MAJOR > 4
55
55
  assert_match %r{params\.fetch\(:post, \{\}\)}, content
56
56
  end
57
57
  end
58
+
59
+
60
+ if Rails::VERSION::MAJOR >= 6
61
+ test 'handles virtual attributes' do
62
+ run_generator ["Message", "content:rich_text", "video:attachment", "photos:attachments"]
63
+
64
+ assert_file 'app/controllers/messages_controller.rb' do |content|
65
+ assert_match %r{params\.require\(:message\)\.permit\(:content, :video, photos: \[\]\)}, content
66
+ end
67
+ end
68
+ end
58
69
  end
59
70
  end
@@ -31,22 +31,22 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
31
31
  assert_instance_method :create, content do |m|
32
32
  assert_match %r{@post = Post\.new\(post_params\)}, m
33
33
  assert_match %r{@post\.save}, m
34
- assert_match %r{format\.html \{ redirect_to @post, notice: 'Post was successfully created\.' \}}, m
34
+ assert_match %r{format\.html \{ redirect_to @post, notice: "Post was successfully created\." \}}, m
35
35
  assert_match %r{format\.json \{ render :show, status: :created, location: @post \}}, m
36
- assert_match %r{format\.html \{ render :new \}}, m
36
+ assert_match %r{format\.html \{ render :new, status: :unprocessable_entity \}}, m
37
37
  assert_match %r{format\.json \{ render json: @post\.errors, status: :unprocessable_entity \}}, m
38
38
  end
39
39
 
40
40
  assert_instance_method :update, content do |m|
41
- assert_match %r{format\.html \{ redirect_to @post, notice: 'Post was successfully updated\.' \}}, m
41
+ assert_match %r{format\.html \{ redirect_to @post, notice: "Post was successfully updated\." \}}, m
42
42
  assert_match %r{format\.json \{ render :show, status: :ok, location: @post \}}, m
43
- assert_match %r{format\.html \{ render :edit \}}, m
43
+ assert_match %r{format\.html \{ render :edit, status: :unprocessable_entity \}}, m
44
44
  assert_match %r{format\.json \{ render json: @post.errors, status: :unprocessable_entity \}}, m
45
45
  end
46
46
 
47
47
  assert_instance_method :destroy, content do |m|
48
48
  assert_match %r{@post\.destroy}, m
49
- assert_match %r{format\.html \{ redirect_to posts_url, notice: 'Post was successfully destroyed\.' \}}, m
49
+ assert_match %r{format\.html \{ redirect_to posts_url, notice: "Post was successfully destroyed\." \}}, m
50
50
  assert_match %r{format\.json \{ head :no_content \}}, m
51
51
  end
52
52
 
@@ -67,4 +67,14 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
67
67
  assert_match %r{params\.fetch\(:post, \{\}\)}, content
68
68
  end
69
69
  end
70
+
71
+ if Rails::VERSION::MAJOR >= 6
72
+ test 'handles virtual attributes' do
73
+ run_generator %w(Message content:rich_text video:attachment photos:attachments)
74
+
75
+ assert_file 'app/controllers/messages_controller.rb' do |content|
76
+ assert_match %r{params\.require\(:message\)\.permit\(:content, :video, photos: \[\]\)}, content
77
+ end
78
+ end
79
+ end
70
80
  end
data/test/test_helper.rb CHANGED
@@ -3,6 +3,7 @@ require "bundler/setup"
3
3
  require "active_support"
4
4
  require "active_support/core_ext/array/access"
5
5
  require "active_support/cache/memory_store"
6
+ require "active_support/json"
6
7
  require "active_model"
7
8
  require "action_view"
8
9
  require "rails/version"
@@ -10,7 +11,7 @@ require "rails/version"
10
11
  require "jbuilder"
11
12
 
12
13
  require "active_support/testing/autorun"
13
- require "mocha/setup"
14
+ require "mocha/minitest"
14
15
 
15
16
  ActiveSupport.test_order = :random
16
17
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jbuilder
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.9.0
4
+ version: 2.11.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-05-13 00:00:00.000000000 Z
11
+ date: 2021-01-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 4.2.0
19
+ version: 5.0.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 4.2.0
26
+ version: 5.0.0
27
27
  description:
28
28
  email: david@basecamp.com
29
29
  executables: []
@@ -39,10 +39,10 @@ files:
39
39
  - MIT-LICENSE
40
40
  - README.md
41
41
  - Rakefile
42
- - gemfiles/rails_4_2.gemfile
43
42
  - gemfiles/rails_5_0.gemfile
44
43
  - gemfiles/rails_5_1.gemfile
45
44
  - gemfiles/rails_5_2.gemfile
45
+ - gemfiles/rails_6_0.gemfile
46
46
  - gemfiles/rails_head.gemfile
47
47
  - jbuilder.gemspec
48
48
  - lib/generators/rails/jbuilder_generator.rb
@@ -79,14 +79,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
- version: 1.9.3
82
+ version: 2.2.2
83
83
  required_rubygems_version: !ruby/object:Gem::Requirement
84
84
  requirements:
85
85
  - - ">="
86
86
  - !ruby/object:Gem::Version
87
87
  version: '0'
88
88
  requirements: []
89
- rubygems_version: 3.0.3
89
+ rubygems_version: 3.1.2
90
90
  signing_key:
91
91
  specification_version: 4
92
92
  summary: Create JSON structures via a Builder-style DSL