jbuilder 2.9.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 +4 -4
 - data/.github/workflows/ruby.yml +108 -0
 - data/.gitignore +2 -0
 - data/Appraisals +15 -13
 - data/CONTRIBUTING.md +4 -6
 - data/README.md +73 -9
 - data/Rakefile +1 -1
 - data/gemfiles/{rails_4_2.gemfile → rails_6_0.gemfile} +1 -1
 - data/gemfiles/rails_6_1.gemfile +10 -0
 - data/gemfiles/rails_head.gemfile +1 -1
 - data/jbuilder.gemspec +18 -3
 - data/lib/generators/rails/jbuilder_generator.rb +4 -0
 - data/lib/generators/rails/templates/api_controller.rb +3 -3
 - data/lib/generators/rails/templates/controller.rb +14 -18
 - data/lib/generators/rails/templates/partial.json.jbuilder +14 -0
 - data/lib/jbuilder/collection_renderer.rb +109 -0
 - data/lib/jbuilder/jbuilder_template.rb +57 -10
 - data/lib/jbuilder/railtie.rb +1 -1
 - data/lib/jbuilder.rb +64 -22
 - data/test/jbuilder_dependency_tracker_test.rb +1 -1
 - data/test/jbuilder_generator_test.rb +12 -0
 - data/test/jbuilder_template_test.rb +100 -2
 - data/test/jbuilder_test.rb +227 -2
 - data/test/scaffold_api_controller_generator_test.rb +12 -1
 - data/test/scaffold_controller_generator_test.rb +35 -6
 - data/test/test_helper.rb +13 -10
 - metadata +30 -10
 - data/.travis.yml +0 -60
 - data/CHANGELOG.md +0 -261
 
| 
         @@ -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 
     | 
    
         | 
| 
         @@ -15,6 +16,38 @@ class JbuilderTemplate < Jbuilder 
     | 
|
| 
       15 
16 
     | 
    
         
             
                super(*args)
         
     | 
| 
       16 
17 
     | 
    
         
             
              end
         
     | 
| 
       17 
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 
     | 
    
         
            +
              #
         
     | 
| 
       18 
51 
     | 
    
         
             
              def partial!(*args)
         
     | 
| 
       19 
52 
     | 
    
         
             
                if args.one? && _is_active_model?(args.first)
         
     | 
| 
       20 
53 
     | 
    
         
             
                  _render_active_model_partial args.first
         
     | 
| 
         @@ -73,8 +106,8 @@ class JbuilderTemplate < Jbuilder 
     | 
|
| 
       73 
106 
     | 
    
         
             
              #   json.cache_if! !admin?, @person, expires_in: 10.minutes do
         
     | 
| 
       74 
107 
     | 
    
         
             
              #     json.extract! @person, :name, :age
         
     | 
| 
       75 
108 
     | 
    
         
             
              #   end
         
     | 
| 
       76 
     | 
    
         
            -
              def cache_if!(condition, *args)
         
     | 
| 
       77 
     | 
    
         
            -
                condition ? cache!(*args,  
     | 
| 
      
 109 
     | 
    
         
            +
              def cache_if!(condition, *args, &block)
         
     | 
| 
      
 110 
     | 
    
         
            +
                condition ? cache!(*args, &block) : yield
         
     | 
| 
       78 
111 
     | 
    
         
             
              end
         
     | 
| 
       79 
112 
     | 
    
         | 
| 
       80 
113 
     | 
    
         
             
              def target!
         
     | 
| 
         @@ -104,11 +137,30 @@ class JbuilderTemplate < Jbuilder 
     | 
|
| 
       104 
137 
     | 
    
         
             
              private
         
     | 
| 
       105 
138 
     | 
    
         | 
| 
       106 
139 
     | 
    
         
             
              def _render_partial_with_options(options)
         
     | 
| 
       107 
     | 
    
         
            -
                options.reverse_merge! locals: options.except(:partial, :as, :collection)
         
     | 
| 
      
 140 
     | 
    
         
            +
                options.reverse_merge! locals: options.except(:partial, :as, :collection, :cached)
         
     | 
| 
       108 
141 
     | 
    
         
             
                options.reverse_merge! ::JbuilderTemplate.template_lookup_options
         
     | 
| 
       109 
142 
     | 
    
         
             
                as = options[:as]
         
     | 
| 
       110 
143 
     | 
    
         | 
| 
       111 
     | 
    
         
            -
                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:
         
     | 
| 
       112 
164 
     | 
    
         
             
                  as = as.to_sym
         
     | 
| 
       113 
165 
     | 
    
         
             
                  collection = options.delete(:collection)
         
     | 
| 
       114 
166 
     | 
    
         
             
                  locals = options.delete(:locals)
         
     | 
| 
         @@ -162,12 +214,7 @@ class JbuilderTemplate < Jbuilder 
     | 
|
| 
       162 
214 
     | 
    
         | 
| 
       163 
215 
     | 
    
         
             
              def _fragment_name_with_digest(key, options)
         
     | 
| 
       164 
216 
     | 
    
         
             
                if @context.respond_to?(:cache_fragment_name)
         
     | 
| 
       165 
     | 
    
         
            -
                   
     | 
| 
       166 
     | 
    
         
            -
                  # should be used instead.
         
     | 
| 
       167 
     | 
    
         
            -
                  @context.cache_fragment_name(key, options)
         
     | 
| 
       168 
     | 
    
         
            -
                elsif @context.respond_to?(:fragment_name_with_digest)
         
     | 
| 
       169 
     | 
    
         
            -
                  # Backwards compatibility for period of time when fragment_name_with_digest was made public.
         
     | 
| 
       170 
     | 
    
         
            -
                  @context.fragment_name_with_digest(key)
         
     | 
| 
      
 217 
     | 
    
         
            +
                  @context.cache_fragment_name(key, **options)
         
     | 
| 
       171 
218 
     | 
    
         
             
                else
         
     | 
| 
       172 
219 
     | 
    
         
             
                  key
         
     | 
| 
       173 
220 
     | 
    
         
             
                end
         
     | 
    
        data/lib/jbuilder/railtie.rb
    CHANGED
    
    
    
        data/lib/jbuilder.rb
    CHANGED
    
    | 
         @@ -1,19 +1,23 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'active_support'
         
     | 
| 
       1 
2 
     | 
    
         
             
            require 'jbuilder/jbuilder'
         
     | 
| 
       2 
3 
     | 
    
         
             
            require 'jbuilder/blank'
         
     | 
| 
       3 
4 
     | 
    
         
             
            require 'jbuilder/key_formatter'
         
     | 
| 
       4 
5 
     | 
    
         
             
            require 'jbuilder/errors'
         
     | 
| 
       5 
6 
     | 
    
         
             
            require 'json'
         
     | 
| 
       6 
7 
     | 
    
         
             
            require 'ostruct'
         
     | 
| 
      
 8 
     | 
    
         
            +
            require 'active_support/core_ext/hash/deep_merge'
         
     | 
| 
       7 
9 
     | 
    
         | 
| 
       8 
10 
     | 
    
         
             
            class Jbuilder
         
     | 
| 
       9 
11 
     | 
    
         
             
              @@key_formatter = nil
         
     | 
| 
       10 
12 
     | 
    
         
             
              @@ignore_nil    = false
         
     | 
| 
      
 13 
     | 
    
         
            +
              @@deep_format_keys = false
         
     | 
| 
       11 
14 
     | 
    
         | 
| 
       12 
15 
     | 
    
         
             
              def initialize(options = {})
         
     | 
| 
       13 
16 
     | 
    
         
             
                @attributes = {}
         
     | 
| 
       14 
17 
     | 
    
         | 
| 
       15 
18 
     | 
    
         
             
                @key_formatter = options.fetch(:key_formatter){ @@key_formatter ? @@key_formatter.clone : nil}
         
     | 
| 
       16 
19 
     | 
    
         
             
                @ignore_nil = options.fetch(:ignore_nil, @@ignore_nil)
         
     | 
| 
      
 20 
     | 
    
         
            +
                @deep_format_keys = options.fetch(:deep_format_keys, @@deep_format_keys)
         
     | 
| 
       17 
21 
     | 
    
         | 
| 
       18 
22 
     | 
    
         
             
                yield self if ::Kernel.block_given?
         
     | 
| 
       19 
23 
     | 
    
         
             
              end
         
     | 
| 
         @@ -26,12 +30,12 @@ class Jbuilder 
     | 
|
| 
       26 
30 
     | 
    
         
             
              BLANK = Blank.new
         
     | 
| 
       27 
31 
     | 
    
         
             
              NON_ENUMERABLES = [ ::Struct, ::OpenStruct ].to_set
         
     | 
| 
       28 
32 
     | 
    
         | 
| 
       29 
     | 
    
         
            -
              def set!(key, value = BLANK, *args)
         
     | 
| 
      
 33 
     | 
    
         
            +
              def set!(key, value = BLANK, *args, &block)
         
     | 
| 
       30 
34 
     | 
    
         
             
                result = if ::Kernel.block_given?
         
     | 
| 
       31 
35 
     | 
    
         
             
                  if !_blank?(value)
         
     | 
| 
       32 
36 
     | 
    
         
             
                    # json.comments @post.comments { |comment| ... }
         
     | 
| 
       33 
37 
     | 
    
         
             
                    # { "comments": [ { ... }, { ... } ] }
         
     | 
| 
       34 
     | 
    
         
            -
                    _scope{ array! value,  
     | 
| 
      
 38 
     | 
    
         
            +
                    _scope{ array! value, &block }
         
     | 
| 
       35 
39 
     | 
    
         
             
                  else
         
     | 
| 
       36 
40 
     | 
    
         
             
                    # json.comments { ... }
         
     | 
| 
       37 
41 
     | 
    
         
             
                    # { "comments": ... }
         
     | 
| 
         @@ -42,11 +46,11 @@ class Jbuilder 
     | 
|
| 
       42 
46 
     | 
    
         
             
                    # json.age 32
         
     | 
| 
       43 
47 
     | 
    
         
             
                    # json.person another_jbuilder
         
     | 
| 
       44 
48 
     | 
    
         
             
                    # { "age": 32, "person": { ...  }
         
     | 
| 
       45 
     | 
    
         
            -
                    value.attributes!
         
     | 
| 
      
 49 
     | 
    
         
            +
                    _format_keys(value.attributes!)
         
     | 
| 
       46 
50 
     | 
    
         
             
                  else
         
     | 
| 
       47 
51 
     | 
    
         
             
                    # json.age 32
         
     | 
| 
       48 
52 
     | 
    
         
             
                    # { "age": 32 }
         
     | 
| 
       49 
     | 
    
         
            -
                    value
         
     | 
| 
      
 53 
     | 
    
         
            +
                    _format_keys(value)
         
     | 
| 
       50 
54 
     | 
    
         
             
                  end
         
     | 
| 
       51 
55 
     | 
    
         
             
                elsif _is_collection?(value)
         
     | 
| 
       52 
56 
     | 
    
         
             
                  # json.comments @post.comments, :content, :created_at
         
     | 
| 
         @@ -61,9 +65,9 @@ class Jbuilder 
     | 
|
| 
       61 
65 
     | 
    
         
             
                _set_value key, result
         
     | 
| 
       62 
66 
     | 
    
         
             
              end
         
     | 
| 
       63 
67 
     | 
    
         | 
| 
       64 
     | 
    
         
            -
              def method_missing(*args)
         
     | 
| 
      
 68 
     | 
    
         
            +
              def method_missing(*args, &block)
         
     | 
| 
       65 
69 
     | 
    
         
             
                if ::Kernel.block_given?
         
     | 
| 
       66 
     | 
    
         
            -
                  set!(*args,  
     | 
| 
      
 70 
     | 
    
         
            +
                  set!(*args, &block)
         
     | 
| 
       67 
71 
     | 
    
         
             
                else
         
     | 
| 
       68 
72 
     | 
    
         
             
                  set!(*args)
         
     | 
| 
       69 
73 
     | 
    
         
             
                end
         
     | 
| 
         @@ -130,6 +134,31 @@ class Jbuilder 
     | 
|
| 
       130 
134 
     | 
    
         
             
                @@ignore_nil = value
         
     | 
| 
       131 
135 
     | 
    
         
             
              end
         
     | 
| 
       132 
136 
     | 
    
         | 
| 
      
 137 
     | 
    
         
            +
              # Deeply apply key format to nested hashes and arrays passed to
         
     | 
| 
      
 138 
     | 
    
         
            +
              # methods like set!, merge! or array!.
         
     | 
| 
      
 139 
     | 
    
         
            +
              #
         
     | 
| 
      
 140 
     | 
    
         
            +
              # Example:
         
     | 
| 
      
 141 
     | 
    
         
            +
              #
         
     | 
| 
      
 142 
     | 
    
         
            +
              #   json.key_format! camelize: :lower
         
     | 
| 
      
 143 
     | 
    
         
            +
              #   json.settings({some_value: "abc"})
         
     | 
| 
      
 144 
     | 
    
         
            +
              #
         
     | 
| 
      
 145 
     | 
    
         
            +
              #   { "settings": { "some_value": "abc" }}
         
     | 
| 
      
 146 
     | 
    
         
            +
              #
         
     | 
| 
      
 147 
     | 
    
         
            +
              #   json.key_format! camelize: :lower
         
     | 
| 
      
 148 
     | 
    
         
            +
              #   json.deep_format_keys!
         
     | 
| 
      
 149 
     | 
    
         
            +
              #   json.settings({some_value: "abc"})
         
     | 
| 
      
 150 
     | 
    
         
            +
              #
         
     | 
| 
      
 151 
     | 
    
         
            +
              #   { "settings": { "someValue": "abc" }}
         
     | 
| 
      
 152 
     | 
    
         
            +
              #
         
     | 
| 
      
 153 
     | 
    
         
            +
              def deep_format_keys!(value = true)
         
     | 
| 
      
 154 
     | 
    
         
            +
                @deep_format_keys = value
         
     | 
| 
      
 155 
     | 
    
         
            +
              end
         
     | 
| 
      
 156 
     | 
    
         
            +
             
     | 
| 
      
 157 
     | 
    
         
            +
              # Same as instance method deep_format_keys! except sets the default.
         
     | 
| 
      
 158 
     | 
    
         
            +
              def self.deep_format_keys(value = true)
         
     | 
| 
      
 159 
     | 
    
         
            +
                @@deep_format_keys = value
         
     | 
| 
      
 160 
     | 
    
         
            +
              end
         
     | 
| 
      
 161 
     | 
    
         
            +
             
     | 
| 
       133 
162 
     | 
    
         
             
              # Turns the current element into an array and yields a builder to add a hash.
         
     | 
| 
       134 
163 
     | 
    
         
             
              #
         
     | 
| 
       135 
164 
     | 
    
         
             
              # Example:
         
     | 
| 
         @@ -163,7 +192,7 @@ class Jbuilder 
     | 
|
| 
       163 
192 
     | 
    
         
             
              #
         
     | 
| 
       164 
193 
     | 
    
         
             
              #   [ { "name": David", "age": 32 }, { "name": Jamie", "age": 31 } ]
         
     | 
| 
       165 
194 
     | 
    
         
             
              #
         
     | 
| 
       166 
     | 
    
         
            -
              #  
     | 
| 
      
 195 
     | 
    
         
            +
              # You can use the call syntax instead of an explicit extract! call:
         
     | 
| 
       167 
196 
     | 
    
         
             
              #
         
     | 
| 
       168 
197 
     | 
    
         
             
              #   json.(@people) { |person| ... }
         
     | 
| 
       169 
198 
     | 
    
         
             
              #
         
     | 
| 
         @@ -181,18 +210,18 @@ class Jbuilder 
     | 
|
| 
       181 
210 
     | 
    
         
             
              #   json.array! [1, 2, 3]
         
     | 
| 
       182 
211 
     | 
    
         
             
              #
         
     | 
| 
       183 
212 
     | 
    
         
             
              #   [1,2,3]
         
     | 
| 
       184 
     | 
    
         
            -
              def array!(collection = [], *attributes)
         
     | 
| 
      
 213 
     | 
    
         
            +
              def array!(collection = [], *attributes, &block)
         
     | 
| 
       185 
214 
     | 
    
         
             
                array = if collection.nil?
         
     | 
| 
       186 
215 
     | 
    
         
             
                  []
         
     | 
| 
       187 
216 
     | 
    
         
             
                elsif ::Kernel.block_given?
         
     | 
| 
       188 
     | 
    
         
            -
                  _map_collection(collection,  
     | 
| 
      
 217 
     | 
    
         
            +
                  _map_collection(collection, &block)
         
     | 
| 
       189 
218 
     | 
    
         
             
                elsif attributes.any?
         
     | 
| 
       190 
219 
     | 
    
         
             
                  _map_collection(collection) { |element| extract! element, *attributes }
         
     | 
| 
       191 
220 
     | 
    
         
             
                else
         
     | 
| 
       192 
     | 
    
         
            -
                  collection.to_a
         
     | 
| 
      
 221 
     | 
    
         
            +
                  _format_keys(collection.to_a)
         
     | 
| 
       193 
222 
     | 
    
         
             
                end
         
     | 
| 
       194 
223 
     | 
    
         | 
| 
       195 
     | 
    
         
            -
                 
     | 
| 
      
 224 
     | 
    
         
            +
                @attributes = _merge_values(@attributes, array)
         
     | 
| 
       196 
225 
     | 
    
         
             
              end
         
     | 
| 
       197 
226 
     | 
    
         | 
| 
       198 
227 
     | 
    
         
             
              # Extracts the mentioned attributes or hash elements from the passed object and turns them into attributes of the JSON.
         
     | 
| 
         @@ -220,9 +249,9 @@ class Jbuilder 
     | 
|
| 
       220 
249 
     | 
    
         
             
                end
         
     | 
| 
       221 
250 
     | 
    
         
             
              end
         
     | 
| 
       222 
251 
     | 
    
         | 
| 
       223 
     | 
    
         
            -
              def call(object, *attributes)
         
     | 
| 
      
 252 
     | 
    
         
            +
              def call(object, *attributes, &block)
         
     | 
| 
       224 
253 
     | 
    
         
             
                if ::Kernel.block_given?
         
     | 
| 
       225 
     | 
    
         
            -
                  array! object,  
     | 
| 
      
 254 
     | 
    
         
            +
                  array! object, &block
         
     | 
| 
       226 
255 
     | 
    
         
             
                else
         
     | 
| 
       227 
256 
     | 
    
         
             
                  extract! object, *attributes
         
     | 
| 
       228 
257 
     | 
    
         
             
                end
         
     | 
| 
         @@ -240,24 +269,25 @@ class Jbuilder 
     | 
|
| 
       240 
269 
     | 
    
         
             
                @attributes
         
     | 
| 
       241 
270 
     | 
    
         
             
              end
         
     | 
| 
       242 
271 
     | 
    
         | 
| 
       243 
     | 
    
         
            -
              # Merges hash or  
     | 
| 
       244 
     | 
    
         
            -
              def merge!( 
     | 
| 
       245 
     | 
    
         
            -
                 
     | 
| 
      
 272 
     | 
    
         
            +
              # Merges hash, array, or Jbuilder instance into current builder.
         
     | 
| 
      
 273 
     | 
    
         
            +
              def merge!(object)
         
     | 
| 
      
 274 
     | 
    
         
            +
                hash_or_array = ::Jbuilder === object ? object.attributes! : object
         
     | 
| 
      
 275 
     | 
    
         
            +
                @attributes = _merge_values(@attributes, _format_keys(hash_or_array))
         
     | 
| 
       246 
276 
     | 
    
         
             
              end
         
     | 
| 
       247 
277 
     | 
    
         | 
| 
       248 
278 
     | 
    
         
             
              # Encodes the current builder as JSON.
         
     | 
| 
       249 
279 
     | 
    
         
             
              def target!
         
     | 
| 
       250 
     | 
    
         
            -
                 
     | 
| 
      
 280 
     | 
    
         
            +
                @attributes.to_json
         
     | 
| 
       251 
281 
     | 
    
         
             
              end
         
     | 
| 
       252 
282 
     | 
    
         | 
| 
       253 
283 
     | 
    
         
             
              private
         
     | 
| 
       254 
284 
     | 
    
         | 
| 
       255 
285 
     | 
    
         
             
              def _extract_hash_values(object, attributes)
         
     | 
| 
       256 
     | 
    
         
            -
                attributes.each{ |key| _set_value key, object.fetch(key) }
         
     | 
| 
      
 286 
     | 
    
         
            +
                attributes.each{ |key| _set_value key, _format_keys(object.fetch(key)) }
         
     | 
| 
       257 
287 
     | 
    
         
             
              end
         
     | 
| 
       258 
288 
     | 
    
         | 
| 
       259 
289 
     | 
    
         
             
              def _extract_method_values(object, attributes)
         
     | 
| 
       260 
     | 
    
         
            -
                attributes.each{ |key| _set_value key, object.public_send(key) }
         
     | 
| 
      
 290 
     | 
    
         
            +
                attributes.each{ |key| _set_value key, _format_keys(object.public_send(key)) }
         
     | 
| 
       261 
291 
     | 
    
         
             
              end
         
     | 
| 
       262 
292 
     | 
    
         | 
| 
       263 
293 
     | 
    
         
             
              def _merge_block(key)
         
     | 
| 
         @@ -275,7 +305,7 @@ class Jbuilder 
     | 
|
| 
       275 
305 
     | 
    
         
             
                elsif ::Array === current_value && ::Array === updates
         
     | 
| 
       276 
306 
     | 
    
         
             
                  current_value + updates
         
     | 
| 
       277 
307 
     | 
    
         
             
                elsif ::Hash === current_value && ::Hash === updates
         
     | 
| 
       278 
     | 
    
         
            -
                  current_value. 
     | 
| 
      
 308 
     | 
    
         
            +
                  current_value.deep_merge(updates)
         
     | 
| 
       279 
309 
     | 
    
         
             
                else
         
     | 
| 
       280 
310 
     | 
    
         
             
                  raise MergeError.build(current_value, updates)
         
     | 
| 
       281 
311 
     | 
    
         
             
                end
         
     | 
| 
         @@ -285,6 +315,18 @@ class Jbuilder 
     | 
|
| 
       285 
315 
     | 
    
         
             
                @key_formatter ? @key_formatter.format(key) : key.to_s
         
     | 
| 
       286 
316 
     | 
    
         
             
              end
         
     | 
| 
       287 
317 
     | 
    
         | 
| 
      
 318 
     | 
    
         
            +
              def _format_keys(hash_or_array)
         
     | 
| 
      
 319 
     | 
    
         
            +
                return hash_or_array unless @deep_format_keys
         
     | 
| 
      
 320 
     | 
    
         
            +
             
     | 
| 
      
 321 
     | 
    
         
            +
                if ::Array === hash_or_array
         
     | 
| 
      
 322 
     | 
    
         
            +
                  hash_or_array.map { |value| _format_keys(value) }
         
     | 
| 
      
 323 
     | 
    
         
            +
                elsif ::Hash === hash_or_array
         
     | 
| 
      
 324 
     | 
    
         
            +
                  ::Hash[hash_or_array.collect { |k, v| [_key(k), _format_keys(v)] }]
         
     | 
| 
      
 325 
     | 
    
         
            +
                else
         
     | 
| 
      
 326 
     | 
    
         
            +
                  hash_or_array
         
     | 
| 
      
 327 
     | 
    
         
            +
                end
         
     | 
| 
      
 328 
     | 
    
         
            +
              end
         
     | 
| 
      
 329 
     | 
    
         
            +
             
     | 
| 
       288 
330 
     | 
    
         
             
              def _set_value(key, value)
         
     | 
| 
       289 
331 
     | 
    
         
             
                raise NullError.build(key) if @attributes.nil?
         
     | 
| 
       290 
332 
     | 
    
         
             
                raise ArrayError.build(key) if ::Array === @attributes
         
     | 
| 
         @@ -300,12 +342,12 @@ class Jbuilder 
     | 
|
| 
       300 
342 
     | 
    
         
             
              end
         
     | 
| 
       301 
343 
     | 
    
         | 
| 
       302 
344 
     | 
    
         
             
              def _scope
         
     | 
| 
       303 
     | 
    
         
            -
                parent_attributes, parent_formatter = @attributes, @key_formatter
         
     | 
| 
      
 345 
     | 
    
         
            +
                parent_attributes, parent_formatter, parent_deep_format_keys = @attributes, @key_formatter, @deep_format_keys
         
     | 
| 
       304 
346 
     | 
    
         
             
                @attributes = BLANK
         
     | 
| 
       305 
347 
     | 
    
         
             
                yield
         
     | 
| 
       306 
348 
     | 
    
         
             
                @attributes
         
     | 
| 
       307 
349 
     | 
    
         
             
              ensure
         
     | 
| 
       308 
     | 
    
         
            -
                @attributes, @key_formatter = parent_attributes, parent_formatter
         
     | 
| 
      
 350 
     | 
    
         
            +
                @attributes, @key_formatter, @deep_format_keys = parent_attributes, parent_formatter, parent_deep_format_keys
         
     | 
| 
       309 
351 
     | 
    
         
             
              end
         
     | 
| 
       310 
352 
     | 
    
         | 
| 
       311 
353 
     | 
    
         
             
              def _is_collection?(object)
         
     | 
| 
         @@ -61,7 +61,7 @@ class JbuilderDependencyTrackerTest < ActiveSupport::TestCase 
     | 
|
| 
       61 
61 
     | 
    
         
             
                  assert_equal %w[comments/comment], dependencies
         
     | 
| 
       62 
62 
     | 
    
         
             
                end
         
     | 
| 
       63 
63 
     | 
    
         | 
| 
       64 
     | 
    
         
            -
                test 'detects explicit  
     | 
| 
      
 64 
     | 
    
         
            +
                test 'detects explicit dependency' do
         
     | 
| 
       65 
65 
     | 
    
         
             
                  dependencies = track_dependencies <<-RUBY
         
     | 
| 
       66 
66 
     | 
    
         
             
                    # Template Dependency: path/to/partial
         
     | 
| 
       67 
67 
     | 
    
         
             
                    json.foo 'bar'
         
     | 
| 
         @@ -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
         
     | 
| 
         @@ -159,7 +159,7 @@ class JbuilderTemplateTest < ActiveSupport::TestCase 
     | 
|
| 
       159 
159 
     | 
    
         
             
              end
         
     | 
| 
       160 
160 
     | 
    
         | 
| 
       161 
161 
     | 
    
         
             
              test "object fragment caching with expiry" do
         
     | 
| 
       162 
     | 
    
         
            -
                travel_to "2018-05- 
     | 
| 
      
 162 
     | 
    
         
            +
                travel_to Time.iso8601("2018-05-12T11:29:00-04:00")
         
     | 
| 
       163 
163 
     | 
    
         | 
| 
       164 
164 
     | 
    
         
             
                render <<-JBUILDER
         
     | 
| 
       165 
165 
     | 
    
         
             
                  json.cache! "cache-key", expires_in: 1.minute do
         
     | 
| 
         @@ -283,6 +283,101 @@ class JbuilderTemplateTest < ActiveSupport::TestCase 
     | 
|
| 
       283 
283 
     | 
    
         
             
                assert_equal "David", result["firstName"]
         
     | 
| 
       284 
284 
     | 
    
         
             
              end
         
     | 
| 
       285 
285 
     | 
    
         | 
| 
      
 286 
     | 
    
         
            +
              if JbuilderTemplate::CollectionRenderer.supported?
         
     | 
| 
      
 287 
     | 
    
         
            +
                test "returns an empty array for an empty collection" do
         
     | 
| 
      
 288 
     | 
    
         
            +
                  result = render('json.array! @posts, partial: "post", as: :post, cached: true', posts: [])
         
     | 
| 
      
 289 
     | 
    
         
            +
             
     | 
| 
      
 290 
     | 
    
         
            +
                  # Do not use #assert_empty as it is important to ensure that the type of the JSON result is an array.
         
     | 
| 
      
 291 
     | 
    
         
            +
                  assert_equal [], result
         
     | 
| 
      
 292 
     | 
    
         
            +
                end
         
     | 
| 
      
 293 
     | 
    
         
            +
             
     | 
| 
      
 294 
     | 
    
         
            +
                test "works with an enumerable object" do
         
     | 
| 
      
 295 
     | 
    
         
            +
                  enumerable_class = Class.new do
         
     | 
| 
      
 296 
     | 
    
         
            +
                    include Enumerable
         
     | 
| 
      
 297 
     | 
    
         
            +
                    alias length count # Rails 6.1 requires this.
         
     | 
| 
      
 298 
     | 
    
         
            +
             
     | 
| 
      
 299 
     | 
    
         
            +
                    def each(&block)
         
     | 
| 
      
 300 
     | 
    
         
            +
                      [].each(&block)
         
     | 
| 
      
 301 
     | 
    
         
            +
                    end
         
     | 
| 
      
 302 
     | 
    
         
            +
                  end
         
     | 
| 
      
 303 
     | 
    
         
            +
             
     | 
| 
      
 304 
     | 
    
         
            +
                  result = render('json.array! @posts, partial: "post", as: :post, cached: true', posts: enumerable_class.new)
         
     | 
| 
      
 305 
     | 
    
         
            +
             
     | 
| 
      
 306 
     | 
    
         
            +
                  # Do not use #assert_empty as it is important to ensure that the type of the JSON result is an array.
         
     | 
| 
      
 307 
     | 
    
         
            +
                  assert_equal [], result
         
     | 
| 
      
 308 
     | 
    
         
            +
                end
         
     | 
| 
      
 309 
     | 
    
         
            +
             
     | 
| 
      
 310 
     | 
    
         
            +
                test "supports the cached: true option" do
         
     | 
| 
      
 311 
     | 
    
         
            +
                  result = render('json.array! @posts, partial: "post", as: :post, cached: true', posts: POSTS)
         
     | 
| 
      
 312 
     | 
    
         
            +
             
     | 
| 
      
 313 
     | 
    
         
            +
                  assert_equal 10, result.count
         
     | 
| 
      
 314 
     | 
    
         
            +
                  assert_equal "Post #5", result[4]["body"]
         
     | 
| 
      
 315 
     | 
    
         
            +
                  assert_equal "Heinemeier Hansson", result[2]["author"]["last_name"]
         
     | 
| 
      
 316 
     | 
    
         
            +
                  assert_equal "Pavel", result[5]["author"]["first_name"]
         
     | 
| 
      
 317 
     | 
    
         
            +
             
     | 
| 
      
 318 
     | 
    
         
            +
                  expected = {
         
     | 
| 
      
 319 
     | 
    
         
            +
                    "id" => 1,
         
     | 
| 
      
 320 
     | 
    
         
            +
                    "body" => "Post #1",
         
     | 
| 
      
 321 
     | 
    
         
            +
                    "author" => {
         
     | 
| 
      
 322 
     | 
    
         
            +
                      "first_name" => "David",
         
     | 
| 
      
 323 
     | 
    
         
            +
                      "last_name" => "Heinemeier Hansson"
         
     | 
| 
      
 324 
     | 
    
         
            +
                    }
         
     | 
| 
      
 325 
     | 
    
         
            +
                  }
         
     | 
| 
      
 326 
     | 
    
         
            +
             
     | 
| 
      
 327 
     | 
    
         
            +
                  assert_equal expected, Rails.cache.read("post-1")
         
     | 
| 
      
 328 
     | 
    
         
            +
             
     | 
| 
      
 329 
     | 
    
         
            +
                  result = render('json.array! @posts, partial: "post", as: :post, cached: true', posts: POSTS)
         
     | 
| 
      
 330 
     | 
    
         
            +
             
     | 
| 
      
 331 
     | 
    
         
            +
                  assert_equal 10, result.count
         
     | 
| 
      
 332 
     | 
    
         
            +
                  assert_equal "Post #5", result[4]["body"]
         
     | 
| 
      
 333 
     | 
    
         
            +
                  assert_equal "Heinemeier Hansson", result[2]["author"]["last_name"]
         
     | 
| 
      
 334 
     | 
    
         
            +
                  assert_equal "Pavel", result[5]["author"]["first_name"]
         
     | 
| 
      
 335 
     | 
    
         
            +
                end
         
     | 
| 
      
 336 
     | 
    
         
            +
             
     | 
| 
      
 337 
     | 
    
         
            +
                test "supports the cached: ->() {} option" do
         
     | 
| 
      
 338 
     | 
    
         
            +
                  result = render('json.array! @posts, partial: "post", as: :post, cached: ->(post) { [post, "foo"] }', posts: POSTS)
         
     | 
| 
      
 339 
     | 
    
         
            +
             
     | 
| 
      
 340 
     | 
    
         
            +
                  assert_equal 10, result.count
         
     | 
| 
      
 341 
     | 
    
         
            +
                  assert_equal "Post #5", result[4]["body"]
         
     | 
| 
      
 342 
     | 
    
         
            +
                  assert_equal "Heinemeier Hansson", result[2]["author"]["last_name"]
         
     | 
| 
      
 343 
     | 
    
         
            +
                  assert_equal "Pavel", result[5]["author"]["first_name"]
         
     | 
| 
      
 344 
     | 
    
         
            +
             
     | 
| 
      
 345 
     | 
    
         
            +
                  expected = {
         
     | 
| 
      
 346 
     | 
    
         
            +
                    "id" => 1,
         
     | 
| 
      
 347 
     | 
    
         
            +
                    "body" => "Post #1",
         
     | 
| 
      
 348 
     | 
    
         
            +
                    "author" => {
         
     | 
| 
      
 349 
     | 
    
         
            +
                      "first_name" => "David",
         
     | 
| 
      
 350 
     | 
    
         
            +
                      "last_name" => "Heinemeier Hansson"
         
     | 
| 
      
 351 
     | 
    
         
            +
                    }
         
     | 
| 
      
 352 
     | 
    
         
            +
                  }
         
     | 
| 
      
 353 
     | 
    
         
            +
             
     | 
| 
      
 354 
     | 
    
         
            +
                  assert_equal expected, Rails.cache.read("post-1/foo")
         
     | 
| 
      
 355 
     | 
    
         
            +
             
     | 
| 
      
 356 
     | 
    
         
            +
                  result = render('json.array! @posts, partial: "post", as: :post, cached: ->(post) { [post, "foo"] }', posts: POSTS)
         
     | 
| 
      
 357 
     | 
    
         
            +
             
     | 
| 
      
 358 
     | 
    
         
            +
                  assert_equal 10, result.count
         
     | 
| 
      
 359 
     | 
    
         
            +
                  assert_equal "Post #5", result[4]["body"]
         
     | 
| 
      
 360 
     | 
    
         
            +
                  assert_equal "Heinemeier Hansson", result[2]["author"]["last_name"]
         
     | 
| 
      
 361 
     | 
    
         
            +
                  assert_equal "Pavel", result[5]["author"]["first_name"]
         
     | 
| 
      
 362 
     | 
    
         
            +
                end
         
     | 
| 
      
 363 
     | 
    
         
            +
             
     | 
| 
      
 364 
     | 
    
         
            +
                test "raises an error on a render call with the :layout option" do
         
     | 
| 
      
 365 
     | 
    
         
            +
                  error = assert_raises NotImplementedError do
         
     | 
| 
      
 366 
     | 
    
         
            +
                    render('json.array! @posts, partial: "post", as: :post, layout: "layout"', posts: POSTS)
         
     | 
| 
      
 367 
     | 
    
         
            +
                  end
         
     | 
| 
      
 368 
     | 
    
         
            +
             
     | 
| 
      
 369 
     | 
    
         
            +
                  assert_equal "The `:layout' option is not supported in collection rendering.", error.message
         
     | 
| 
      
 370 
     | 
    
         
            +
                end
         
     | 
| 
      
 371 
     | 
    
         
            +
             
     | 
| 
      
 372 
     | 
    
         
            +
                test "raises an error on a render call with the :spacer_template option" do
         
     | 
| 
      
 373 
     | 
    
         
            +
                  error = assert_raises NotImplementedError do
         
     | 
| 
      
 374 
     | 
    
         
            +
                    render('json.array! @posts, partial: "post", as: :post, spacer_template: "template"', posts: POSTS)
         
     | 
| 
      
 375 
     | 
    
         
            +
                  end
         
     | 
| 
      
 376 
     | 
    
         
            +
             
     | 
| 
      
 377 
     | 
    
         
            +
                  assert_equal "The `:spacer_template' option is not supported in collection rendering.", error.message
         
     | 
| 
      
 378 
     | 
    
         
            +
                end
         
     | 
| 
      
 379 
     | 
    
         
            +
              end
         
     | 
| 
      
 380 
     | 
    
         
            +
             
     | 
| 
       286 
381 
     | 
    
         
             
              private
         
     | 
| 
       287 
382 
     | 
    
         
             
                def render(*args)
         
     | 
| 
       288 
383 
     | 
    
         
             
                  JSON.load render_without_parsing(*args)
         
     | 
| 
         @@ -290,7 +385,7 @@ class JbuilderTemplateTest < ActiveSupport::TestCase 
     | 
|
| 
       290 
385 
     | 
    
         | 
| 
       291 
386 
     | 
    
         
             
                def render_without_parsing(source, assigns = {})
         
     | 
| 
       292 
387 
     | 
    
         
             
                  view = build_view(fixtures: PARTIALS.merge("source.json.jbuilder" => source), assigns: assigns)
         
     | 
| 
       293 
     | 
    
         
            -
                  view.render(template: "source 
     | 
| 
      
 388 
     | 
    
         
            +
                  view.render(template: "source")
         
     | 
| 
       294 
389 
     | 
    
         
             
                end
         
     | 
| 
       295 
390 
     | 
    
         | 
| 
       296 
391 
     | 
    
         
             
                def build_view(options = {})
         
     | 
| 
         @@ -306,6 +401,9 @@ class JbuilderTemplateTest < ActiveSupport::TestCase 
     | 
|
| 
       306 
401 
     | 
    
         
             
                  end
         
     | 
| 
       307 
402 
     | 
    
         | 
| 
       308 
403 
     | 
    
         
             
                  def view.view_cache_dependencies; []; end
         
     | 
| 
      
 404 
     | 
    
         
            +
                  def view.combined_fragment_cache_key(key) [ key ] end
         
     | 
| 
      
 405 
     | 
    
         
            +
                  def view.cache_fragment_name(key, *) key end
         
     | 
| 
      
 406 
     | 
    
         
            +
                  def view.fragment_name_with_digest(key) key end
         
     | 
| 
       309 
407 
     | 
    
         | 
| 
       310 
408 
     | 
    
         
             
                  view
         
     | 
| 
       311 
409 
     | 
    
         
             
                end
         
     |