Виноград: обязательные параметры с виноградной сущностью

Я пишу сервер API с виноградом, и я решил использовать виноградную сущность, потому что у нее есть возможность автоматически генерировать документацию для чванства. Но теперь у меня проблема, когда я устанавливаю параметр по мере необходимости. Потому что виноград не подтверждает наличие параметра. Похоже, что виноград игнорирует required: true параметров объекта.

app.rb

module Smart
  module Version1
    class App < BaseApi

      resource :app do

        # POST /app
        desc 'Creates a new app' do
          detail 'It is used to re gister a new app on the server and get the app_id'
          params  Entities::OSEntity.documentation
          success Entities::AppEntity
          failure [[401, 'Unauthorized', Entities::ErrorEntity]]
          named 'My named route'
        end
        post do
          app = ::App.create params
          present app, with: Entities::AppEntity
        end
      end
    end
  end
end

os_entity.rb

module Smart
  module Entities
    class OSEntity < Grape::Entity

      expose :os, documentation: { type: String, desc: 'Operative system name', values: App::OS_LIST, required: true }

    end
  end
end

app_entity.rb

module Smart
  module Entities
    class AppEntity < OSEntity

      expose :id, documentation: { type: 'integer', desc: 'Id of the created app', required: true }
      expose :customer_id, documentation: { type: 'integer', desc: 'Id of the customer', required: true }

    end
  end
end

Все остальное теперь отлично работает, но я не знаю, как использовать сущности СУХИМ способом и заставить виноград проверять требование параметра.


person Spike886    schedule 08.04.2015    source источник


Ответы (2)


После некоторой работы я смог заставить виноград работать так, как я думаю, он должен работать. Потому что я не хочу повторять код как для проверки, так и для документации. Вам просто нужно добавить это в инициализаторы (если вы, конечно, в рельсах). Я также смог поддерживать вложенные ассоциации. Как видите, код API выглядит очень просто, а чванство выглядит идеально. Вот API и все необходимые сущности:

приложение/api/smart/entities/characteristics_params_entity.rb

module Smart
  module Entities
    class CharacteristicsParamsEntity < Grape::Entity

      root :characteristics, :characteristic
      expose :id, documentation: { type: Integer, desc: 'Id of the characteristic' }

    end
  end
end

приложение/api/smart/entities/characterisitcs_entity.rb

module Smart
  module Entities
    class CharacteristicsEntity < CharacteristicsParamsEntity

      expose :id, documentation: { type: Integer, desc: 'Id of the characteristic' }
      expose :name, documentation: { type: String, desc: 'Name of the characteristic' }
      expose :description, documentation: { type: String, desc: 'Description of the characteristic' }
      expose :characteristic_type, documentation: { type: String, desc: 'Type of the characteristic' }
      expose :updated_at, documentation: { type: Date, desc: 'Last updated time of the characteristic' }

    end
  end
end

приложение/api/smart/entities/apps_params_entity.rb

module Smart
  module Entities
    class AppsParamsEntity < Grape::Entity

      expose :os, documentation: { type: String, desc: 'Operative system name', values: App::OS_LIST, required: true }
      expose :characteristic_ids, using: CharacteristicsParamsEntity, documentation: { type: CharacteristicsParamsEntity, desc: 'List of characteristic_id that the customer has', is_array: true }


    end
  end
end

приложение/api/smart/entities/apps_entity.rb

module Smart
  module Entities
    class AppsEntity < AppsParamsEntity

      unexpose :characteristic_ids
      expose :id, documentation: { type: 'integer', desc: 'Id of the created app', required: true }
      expose :customer_id, documentation: { type: 'integer', desc: 'Id of the customer', required: true }
      expose :characteristics, using: CharacteristicsEntity, documentation: { is_array: true, desc: 'List of characteristics that the customer has' }

    end
  end
end

приложение/api/smart/version1/apps.rb

module Smart
  module Version1
    class Apps < Version1::BaseAPI

    resource :apps do

        # POST /apps
        desc 'Creates a new app' do
          detail 'It is used to register a new app on the server and get the app_id'
          params Entities::AppsParamsEntity.documentation
          success Entities::AppsEntity
          failure [[400, 'Bad Request', Entities::ErrorEntity]]
          named 'create app'
        end
        post do
          app = ::App.create! params
          present app, with: Entities::AppsEntity
        end

      end

    end
  end
end

И это код, который делает волшебство, чтобы заставить его работать:

config/initializers/grape_extensions.rb

class Evaluator
  def initialize(instance)
    @instance = instance
  end

  def params parameters
    evaluator = self
    @instance.normal_params do
      evaluator.list_parameters(parameters, self)
    end
  end

  def method_missing(name, *args, &block)
  end

  def list_parameters(parameters, grape)
    evaluator = self
    parameters.each do |name, description|
      description_filtered = description.reject { |k| [:required, :is_array].include?(k) }
      if description.present? && description[:required]
        if description[:type] < Grape::Entity
          grape.requires name, description_filtered.merge(type: Array) do
            evaluator.list_parameters description[:type].documentation, self
          end
        else
          grape.requires name, description_filtered
        end
      else
        if description[:type] < Grape::Entity
          grape.optional name, description_filtered.merge(type: Array) do
            evaluator.list_parameters description[:type].documentation, self
          end
        else
          grape.optional name, description_filtered
        end
      end
    end
  end
end

module GrapeExtension
  def desc name, options = {}, &block
    Evaluator.new(self).instance_eval &block if block
    super name, options do
      def params *args
      end

      instance_eval &block if block
    end
  end
end

class Grape::API
  class << self
    prepend GrapeExtension
  end
end

Это результат примера:

Результат чванства

person Spike886    schedule 11.04.2015
comment
Я имел в виду, что параметры в блоке desc бесполезны для чванства винограда, так зачем вообще пытаться их объявлять? И зачем объявлять определенные сущности для API? Но с другой стороны: отличная работа. Почему бы не добавить это к винограду в пулреквесте? - person nathanvda; 11.04.2015

Мне нравится сочетание винограда/винограда-чванства/винограда-сущности для создания API. Обычно я использую объекты винограда для построения результата, а вовсе не для документирования/проверки API. Согласно документации (для виноградной сущности) это должно работать, но я предполагаю, что просто собрать документацию.

Согласно документации по проверке и приведению параметров, требуется block для обеспечения любой проверки/принуждения.

[EDIT: смешивание параметров]

Вы можете определить параметры в desc с помощью объекта, но для проверки вы должны предоставить блок params на том же уровне, что и блок desc, например:

    # POST /app
    desc 'Creates a new app' do
      detail 'It is used to re gister a new app on the server and get the app_id'
      params  Entities::OSEntity.documentation
      success Entities::AppEntity
      failure [[401, 'Unauthorized', Entities::ErrorEntity]]
      named 'My named route'
    end
    params do
      requires :name, String
      optional :description, String
    end 
    post do
      app = ::App.create params
      present app, with: Entities::AppEntity
    end

Они оба называются params, но расположены совершенно по-разному и выполняют разные функции.
Я не уверен, что блок desc имеет какое-либо применение, кроме документации (и как извлечь эту документацию для меня немного загадка).

Жемчужина виноградного чванства не использует его, мой типичный desc выглядит так:

  desc "list of batches", {
    :notes => <<-NOTE
      Show a list of all available batches.

      ## Request properties

      * _Safe:_ Yes
      * _Idempotent:_ Yes
      * _Can be retried:_ Yes
    NOTE
  }
  params do
    optional :page, desc: 'paginated per 25'
  end
  get do
    present Batch.page(params[:page]), with: API::Entities::Batch
  end

где :notes отображаются с использованием уценки. Как это выглядит в swagger-ui swagger-ui

person nathanvda    schedule 08.04.2015
comment
Но если это только для документации (чванство), что означает params Entities::OSEntity.documentation? Потому что вы всегда должны добавлять параметры другим способом для проверки. Также в документации по винограду в разделе params API::Entities::Status.documentation говорится о параметрах: определение параметров непосредственно из объекта. - person Spike886; 08.04.2015
comment
Теперь я понимаю путаницу: есть две команды params. Итак, объект определяет параметры, но в блоке desc. Который imho не используется для проверки, может быть использован для создания документации, но не с помощью жемчужины виноградного чванства. Блок params на том же уровне, что и desc, обрабатывает проверку (и используется драгоценным камнем виноградного чванства). - person nathanvda; 08.04.2015
comment
Есть запрос функции, который частично решит эту проблему: github.com/inridea/grape/issues/827. - person nathanvda; 08.04.2015
comment
Наконец-то я создал хак, чтобы сделать виноград, работающим как надо. посмотри на мой ответ - person Spike886; 11.04.2015