I'm writing an API server with grape and i choose to use grape-entity because it has the capability to auto generate the documentation for swagger.
But now i have a problem when i set a param as required. Because grape don't validate that the param is present. It looks like grape ignores the required: true
of the entity's params.
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
Everything else is working great now, but i don't know how to use the entities in a DRY way, and make grape validating the requirement of the parameter.
After some work, I was able to make grape work as I think it should be working. Because I don't want to repeat the code for both of the validation and the documentation. You just have to add this to the initializers (if you are in rails, of course). I also was able to support nested associations. As you can see, the API code looks so simple and the swagger looks perfect. Here are the API and all the needed entities:
app/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
app/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
app/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
app/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
app/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
And this is the code that do the magic to make it work:
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
This is the result of the example:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With