Class: Grape::Entity

Inherits:
Object
  • Object
show all
Defined in:
lib/grape_entity/entity.rb,
lib/grape_entity/options.rb,
lib/grape_entity/exposure.rb,
lib/grape_entity/condition.rb,
lib/grape_entity/delegator.rb,
lib/grape_entity/exposure/base.rb,
lib/grape_entity/condition/base.rb,
lib/grape_entity/delegator/base.rb,
lib/grape_entity/delegator/hash_object.rb,
lib/grape_entity/delegator/plain_object.rb,
lib/grape_entity/exposure/block_exposure.rb,
lib/grape_entity/condition/hash_condition.rb,
lib/grape_entity/condition/block_condition.rb,
lib/grape_entity/exposure/nesting_exposure.rb,
lib/grape_entity/condition/symbol_condition.rb,
lib/grape_entity/delegator/fetchable_object.rb,
lib/grape_entity/delegator/openstruct_object.rb,
lib/grape_entity/exposure/delegator_exposure.rb,
lib/grape_entity/exposure/formatter_exposure.rb,
lib/grape_entity/exposure/represent_exposure.rb,
lib/grape_entity/exposure/formatter_block_exposure.rb,
lib/grape_entity/exposure/nesting_exposure/output_builder.rb,
lib/grape_entity/exposure/nesting_exposure/nested_exposures.rb

Overview

An Entity is a lightweight structure that allows you to easily represent data from your application in a consistent and abstracted way in your API. Entities can also provide documentation for the fields exposed.

Entities are not independent structures, rather, they create representations of other Ruby objects using a number of methods that are convenient for use in an API. Once you've defined an Entity, you can use it in your API like this:

Examples:

Entity Definition


module API
  module Entities
    class User < Grape::Entity
      expose :first_name, :last_name, :screen_name, :location
      expose :field, documentation: { type: "string", desc: "describe the field" }
      expose :latest_status, using: API::Status, as: :status, unless: { collection: true }
      expose :email, if: { type: :full }
      expose :new_attribute, if: { version: 'v2' }
      expose(:name) { |model, options| [model.first_name, model.last_name].join(' ') }
    end
  end
end

Usage in the API Layer


module API
  class Users < Grape::API
    version 'v2'

    desc 'User index', { params: API::Entities::User.documentation }
    get '/users' do
      @users = User.all
      type = current_user.admin? ? :full : :default
      present @users, with: API::Entities::User, type: type
    end
  end
end

Defined Under Namespace

Modules: Condition, DSL, Delegator, Exposure Classes: Options

Constant Summary collapse

OPTIONS =

All supported options.

%i[
  rewrite as if unless using with proc documentation format_with safe attr_path if_extras unless_extras merge
].to_set.freeze

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(object, options = {}) ⇒ Entity

Returns a new instance of Entity


453
454
455
456
457
458
459
460
461
# File 'lib/grape_entity/entity.rb', line 453

def initialize(object, options = {})
  @object = object
  @delegator = Delegator.new object
  @options = if options.is_a? Options
               options
             else
               Options.new options
             end
end

Class Attribute Details

.formattersHash

Returns all formatters that are registered for this and it's ancestors

Returns:

  • (Hash)

    of formatters


112
113
114
# File 'lib/grape_entity/entity.rb', line 112

def formatters
  @formatters ||= {}
end

.root_exposureObject


104
105
106
# File 'lib/grape_entity/entity.rb', line 104

def root_exposure
  @root_exposure ||= Exposure.new(nil, nesting: true)
end

Instance Attribute Details

#delegatorObject (readonly)

Returns the value of attribute delegator


47
48
49
# File 'lib/grape_entity/entity.rb', line 47

def delegator
  @delegator
end

#objectObject (readonly)

Returns the value of attribute object


47
48
49
# File 'lib/grape_entity/entity.rb', line 47

def object
  @object
end

#optionsObject (readonly)

Returns the value of attribute options


47
48
49
# File 'lib/grape_entity/entity.rb', line 47

def options
  @options
end

Class Method Details

.can_unexpose?Boolean

Returns:

  • (Boolean)

230
231
232
# File 'lib/grape_entity/entity.rb', line 230

def self.can_unexpose?
  (@nesting_stack ||= []).empty?
end

.cannot_unexpose!Object


234
235
236
# File 'lib/grape_entity/entity.rb', line 234

def self.cannot_unexpose!
  raise "You cannot call 'unexpose` inside of nesting exposure!"
end

.documentationObject

Returns a hash, the keys are symbolized references to fields in the entity, the values are document keys in the entity's documentation key. When calling

docmentation, any exposure without a documentation key will be ignored.


256
257
258
259
260
261
262
# File 'lib/grape_entity/entity.rb', line 256

def self.documentation
  @documentation ||= root_exposures.each_with_object({}) do |exposure, memo|
    if exposure.documentation && !exposure.documentation.empty?
      memo[exposure.key] = exposure.documentation
    end
  end
end

.expose(*args, &block) ⇒ Object

This method is the primary means by which you will declare what attributes should be exposed by the entity.

Note the parameters passed in via the lambda syntax.

Examples:

as: a proc or lambda


object = OpenStruct(awesomness: 'awesome_key', awesome: 'not-my-key', other: 'other-key' )

class MyEntity < Grape::Entity
  expose :awesome, as: proc { object.awesomeness }
  expose :awesomeness, as: ->(object, opts) { object.other }
end

=> { 'awesome_key': 'not-my-key', 'other-key': 'awesome_key' }

Parameters:

  • options (Hash)

    a customizable set of options

Raises:

  • (ArgumentError)

168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/grape_entity/entity.rb', line 168

def self.expose(*args, &block)
  options = merge_options(args.last.is_a?(Hash) ? args.pop : {})

  if args.size > 1
    raise ArgumentError, 'You may not use the :as option on multi-attribute exposures.' if options[:as]
    raise ArgumentError, 'You may not use block-setting on multi-attribute exposures.' if block_given?
  end

  raise ArgumentError, 'You may not use block-setting when also using format_with' if block_given? && options[:format_with].respond_to?(:call)

  if block_given?
    if block.parameters.any?
      options[:proc] = block
    else
      options[:nesting] = true
    end
  end

  @documentation = nil
  @nesting_stack ||= []

  # rubocop:disable Style/Next
  args.each do |attribute|
    exposure = Exposure.new(attribute, options)

    if @nesting_stack.empty?
      root_exposures << exposure
    else
      @nesting_stack.last.nested_exposures << exposure
    end

    # Nested exposures are given in a block with no parameters.
    if exposure.nesting?
      @nesting_stack << exposure
      block.call
      @nesting_stack.pop
    end
  end
end

.find_exposure(attribute) ⇒ Object


214
215
216
# File 'lib/grape_entity/entity.rb', line 214

def self.find_exposure(attribute)
  root_exposures.find_by(attribute)
end

.format_with(name, &block) ⇒ Object

This allows you to declare a Proc in which exposures can be formatted with. It take a block with an arity of 1 which is passed as the value of the exposed attribute.

Examples:

Formatter declaration


module API
  module Entities
    class User < Grape::Entity
      format_with :timestamp do |date|
        date.strftime('%m/%d/%Y')
      end

      expose :birthday, :last_signed_in, format_with: :timestamp
    end
  end
end

Formatters are available to all decendants


Grape::Entity.format_with :timestamp do |date|
  date.strftime('%m/%d/%Y')
end

Parameters:

  • name (Symbol)

    the name of the formatter

  • block (Proc)

    the block that will interpret the exposed attribute

Raises:

  • (ArgumentError)

290
291
292
293
# File 'lib/grape_entity/entity.rb', line 290

def self.format_with(name, &block)
  raise ArgumentError, 'You must pass a block for formatters' unless block_given?
  formatters[name.to_sym] = block
end

.inherited(subclass) ⇒ Object


121
122
123
124
# File 'lib/grape_entity/entity.rb', line 121

def self.inherited(subclass)
  subclass.root_exposure = root_exposure.dup
  subclass.formatters = formatters.dup
end

.merge_options(options) ⇒ Object

Merges the given options with current block options.

Parameters:

  • options (Hash)

    Exposure options.


534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
# File 'lib/grape_entity/entity.rb', line 534

def self.merge_options(options)
  opts = {}

  merge_logic = proc do |key, existing_val, new_val|
    if %i[if unless].include?(key)
      if existing_val.is_a?(Hash) && new_val.is_a?(Hash)
        existing_val.merge(new_val)
      elsif new_val.is_a?(Hash)
        (opts["#{key}_extras".to_sym] ||= []) << existing_val
        new_val
      else
        (opts["#{key}_extras".to_sym] ||= []) << new_val
        existing_val
      end
    else
      new_val
    end
  end

  @block_options ||= []
  opts.merge @block_options.inject({}) { |final, step|
    final.merge(step, &merge_logic)
  }.merge(valid_options(options), &merge_logic)
end

.present_collection(present_collection = false, collection_name = :items) ⇒ Object

This allows you to present a collection of objects.

When false (default) every object in a collection to present will be wrapped separately into an instance of your presenter.

Examples:

Entity Definition


module API
  module Entities
    class User < Grape::Entity
      expose :id
    end

    class Users < Grape::Entity
      present_collection true
      expose :items, as: 'users', using: API::Entities::User
      expose :version, documentation: { type: 'string',
                                        desc: 'actual api version',
                                        required: true }

      def version
        options[:version]
      end
    end
  end
end

Usage in the API Layer


module API
  class Users < Grape::API
    version 'v2'

    # this will render { "users" : [ { "id" : "1" }, { "id" : "2" } ], "version" : "v2" }
    get '/users' do
      @users = User.all
      present @users, with: API::Entities::Users
    end

    # this will render { "user" : { "id" : "1" } }
    get '/users/:id' do
      @user = User.find(params[:id])
      present @user, with: API::Entities::User
    end
  end
end

Parameters:

  • present_collection (true or false) (defaults to: false)

    when true all objects will be available as items in your presenter instead of wrapping each object in an instance of your presenter.

  • collection_name (Symbol) (defaults to: :items)

    the name of the collection accessor in your entity object. Default :items


390
391
392
393
# File 'lib/grape_entity/entity.rb', line 390

def self.present_collection(present_collection = false, collection_name = :items)
  @present_collection = present_collection
  @collection_name = collection_name
end

.represent(objects, options = {}) ⇒ Object

This convenience method allows you to instantiate one or more entities by passing either a singular or collection of objects. Each object will be initialized with the same options. If an array of objects is passed in, an array of entities will be returned. If a single object is passed in, a single entity will be returned.

Parameters:

  • objects (Object or Array)

    One or more objects to be represented.

  • options (Hash) (defaults to: {})

    Options that will be passed through to each entity representation.

Options Hash (options):

  • :root (String or false)

    override the default root name set for the entity. Pass nil or false to represent the object or objects with no root name even if one is defined for the entity.

  • :serializable (true or false)

    when true a serializable Hash will be returned

  • :only (Array)

    all the fields that should be returned

  • :except (Array)

    all the fields that should not be returned


412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
# File 'lib/grape_entity/entity.rb', line 412

def self.represent(objects, options = {})
  @present_collection ||= nil
  if objects.respond_to?(:to_ary) && !@present_collection
    root_element = root_element(:collection_root)
    inner = objects.to_ary.map { |object| new(object, options.reverse_merge(collection: true)).presented }
  else
    objects = { @collection_name => objects } if @present_collection
    root_element = root_element(:root)
    inner = new(objects, options).presented
  end

  root_element = options[:root] if options.key?(:root)

  root_element ? { root_element => inner } : inner
end

.root(plural, singular = nil) ⇒ Object

This allows you to set a root element name for your representation.

Examples:

Entity Definition


module API
  module Entities
    class User < Grape::Entity
      root 'users', 'user'
      expose :id
    end
  end
end

Usage in the API Layer


module API
  class Users < Grape::API
    version 'v2'

    # this will render { "users" : [ { "id" : "1" }, { "id" : "2" } ] }
    get '/users' do
      @users = User.all
      present @users, with: API::Entities::User
    end

    # this will render { "user" : { "id" : "1" } }
    get '/users/:id' do
      @user = User.find(params[:id])
      present @user, with: API::Entities::User
    end
  end
end

Parameters:

  • plural (String)

    the root key to use when representing a collection of objects. If missing or nil, no root key will be used when representing collections of objects.

  • singular (String) (defaults to: nil)

    the root key to use when representing a single object. If missing or nil, no root key will be used when representing an individual object.


334
335
336
337
# File 'lib/grape_entity/entity.rb', line 334

def self.root(plural, singular = nil)
  @collection_root = plural
  @root = singular
end

.root_element(root_type) ⇒ Object

This method returns the entity's root or collection root node, or its parent's

Parameters:

  • root_type:

    either :collection_root or just :root


430
431
432
433
434
435
436
437
# File 'lib/grape_entity/entity.rb', line 430

def self.root_element(root_type)
  instance_variable = "@#{root_type}"
  if instance_variable_defined?(instance_variable) && instance_variable_get(instance_variable)
    instance_variable_get(instance_variable)
  elsif superclass.respond_to? :root_element
    superclass.root_element(root_type)
  end
end

.root_exposuresArray

Returns exposures that have been declared for this Entity on the top level.

Returns:

  • (Array)

    of exposures


210
211
212
# File 'lib/grape_entity/entity.rb', line 210

def self.root_exposures
  root_exposure.nested_exposures
end

.unexpose(*attributes) ⇒ Object


218
219
220
221
222
# File 'lib/grape_entity/entity.rb', line 218

def self.unexpose(*attributes)
  cannot_unexpose! unless can_unexpose?
  @documentation = nil
  root_exposures.delete_by(*attributes)
end

.unexpose_allObject


224
225
226
227
228
# File 'lib/grape_entity/entity.rb', line 224

def self.unexpose_all
  cannot_unexpose! unless can_unexpose?
  @documentation = nil
  root_exposures.clear
end

.valid_options(options) ⇒ Object

Raises an error if the given options include unknown keys. Renames aliased options.

Parameters:

  • options (Hash)

    Exposure options.


563
564
565
566
567
568
569
570
# File 'lib/grape_entity/entity.rb', line 563

def self.valid_options(options)
  options.keys.each do |key|
    raise ArgumentError, "#{key.inspect} is not a valid option." unless OPTIONS.include?(key)
  end

  options[:using] = options.delete(:with) if options.key?(:with)
  options
end

.with_options(options) ⇒ Object

Set options that will be applied to any exposures declared inside the block.

Examples:

Multi-exposure if


class MyEntity < Grape::Entity
  with_options if: { awesome: true } do
    expose :awesome, :sweet
  end
end

247
248
249
250
251
# File 'lib/grape_entity/entity.rb', line 247

def self.with_options(options)
  (@block_options ||= []).push(valid_options(options))
  yield
  @block_options.pop
end

Instance Method Details

#delegate_attribute(attribute) ⇒ Object


506
507
508
509
510
511
512
# File 'lib/grape_entity/entity.rb', line 506

def delegate_attribute(attribute)
  if respond_to?(attribute, true) && Grape::Entity > method(attribute).owner
    send(attribute)
  else
    delegator.delegate(attribute)
  end
end

#documentationObject


471
472
473
# File 'lib/grape_entity/entity.rb', line 471

def documentation
  self.class.documentation
end

#exec_with_attribute(attribute, &block) ⇒ Object


498
499
500
# File 'lib/grape_entity/entity.rb', line 498

def exec_with_attribute(attribute, &block)
  instance_exec(delegate_attribute(attribute), &block)
end

#exec_with_object(options, &block) ⇒ Object


494
495
496
# File 'lib/grape_entity/entity.rb', line 494

def exec_with_object(options, &block)
  instance_exec(object, options, &block)
end

#formattersObject


475
476
477
# File 'lib/grape_entity/entity.rb', line 475

def formatters
  self.class.formatters
end

#inspectObject

Prevent default serialization of :options or :delegator.


448
449
450
451
# File 'lib/grape_entity/entity.rb', line 448

def inspect
  fields = serializable_hash.map { |k, v| "#{k}=#{v}" }
  "#<#{self.class.name}:#{object_id} #{fields.join(' ')}>"
end

#presentedObject


439
440
441
442
443
444
445
# File 'lib/grape_entity/entity.rb', line 439

def presented
  if options[:serializable]
    serializable_hash
  else
    self
  end
end

#root_exposureObject


467
468
469
# File 'lib/grape_entity/entity.rb', line 467

def root_exposure
  self.class.root_exposure
end

#root_exposuresObject


463
464
465
# File 'lib/grape_entity/entity.rb', line 463

def root_exposures
  self.class.root_exposures
end

#serializable_hash(runtime_options = {}) ⇒ Object Also known as: as_json

The serializable hash is the Entity's primary output. It is the transformed hash for the given data model and is used as the basis for serialization to JSON and other formats.

Parameters:

  • runtime_options (Hash) (defaults to: {})

    Any options you pass in here will be known to the entity representation, this is where you can trigger things from conditional options etc.


486
487
488
489
490
491
492
# File 'lib/grape_entity/entity.rb', line 486

def serializable_hash(runtime_options = {})
  return nil if object.nil?

  opts = options.merge(runtime_options || {})

  root_exposure.serializable_value(self, opts)
end

#to_json(options = {}) ⇒ Object


516
517
518
519
# File 'lib/grape_entity/entity.rb', line 516

def to_json(options = {})
  options = options.to_h if options && options.respond_to?(:to_h)
  MultiJson.dump(serializable_hash(options))
end

#to_xml(options = {}) ⇒ Object


521
522
523
524
# File 'lib/grape_entity/entity.rb', line 521

def to_xml(options = {})
  options = options.to_h if options && options.respond_to?(:to_h)
  serializable_hash(options).to_xml(options)
end

#value_for(key, options = Options.new) ⇒ Object


502
503
504
# File 'lib/grape_entity/entity.rb', line 502

def value_for(key, options = Options.new)
  root_exposure.valid_value_for(key, self, options)
end