Class: Grape::Entity

Inherits:
Object
  • Object
show all
Defined in:
lib/grape_entity/entity.rb,
lib/grape_entity/delegator.rb,
lib/grape_entity/delegator/base.rb,
lib/grape_entity/delegator/hash_object.rb,
lib/grape_entity/delegator/plain_object.rb,
lib/grape_entity/delegator/fetchable_object.rb,
lib/grape_entity/delegator/openstruct_object.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: DSL, Delegator

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

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



405
406
407
408
409
# File 'lib/grape_entity/entity.rb', line 405

def initialize(object, options = {})
  @object = object
  @delegator = Delegator.new object
  @options = options
end

Class Attribute Details

.exposuresHash

Returns exposures that have been declared for this Entity or ancestors. The keys are symbolized references to methods on the containing object, the values are the options that were passed into expose.



106
107
108
# File 'lib/grape_entity/entity.rb', line 106

def exposures
  @exposures
end

.formattersHash

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



110
111
112
# File 'lib/grape_entity/entity.rb', line 110

def formatters
  @formatters
end

.nested_attribute_namesObject

Returns the value of attribute nested_attribute_names.



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

def nested_attribute_names
  @nested_attribute_names
end

.nested_exposuresObject

Returns the value of attribute nested_exposures.



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

def nested_exposures
  @nested_exposures
end

.root_exposuresObject

Returns the value of attribute root_exposures.



107
108
109
# File 'lib/grape_entity/entity.rb', line 107

def root_exposures
  @root_exposures
end

Instance Attribute Details

#delegatorObject (readonly)

Returns the value of attribute delegator.



45
46
47
# File 'lib/grape_entity/entity.rb', line 45

def delegator
  @delegator
end

#objectObject (readonly)

Returns the value of attribute object.



45
46
47
# File 'lib/grape_entity/entity.rb', line 45

def object
  @object
end

#optionsObject (readonly)

Returns the value of attribute options.



45
46
47
# File 'lib/grape_entity/entity.rb', line 45

def options
  @options
end

Class Method Details

.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.



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

def self.documentation
  @documentation ||= exposures.each_with_object({}) do |(attribute, exposure_options), memo|
    if exposure_options[:documentation].present?
      memo[key_for(attribute)] = exposure_options[: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.



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/grape_entity/entity.rb', line 154

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

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

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

  options[:proc] = block if block_given? && block.parameters.any?

  @nested_attributes ||= []

  # rubocop:disable Style/Next
  args.each do |attribute|
    if @nested_attributes.empty?
      root_exposures[attribute] = options
    else
      orig_attribute = attribute.to_sym
      attribute = "#{@nested_attributes.last}__#{attribute}".to_sym
      nested_attribute_names[attribute] = orig_attribute
      options[:nested] = true
      nested_exposures.deep_merge!(@nested_attributes.last.to_sym  => { attribute => options })
    end

    exposures[attribute] = options

    # Nested exposures are given in a block with no parameters.
    if block_given? && block.parameters.empty?
      @nested_attributes << attribute
      block.call
      @nested_attributes.pop
    end
  end
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


250
251
252
253
# File 'lib/grape_entity/entity.rb', line 250

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

.inherited(subclass) ⇒ Object



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

def self.inherited(subclass)
  subclass.exposures = exposures.dup
  subclass.root_exposures = root_exposures.dup
  subclass.nested_exposures = nested_exposures.dup
  subclass.nested_attribute_names = nested_attribute_names.dup
  subclass.formatters = formatters.dup
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::Users
      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


350
351
352
353
# File 'lib/grape_entity/entity.rb', line 350

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.

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



372
373
374
375
376
377
378
379
380
381
382
383
384
385
# File 'lib/grape_entity/entity.rb', line 372

def self.represent(objects, options = {})
  if objects.respond_to?(:to_ary) && ! @present_collection
    root_element =  root_element(:collection_root)
    inner = objects.to_ary.map { |object| new(object, { collection: true }.merge(options)).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


294
295
296
297
# File 'lib/grape_entity/entity.rb', line 294

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



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

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

.unexpose(attribute) ⇒ Object



191
192
193
194
195
196
# File 'lib/grape_entity/entity.rb', line 191

def self.unexpose(attribute)
  root_exposures.delete(attribute)
  exposures.delete(attribute)
  nested_exposures.delete(attribute)
  nested_attribute_names.delete(attribute)
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


207
208
209
210
211
# File 'lib/grape_entity/entity.rb', line 207

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

Instance Method Details

#documentationObject



419
420
421
# File 'lib/grape_entity/entity.rb', line 419

def documentation
  self.class.documentation
end

#except_fields(options, for_attribute = nil) ⇒ Object



489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
# File 'lib/grape_entity/entity.rb', line 489

def except_fields(options, for_attribute = nil)
  return nil unless options[:except]

  @except_fields ||= options[:except].each_with_object({}) do |attribute, allowed_fields|
    if attribute.is_a?(Hash)
      attribute.each do |attr, nested_attrs|
        allowed_fields[attr] ||= []
        allowed_fields[attr] += nested_attrs
      end
    else
      allowed_fields[attribute] = true
    end
  end.symbolize_keys

  if for_attribute && @except_fields[for_attribute].is_a?(Array)
    @except_fields[for_attribute]
  elsif for_attribute.nil?
    @except_fields
  end
end

#exposuresObject



411
412
413
# File 'lib/grape_entity/entity.rb', line 411

def exposures
  self.class.exposures
end

#formattersObject



423
424
425
# File 'lib/grape_entity/entity.rb', line 423

def formatters
  self.class.formatters
end

#only_fields(options, for_attribute = nil) ⇒ Object



468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
# File 'lib/grape_entity/entity.rb', line 468

def only_fields(options, for_attribute = nil)
  return nil unless options[:only]

  @only_fields ||= options[:only].each_with_object({}) do |attribute, allowed_fields|
    if attribute.is_a?(Hash)
      attribute.each do |attr, nested_attrs|
        allowed_fields[attr] ||= []
        allowed_fields[attr] += nested_attrs
      end
    else
      allowed_fields[attribute] = true
    end
  end.symbolize_keys

  if for_attribute && @only_fields[for_attribute].is_a?(Array)
    @only_fields[for_attribute]
  elsif for_attribute.nil?
    @only_fields
  end
end

#presentedObject



397
398
399
400
401
402
403
# File 'lib/grape_entity/entity.rb', line 397

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

#root_exposuresObject



415
416
417
# File 'lib/grape_entity/entity.rb', line 415

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.



434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
# File 'lib/grape_entity/entity.rb', line 434

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

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

  root_exposures.each_with_object({}) do |(attribute, exposure_options), output|
    next unless should_return_attribute?(attribute, opts) && conditions_met?(exposure_options, opts)

    partial_output = value_for(attribute, opts)

    output[self.class.key_for(attribute)] =
      if partial_output.respond_to?(:serializable_hash)
        partial_output.serializable_hash(runtime_options)
      elsif partial_output.is_a?(Array) && partial_output.all? { |o| o.respond_to?(:serializable_hash) }
        partial_output.map(&:serializable_hash)
      elsif partial_output.is_a?(Hash)
        partial_output.each do |key, value|
          partial_output[key] = value.serializable_hash if value.respond_to?(:serializable_hash)
        end
      else
        partial_output
      end
  end
end

#should_return_attribute?(attribute, options) ⇒ Boolean



459
460
461
462
463
464
465
466
# File 'lib/grape_entity/entity.rb', line 459

def should_return_attribute?(attribute, options)
  key = self.class.key_for(attribute)
  only = only_fields(options).nil? ||
         only_fields(options).include?(key)
  except = except_fields(options) && except_fields(options).include?(key) &&
           except_fields(options)[key] == true
  only && !except
end

#to_json(options = {}) ⇒ Object



512
513
514
515
# File 'lib/grape_entity/entity.rb', line 512

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



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

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