Class: Grape::Entity

Inherits:
Object
  • Object
show all
Defined in:
lib/grape_entity/entity.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

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

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



426
427
428
# File 'lib/grape_entity/entity.rb', line 426

def initialize(object, options = {})
  @object, @options = object, options
end

Class Attribute Details

._nested_attribute_names_hashObject

Returns the value of attribute _nested_attribute_names_hash



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

def _nested_attribute_names_hash
  @_nested_attribute_names_hash
end

._nested_exposures_hashObject

Returns the value of attribute _nested_exposures_hash



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

def _nested_exposures_hash
  @_nested_exposures_hash
end

Instance Attribute Details

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



231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/grape_entity/entity.rb', line 231

def self.documentation
  @documentation ||= exposures.inject({}) do |memo, (attribute, exposure_options)|
    unless exposure_options[:documentation].nil? || exposure_options[:documentation].empty?
      memo[key_for(attribute)] = exposure_options[:documentation]
    end
    memo
  end

  if superclass.respond_to? :documentation
    @documentation = superclass.documentation.merge(@documentation)
  end

  @documentation
end

.expose(*args, &block) ⇒ Object

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



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/grape_entity/entity.rb', line 126

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 ||= []

  args.each do |attribute|
    unless @nested_attributes.empty?
      orig_attribute = attribute.to_sym
      attribute = "#{@nested_attributes.last}__#{attribute}"
      nested_attribute_names_hash[attribute.to_sym] = orig_attribute
      options[:nested] = true
      nested_exposures_hash[@nested_attributes.last.to_sym] ||= {}
      nested_exposures_hash[@nested_attributes.last.to_sym][attribute.to_sym] = options
    end

    exposures[attribute.to_sym] = 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

.exposuresObject

Returns a hash of 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.



179
180
181
182
183
184
185
186
187
188
189
# File 'lib/grape_entity/entity.rb', line 179

def self.exposures
  return @exposures unless @exposures.nil?

  @exposures = {}

  if superclass.respond_to? :exposures
    @exposures = superclass.exposures.merge(@exposures)
  end

  @exposures
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


272
273
274
275
# File 'lib/grape_entity/entity.rb', line 272

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

.formattersObject

Returns a hash of all formatters that are registered for this and it's ancestors.



278
279
280
281
282
283
284
285
286
# File 'lib/grape_entity/entity.rb', line 278

def self.formatters
  @formatters ||= {}

  if superclass.respond_to? :formatters
    @formatters = superclass.formatters.merge(@formatters)
  end

  @formatters
end

.nested_attribute_namesObject



203
204
205
206
207
208
209
210
211
212
213
# File 'lib/grape_entity/entity.rb', line 203

def nested_attribute_names
  return @nested_attribute_names unless @nested_attribute_names.nil?

  @nested_attribute_names = {}.merge(nested_attribute_names_hash)

  if superclass.respond_to? :nested_attribute_names
    @nested_attribute_names = superclass.nested_attribute_names.deep_merge(@nested_attribute_names)
  end

  @nested_attribute_names
end

.nested_attribute_names_hashObject



195
196
197
# File 'lib/grape_entity/entity.rb', line 195

def nested_attribute_names_hash
  self._nested_attribute_names_hash ||= {}
end

.nested_exposuresObject



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

def nested_exposures
  return @nested_exposures unless @nested_exposures.nil?

  @nested_exposures = {}.merge(nested_exposures_hash)

  if superclass.respond_to? :nested_exposures
    @nested_exposures = superclass.nested_exposures.deep_merge(@nested_exposures)
  end

  @nested_exposures
end

.nested_exposures_hashObject



199
200
201
# File 'lib/grape_entity/entity.rb', line 199

def nested_exposures_hash
  self._nested_exposures_hash ||= {}
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


383
384
385
386
# File 'lib/grape_entity/entity.rb', line 383

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



403
404
405
406
407
408
409
410
411
412
413
414
415
416
# File 'lib/grape_entity/entity.rb', line 403

def self.represent(objects, options = {})
  if objects.respond_to?(:to_ary) && ! @present_collection
    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
    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


327
328
329
330
# File 'lib/grape_entity/entity.rb', line 327

def self.root(plural, singular = nil)
  @collection_root = plural
  @root = singular
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


170
171
172
173
174
# File 'lib/grape_entity/entity.rb', line 170

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

Instance Method Details

#documentationObject



440
441
442
# File 'lib/grape_entity/entity.rb', line 440

def documentation
  self.class.documentation
end

#exposuresObject



430
431
432
# File 'lib/grape_entity/entity.rb', line 430

def exposures
  self.class.exposures
end

#formattersObject



444
445
446
# File 'lib/grape_entity/entity.rb', line 444

def formatters
  self.class.formatters
end

#presentedObject



418
419
420
421
422
423
424
# File 'lib/grape_entity/entity.rb', line 418

def presented
  if options[:serializable]
    serializable_hash
  else
    self
  end
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.



455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
# File 'lib/grape_entity/entity.rb', line 455

def serializable_hash(runtime_options = {})
  return nil if object.nil?
  opts = options.merge(runtime_options || {})
  valid_exposures.inject({}) do |output, (attribute, exposure_options)|
    if 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.map { |o| o.respond_to? :serializable_hash }.include?(false)
          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
    output
  end
end

#to_json(options = {}) ⇒ Object



480
481
482
483
# File 'lib/grape_entity/entity.rb', line 480

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



485
486
487
488
# File 'lib/grape_entity/entity.rb', line 485

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

#valid_exposuresObject



434
435
436
437
438
# File 'lib/grape_entity/entity.rb', line 434

def valid_exposures
  exposures.reject { |_, options| options[:nested] }.select do |attribute, exposure_options|
    valid_exposure?(attribute, exposure_options)
  end
end