grape-entity 0.7.1 → 0.9.0
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/dependabot.yml +14 -0
- data/.github/workflows/rubocop.yml +26 -0
- data/.github/workflows/ruby.yml +26 -0
- data/.rubocop.yml +72 -23
- data/.rubocop_todo.yml +8 -38
- data/CHANGELOG.md +59 -1
- data/Gemfile +3 -3
- data/Guardfile +4 -2
- data/README.md +43 -7
- data/UPGRADING.md +19 -2
- data/bench/serializing.rb +5 -0
- data/grape-entity.gemspec +4 -6
- data/lib/grape_entity.rb +1 -0
- data/lib/grape_entity/condition/base.rb +1 -1
- data/lib/grape_entity/delegator/hash_object.rb +2 -2
- data/lib/grape_entity/deprecated.rb +13 -0
- data/lib/grape_entity/entity.rb +66 -9
- data/lib/grape_entity/exposure.rb +9 -3
- data/lib/grape_entity/exposure/base.rb +6 -5
- data/lib/grape_entity/exposure/nesting_exposure.rb +2 -0
- data/lib/grape_entity/exposure/nesting_exposure/nested_exposures.rb +3 -1
- data/lib/grape_entity/exposure/nesting_exposure/output_builder.rb +3 -0
- data/lib/grape_entity/options.rb +3 -2
- data/lib/grape_entity/version.rb +1 -1
- data/spec/grape_entity/entity_spec.rb +74 -15
- data/spec/grape_entity/exposure/represent_exposure_spec.rb +3 -3
- data/spec/grape_entity/hash_spec.rb +36 -1
- data/spec/spec_helper.rb +7 -1
- metadata +15 -13
- data/.travis.yml +0 -30
    
        data/Guardfile
    CHANGED
    
    | @@ -1,11 +1,13 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            # A sample Guardfile
         | 
| 2 4 | 
             
            # More info at https://github.com/guard/guard#readme
         | 
| 3 5 |  | 
| 4 6 | 
             
            guard 'rspec', version: 2 do
         | 
| 5 7 | 
             
              watch(%r{^spec/.+_spec\.rb$})
         | 
| 6 | 
            -
              watch(%r{^lib/(.+)\.rb$}) | 
| 8 | 
            +
              watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
         | 
| 7 9 | 
             
              watch(%r{^spec/support/shared_versioning_examples.rb$}) { |_m| 'spec/' }
         | 
| 8 | 
            -
              watch('spec/spec_helper.rb') | 
| 10 | 
            +
              watch('spec/spec_helper.rb') { 'spec/' }
         | 
| 9 11 | 
             
            end
         | 
| 10 12 |  | 
| 11 13 | 
             
            guard 'bundler' do
         | 
    
        data/README.md
    CHANGED
    
    | @@ -1,11 +1,46 @@ | |
| 1 | 
            -
            # Grape::Entity
         | 
| 2 | 
            -
             | 
| 3 1 | 
             
            [](http://badge.fury.io/rb/grape-entity)
         | 
| 4 | 
            -
             | 
| 2 | 
            +
            
         | 
| 5 3 | 
             
            [](https://coveralls.io/github/ruby-grape/grape-entity?branch=master)
         | 
| 6 | 
            -
            [](https://gemnasium.com/ruby-grape/grape-entity)
         | 
| 7 4 | 
             
            [](https://codeclimate.com/github/ruby-grape/grape-entity)
         | 
| 8 5 |  | 
| 6 | 
            +
            # Table of Contents
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            - [Grape::Entity](#grapeentity)
         | 
| 9 | 
            +
              - [Introduction](#introduction)
         | 
| 10 | 
            +
                - [Example](#example)
         | 
| 11 | 
            +
              - [Reusable Responses with Entities](#reusable-responses-with-entities)
         | 
| 12 | 
            +
                - [Defining Entities](#defining-entities)
         | 
| 13 | 
            +
                  - [Basic Exposure](#basic-exposure)
         | 
| 14 | 
            +
                  - [Exposing with a Presenter](#exposing-with-a-presenter)
         | 
| 15 | 
            +
                  - [Conditional Exposure](#conditional-exposure)
         | 
| 16 | 
            +
                  - [Safe Exposure](#safe-exposure)
         | 
| 17 | 
            +
                  - [Nested Exposure](#nested-exposure)
         | 
| 18 | 
            +
                  - [Collection Exposure](#collection-exposure)
         | 
| 19 | 
            +
                  - [Merge Fields](#merge-fields)
         | 
| 20 | 
            +
                  - [Runtime Exposure](#runtime-exposure)
         | 
| 21 | 
            +
                  - [Unexpose](#unexpose)
         | 
| 22 | 
            +
                  - [Overriding exposures](#overriding-exposures)
         | 
| 23 | 
            +
                  - [Returning only the fields you want](#returning-only-the-fields-you-want)
         | 
| 24 | 
            +
                  - [Aliases](#aliases)
         | 
| 25 | 
            +
                  - [Format Before Exposing](#format-before-exposing)
         | 
| 26 | 
            +
                  - [Expose Nil](#expose-nil)
         | 
| 27 | 
            +
                  - [Documentation](#documentation)
         | 
| 28 | 
            +
                - [Options Hash](#options-hash)
         | 
| 29 | 
            +
                  - [Passing Additional Option To Nested Exposure](#passing-additional-option-to-nested-exposure)
         | 
| 30 | 
            +
                  - [Attribute Path Tracking](#attribute-path-tracking)
         | 
| 31 | 
            +
                - [Using the Exposure DSL](#using-the-exposure-dsl)
         | 
| 32 | 
            +
                - [Using Entities](#using-entities)
         | 
| 33 | 
            +
                - [Entity Organization](#entity-organization)
         | 
| 34 | 
            +
                - [Caveats](#caveats)
         | 
| 35 | 
            +
              - [Installation](#installation)
         | 
| 36 | 
            +
              - [Testing with Entities](#testing-with-entities)
         | 
| 37 | 
            +
              - [Project Resources](#project-resources)
         | 
| 38 | 
            +
              - [Contributing](#contributing)
         | 
| 39 | 
            +
              - [License](#license)
         | 
| 40 | 
            +
              - [Copyright](#copyright)
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            # Grape::Entity
         | 
| 43 | 
            +
             | 
| 9 44 | 
             
            ## Introduction
         | 
| 10 45 |  | 
| 11 46 | 
             
            This gem adds Entity support to API frameworks, such as [Grape](https://github.com/ruby-grape/grape). Grape's Entity is an API focused facade that sits on top of an object model.
         | 
| @@ -221,7 +256,8 @@ class ExampleEntity < Grape::Entity | |
| 221 256 | 
             
            end
         | 
| 222 257 | 
             
            ```
         | 
| 223 258 |  | 
| 224 | 
            -
            You have  | 
| 259 | 
            +
            You always have access to the presented instance (`object`) and the top-level
         | 
| 260 | 
            +
            entity options (`options`).
         | 
| 225 261 |  | 
| 226 262 | 
             
            ```ruby
         | 
| 227 263 | 
             
            class ExampleEntity < Grape::Entity
         | 
| @@ -230,7 +266,7 @@ class ExampleEntity < Grape::Entity | |
| 230 266 | 
             
              private
         | 
| 231 267 |  | 
| 232 268 | 
             
              def formatted_value
         | 
| 233 | 
            -
                "+ X #{object.value}"
         | 
| 269 | 
            +
                "+ X #{object.value} #{options[:y]}"
         | 
| 234 270 | 
             
              end
         | 
| 235 271 | 
             
            end
         | 
| 236 272 | 
             
            ```
         | 
| @@ -265,7 +301,7 @@ class User < Grape::Entity | |
| 265 301 | 
             
              expose :name
         | 
| 266 302 | 
             
            end
         | 
| 267 303 |  | 
| 268 | 
            -
            class Employee <  | 
| 304 | 
            +
            class Employee < User
         | 
| 269 305 | 
             
              expose :name, as: :employee_name, override: true
         | 
| 270 306 | 
             
            end
         | 
| 271 307 | 
             
            ```
         | 
    
        data/UPGRADING.md
    CHANGED
    
    | @@ -1,5 +1,22 @@ | |
| 1 | 
            -
            Upgrading Grape Entity
         | 
| 2 | 
            -
             | 
| 1 | 
            +
            # Upgrading Grape Entity
         | 
| 2 | 
            +
             | 
| 3 | 
            +
             | 
| 4 | 
            +
            ### Upgrading to >= 0.8.2
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            Official support for ruby < 2.5 removed, ruby 2.5 only in testing mode, but no support.
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            In Ruby 3.0: the block handling will be changed
         | 
| 9 | 
            +
            [language-changes point 3, Proc](https://github.com/ruby/ruby/blob/v3_0_0_preview1/NEWS.md#language-changes).
         | 
| 10 | 
            +
            This:
         | 
| 11 | 
            +
            ```ruby
         | 
| 12 | 
            +
            expose :that_method_without_args, &:method_without_args
         | 
| 13 | 
            +
            ```
         | 
| 14 | 
            +
            will be deprecated.
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            Prefer to use this pattern for simple setting a value
         | 
| 17 | 
            +
            ```ruby
         | 
| 18 | 
            +
            expose :method_without_args, as: :that_method_without_args
         | 
| 19 | 
            +
            ```
         | 
| 3 20 |  | 
| 4 21 | 
             
            ### Upgrading to >= 0.6.0
         | 
| 5 22 |  | 
    
        data/bench/serializing.rb
    CHANGED
    
    | @@ -7,6 +7,7 @@ require 'benchmark' | |
| 7 7 | 
             
            module Models
         | 
| 8 8 | 
             
              class School
         | 
| 9 9 | 
             
                attr_reader :classrooms
         | 
| 10 | 
            +
             | 
| 10 11 | 
             
                def initialize
         | 
| 11 12 | 
             
                  @classrooms = []
         | 
| 12 13 | 
             
                end
         | 
| @@ -15,6 +16,7 @@ module Models | |
| 15 16 | 
             
              class ClassRoom
         | 
| 16 17 | 
             
                attr_reader :students
         | 
| 17 18 | 
             
                attr_accessor :teacher
         | 
| 19 | 
            +
             | 
| 18 20 | 
             
                def initialize(opts = {})
         | 
| 19 21 | 
             
                  @teacher = opts[:teacher]
         | 
| 20 22 | 
             
                  @students = []
         | 
| @@ -23,6 +25,7 @@ module Models | |
| 23 25 |  | 
| 24 26 | 
             
              class Person
         | 
| 25 27 | 
             
                attr_accessor :name
         | 
| 28 | 
            +
             | 
| 26 29 | 
             
                def initialize(opts = {})
         | 
| 27 30 | 
             
                  @name = opts[:name]
         | 
| 28 31 | 
             
                end
         | 
| @@ -30,6 +33,7 @@ module Models | |
| 30 33 |  | 
| 31 34 | 
             
              class Teacher < Models::Person
         | 
| 32 35 | 
             
                attr_accessor :tenure
         | 
| 36 | 
            +
             | 
| 33 37 | 
             
                def initialize(opts = {})
         | 
| 34 38 | 
             
                  super(opts)
         | 
| 35 39 | 
             
                  @tenure = opts[:tenure]
         | 
| @@ -38,6 +42,7 @@ module Models | |
| 38 42 |  | 
| 39 43 | 
             
              class Student < Models::Person
         | 
| 40 44 | 
             
                attr_reader :grade
         | 
| 45 | 
            +
             | 
| 41 46 | 
             
                def initialize(opts = {})
         | 
| 42 47 | 
             
                  super(opts)
         | 
| 43 48 | 
             
                  @grade = opts[:grade]
         | 
    
        data/grape-entity.gemspec
    CHANGED
    
    | @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            $LOAD_PATH.push File.expand_path(' | 
| 3 | 
            +
            $LOAD_PATH.push File.expand_path('lib', __dir__)
         | 
| 4 4 | 
             
            require 'grape_entity/version'
         | 
| 5 5 |  | 
| 6 6 | 
             
            Gem::Specification.new do |s|
         | 
| @@ -14,11 +14,9 @@ Gem::Specification.new do |s| | |
| 14 14 | 
             
              s.description = 'Extracted from Grape, A Ruby framework for rapid API development with great conventions.'
         | 
| 15 15 | 
             
              s.license     = 'MIT'
         | 
| 16 16 |  | 
| 17 | 
            -
              s.required_ruby_version = '>= 2. | 
| 17 | 
            +
              s.required_ruby_version = '>= 2.5'
         | 
| 18 18 |  | 
| 19 | 
            -
              s. | 
| 20 | 
            -
             | 
| 21 | 
            -
              s.add_runtime_dependency 'activesupport', '>=4.0'
         | 
| 19 | 
            +
              s.add_runtime_dependency 'activesupport', '>= 3.0.0'
         | 
| 22 20 | 
             
              # FIXME: remove dependecy
         | 
| 23 21 | 
             
              s.add_runtime_dependency 'multi_json', '>= 1.3.2'
         | 
| 24 22 |  | 
| @@ -28,7 +26,7 @@ Gem::Specification.new do |s| | |
| 28 26 | 
             
              s.add_development_dependency 'pry-byebug' unless RUBY_PLATFORM.eql?('java') || RUBY_ENGINE.eql?('rbx')
         | 
| 29 27 | 
             
              s.add_development_dependency 'rack-test'
         | 
| 30 28 | 
             
              s.add_development_dependency 'rake'
         | 
| 31 | 
            -
              s.add_development_dependency 'rspec', '~> 3. | 
| 29 | 
            +
              s.add_development_dependency 'rspec', '~> 3.9'
         | 
| 32 30 | 
             
              s.add_development_dependency 'yard'
         | 
| 33 31 |  | 
| 34 32 | 
             
              s.files         = `git ls-files`.split("\n")
         | 
    
        data/lib/grape_entity.rb
    CHANGED
    
    
    
        data/lib/grape_entity/entity.rb
    CHANGED
    
    | @@ -105,7 +105,7 @@ module Grape | |
| 105 105 | 
             
                    @root_exposure ||= Exposure.new(nil, nesting: true)
         | 
| 106 106 | 
             
                  end
         | 
| 107 107 |  | 
| 108 | 
            -
                  attr_writer :root_exposure
         | 
| 108 | 
            +
                  attr_writer :root_exposure, :formatters
         | 
| 109 109 |  | 
| 110 110 | 
             
                  # Returns all formatters that are registered for this and it's ancestors
         | 
| 111 111 | 
             
                  # @return [Hash] of formatters
         | 
| @@ -113,7 +113,23 @@ module Grape | |
| 113 113 | 
             
                    @formatters ||= {}
         | 
| 114 114 | 
             
                  end
         | 
| 115 115 |  | 
| 116 | 
            -
                   | 
| 116 | 
            +
                  def hash_access
         | 
| 117 | 
            +
                    @hash_access ||= :to_sym
         | 
| 118 | 
            +
                  end
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                  def hash_access=(value)
         | 
| 121 | 
            +
                    @hash_access =
         | 
| 122 | 
            +
                      case value
         | 
| 123 | 
            +
                      when :to_s, :str, :string
         | 
| 124 | 
            +
                        :to_s
         | 
| 125 | 
            +
                      else
         | 
| 126 | 
            +
                        :to_sym
         | 
| 127 | 
            +
                      end
         | 
| 128 | 
            +
                  end
         | 
| 129 | 
            +
             | 
| 130 | 
            +
                  def delegation_opts
         | 
| 131 | 
            +
                    @delegation_opts ||= { hash_access: hash_access }
         | 
| 132 | 
            +
                  end
         | 
| 117 133 | 
             
                end
         | 
| 118 134 |  | 
| 119 135 | 
             
                @formatters = {}
         | 
| @@ -121,6 +137,8 @@ module Grape | |
| 121 137 | 
             
                def self.inherited(subclass)
         | 
| 122 138 | 
             
                  subclass.root_exposure = root_exposure.dup
         | 
| 123 139 | 
             
                  subclass.formatters = formatters.dup
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                  super
         | 
| 124 142 | 
             
                end
         | 
| 125 143 |  | 
| 126 144 | 
             
                # This method is the primary means by which you will declare what attributes
         | 
| @@ -168,18 +186,23 @@ module Grape | |
| 168 186 | 
             
                # @option options :documentation Define documenation for an exposed
         | 
| 169 187 | 
             
                #   field, typically the value is a hash with two fields, type and desc.
         | 
| 170 188 | 
             
                # @option options :merge This option allows you to merge an exposed field to the root
         | 
| 189 | 
            +
                #
         | 
| 190 | 
            +
                # rubocop:disable Layout/LineLength
         | 
| 171 191 | 
             
                def self.expose(*args, &block)
         | 
| 172 192 | 
             
                  options = merge_options(args.last.is_a?(Hash) ? args.pop : {})
         | 
| 173 193 |  | 
| 174 194 | 
             
                  if args.size > 1
         | 
| 195 | 
            +
             | 
| 175 196 | 
             
                    raise ArgumentError, 'You may not use the :as option on multi-attribute exposures.' if options[:as]
         | 
| 176 197 | 
             
                    raise ArgumentError, 'You may not use the :expose_nil on multi-attribute exposures.' if options.key?(:expose_nil)
         | 
| 177 198 | 
             
                    raise ArgumentError, 'You may not use block-setting on multi-attribute exposures.' if block_given?
         | 
| 178 199 | 
             
                  end
         | 
| 179 200 |  | 
| 180 | 
            -
                  raise ArgumentError, 'You may not use block-setting when also using format_with' if block_given? && options[:format_with].respond_to?(:call)
         | 
| 181 | 
            -
             | 
| 182 201 | 
             
                  if block_given?
         | 
| 202 | 
            +
                    if options[:format_with].respond_to?(:call)
         | 
| 203 | 
            +
                      raise ArgumentError, 'You may not use block-setting when also using format_with'
         | 
| 204 | 
            +
                    end
         | 
| 205 | 
            +
             | 
| 183 206 | 
             
                    if block.parameters.any?
         | 
| 184 207 | 
             
                      options[:proc] = block
         | 
| 185 208 | 
             
                    else
         | 
| @@ -191,6 +214,7 @@ module Grape | |
| 191 214 | 
             
                  @nesting_stack ||= []
         | 
| 192 215 | 
             
                  args.each { |attribute| build_exposure_for_attribute(attribute, @nesting_stack, options, block) }
         | 
| 193 216 | 
             
                end
         | 
| 217 | 
            +
                # rubocop:enable Layout/LineLength
         | 
| 194 218 |  | 
| 195 219 | 
             
                def self.build_exposure_for_attribute(attribute, nesting_stack, options, block)
         | 
| 196 220 | 
             
                  exposure_list = nesting_stack.empty? ? root_exposures : nesting_stack.last.nested_exposures
         | 
| @@ -291,6 +315,7 @@ module Grape | |
| 291 315 | 
             
                #
         | 
| 292 316 | 
             
                def self.format_with(name, &block)
         | 
| 293 317 | 
             
                  raise ArgumentError, 'You must pass a block for formatters' unless block_given?
         | 
| 318 | 
            +
             | 
| 294 319 | 
             
                  formatters[name.to_sym] = block
         | 
| 295 320 | 
             
                end
         | 
| 296 321 |  | 
| @@ -454,8 +479,11 @@ module Grape | |
| 454 479 |  | 
| 455 480 | 
             
                def initialize(object, options = {})
         | 
| 456 481 | 
             
                  @object = object
         | 
| 457 | 
            -
                  @delegator = Delegator.new(object)
         | 
| 458 482 | 
             
                  @options = options.is_a?(Options) ? options : Options.new(options)
         | 
| 483 | 
            +
                  @delegator = Delegator.new(object)
         | 
| 484 | 
            +
             | 
| 485 | 
            +
                  # Why not `arity > 1`? It might be negative https://ruby-doc.org/core-2.6.6/Method.html#method-i-arity
         | 
| 486 | 
            +
                  @delegator_accepts_opts = @delegator.method(:delegate).arity != 1
         | 
| 459 487 | 
             
                end
         | 
| 460 488 |  | 
| 461 489 | 
             
                def root_exposures
         | 
| @@ -495,6 +523,11 @@ module Grape | |
| 495 523 | 
             
                  else
         | 
| 496 524 | 
             
                    instance_exec(object, options, &block)
         | 
| 497 525 | 
             
                  end
         | 
| 526 | 
            +
                rescue StandardError => e
         | 
| 527 | 
            +
                  # it handles: https://github.com/ruby/ruby/blob/v3_0_0_preview1/NEWS.md#language-changes point 3, Proc
         | 
| 528 | 
            +
                  raise Grape::Entity::Deprecated.new e.message, 'in ruby 3.0' if e.is_a?(ArgumentError)
         | 
| 529 | 
            +
             | 
| 530 | 
            +
                  raise e.class, e.message
         | 
| 498 531 | 
             
                end
         | 
| 499 532 |  | 
| 500 533 | 
             
                def exec_with_attribute(attribute, &block)
         | 
| @@ -506,28 +539,52 @@ module Grape | |
| 506 539 | 
             
                end
         | 
| 507 540 |  | 
| 508 541 | 
             
                def delegate_attribute(attribute)
         | 
| 509 | 
            -
                  if  | 
| 542 | 
            +
                  if is_defined_in_entity?(attribute)
         | 
| 510 543 | 
             
                    send(attribute)
         | 
| 544 | 
            +
                  elsif @delegator_accepts_opts
         | 
| 545 | 
            +
                    delegator.delegate(attribute, **self.class.delegation_opts)
         | 
| 511 546 | 
             
                  else
         | 
| 512 547 | 
             
                    delegator.delegate(attribute)
         | 
| 513 548 | 
             
                  end
         | 
| 514 549 | 
             
                end
         | 
| 515 550 |  | 
| 551 | 
            +
                def is_defined_in_entity?(attribute)
         | 
| 552 | 
            +
                  return false unless respond_to?(attribute, true)
         | 
| 553 | 
            +
             | 
| 554 | 
            +
                  ancestors = self.class.ancestors
         | 
| 555 | 
            +
                  ancestors.index(Grape::Entity) > ancestors.index(method(attribute).owner)
         | 
| 556 | 
            +
                end
         | 
| 557 | 
            +
             | 
| 516 558 | 
             
                alias as_json serializable_hash
         | 
| 517 559 |  | 
| 518 560 | 
             
                def to_json(options = {})
         | 
| 519 | 
            -
                  options = options.to_h if options | 
| 561 | 
            +
                  options = options.to_h if options&.respond_to?(:to_h)
         | 
| 520 562 | 
             
                  MultiJson.dump(serializable_hash(options))
         | 
| 521 563 | 
             
                end
         | 
| 522 564 |  | 
| 523 565 | 
             
                def to_xml(options = {})
         | 
| 524 | 
            -
                  options = options.to_h if options | 
| 566 | 
            +
                  options = options.to_h if options&.respond_to?(:to_h)
         | 
| 525 567 | 
             
                  serializable_hash(options).to_xml(options)
         | 
| 526 568 | 
             
                end
         | 
| 527 569 |  | 
| 528 570 | 
             
                # All supported options.
         | 
| 529 571 | 
             
                OPTIONS = %i[
         | 
| 530 | 
            -
                  rewrite | 
| 572 | 
            +
                  rewrite
         | 
| 573 | 
            +
                  as
         | 
| 574 | 
            +
                  if
         | 
| 575 | 
            +
                  unless
         | 
| 576 | 
            +
                  using
         | 
| 577 | 
            +
                  with
         | 
| 578 | 
            +
                  proc
         | 
| 579 | 
            +
                  documentation
         | 
| 580 | 
            +
                  format_with
         | 
| 581 | 
            +
                  safe
         | 
| 582 | 
            +
                  attr_path
         | 
| 583 | 
            +
                  if_extras
         | 
| 584 | 
            +
                  unless_extras
         | 
| 585 | 
            +
                  merge
         | 
| 586 | 
            +
                  expose_nil
         | 
| 587 | 
            +
                  override
         | 
| 531 588 | 
             
                ].to_set.freeze
         | 
| 532 589 |  | 
| 533 590 | 
             
                # Merges the given options with current block options.
         | 
| @@ -47,14 +47,20 @@ module Grape | |
| 47 47 | 
             
                        options[:unless]
         | 
| 48 48 | 
             
                      ].compact.flatten.map { |cond| Condition.new_unless(cond) }
         | 
| 49 49 |  | 
| 50 | 
            -
                      unless_conditions << expose_nil_condition(attribute) if options[:expose_nil] == false
         | 
| 50 | 
            +
                      unless_conditions << expose_nil_condition(attribute, options) if options[:expose_nil] == false
         | 
| 51 51 |  | 
| 52 52 | 
             
                      if_conditions + unless_conditions
         | 
| 53 53 | 
             
                    end
         | 
| 54 54 |  | 
| 55 | 
            -
                    def expose_nil_condition(attribute)
         | 
| 55 | 
            +
                    def expose_nil_condition(attribute, options)
         | 
| 56 56 | 
             
                      Condition.new_unless(
         | 
| 57 | 
            -
                        proc  | 
| 57 | 
            +
                        proc do |object, _options|
         | 
| 58 | 
            +
                          if options[:proc].nil?
         | 
| 59 | 
            +
                            Delegator.new(object).delegate(attribute).nil?
         | 
| 60 | 
            +
                          else
         | 
| 61 | 
            +
                            exec_with_object(options, &options[:proc]).nil?
         | 
| 62 | 
            +
                          end
         | 
| 63 | 
            +
                        end
         | 
| 58 64 | 
             
                      )
         | 
| 59 65 | 
             
                    end
         | 
| 60 66 |  | 
| @@ -54,7 +54,10 @@ module Grape | |
| 54 54 | 
             
                      if @is_safe
         | 
| 55 55 | 
             
                        is_delegatable
         | 
| 56 56 | 
             
                      else
         | 
| 57 | 
            -
                        is_delegatable || raise( | 
| 57 | 
            +
                        is_delegatable || raise(
         | 
| 58 | 
            +
                          NoMethodError,
         | 
| 59 | 
            +
                          "#{entity.class.name} missing attribute `#{@attribute}' on #{entity.object}"
         | 
| 60 | 
            +
                        )
         | 
| 58 61 | 
             
                      end
         | 
| 59 62 | 
             
                    end
         | 
| 60 63 |  | 
| @@ -110,11 +113,9 @@ module Grape | |
| 110 113 | 
             
                      @key.respond_to?(:call) ? entity.exec_with_object(@options, &@key) : @key
         | 
| 111 114 | 
             
                    end
         | 
| 112 115 |  | 
| 113 | 
            -
                    def with_attr_path(entity, options)
         | 
| 116 | 
            +
                    def with_attr_path(entity, options, &block)
         | 
| 114 117 | 
             
                      path_part = attr_path(entity, options)
         | 
| 115 | 
            -
                      options.with_attr_path(path_part) | 
| 116 | 
            -
                        yield
         | 
| 117 | 
            -
                      end
         | 
| 118 | 
            +
                      options.with_attr_path(path_part, &block)
         | 
| 118 119 | 
             
                    end
         | 
| 119 120 |  | 
| 120 121 | 
             
                    def override?
         |