API
Web app
Mobile App
...
...
Business Logic
External Services
Grape lets you write a REST API easily as drink a glass of wine...
class WinesCollection::V1::Wines < Grape::API
version 'v1', using: :path
format :json
content_type :json, 'application/json'
formatter :json, JavascriptCaseFormatter
helpers do
def current_user
User.find params[:user_id]
end
end
resource :wines do
desc 'search for wines.'
params do
optional :name, type: String
end
get :search do
# ...
end
after do
logger.info("#{current_user} loves wines!")
end
end
end
class WinesCollection::V1::Wines < Grape::API
resource :wines do
# ...
end
# alias_method: resources, group, resource, resources segment
end
GET/HEAD get do # ...
POST post do # ...
PUT put do # ...
PATCH patch do # ...
DELETE delete do # ...
CUSTOM ROUTES! route :any, '/path' do # ...
HTTP methods
class WinesCollection::V1::Wines < Grape::API
helpers do
def current_user
User.find(params.user_id)
end
def logger
self.logger
end
end
# ...
get ':user_id' do
logger.info("hello #{current_user.name}!")
end
# ...
end
post '' do
params # => {"name"=>"Dolcetto", "bottling_date"=>"2015/03/31", "color"=>"red", "bac"=>"5.7", "acquisition_date"=>"2015/04/16", "image"=>"http://lol.com/dolcetto.jpg"}
params.class # => Hashie::Mash
params['name'] # => "Dolcetto"
params[:name] # => "Dolcetto"
params.name # => "Dolcetto"
end
curl -X POST -H "Content-Type: application/json" -d '{
"name": "Dolcetto",
"bottling_date": "2015/03/31",
"color": "red",
"bac": "5.7",
"acquisition_date": "2015/04/16",
"image": "http://lol.com/dolcetto.jpg"
}' 'http://api.winescollection.com:3000/v1/wines'
...parse any kind of data...
curl -X POST -H "Content-Type: application/json" -d '{"name": "Dolcetto",
"bottling_date": "2015/03/31","color": "red","bac": "5.7",
"acquisition_date": "2015/04/16","image":
"http://lol.com/dolcetto.jpg"}' 'http://api.winescollection.com:3000/v1/wines'
curl -X POST -H "Content-Type: application/xml" -d
'<wine>
<name>Nebbiolo</name>
<bottling_date>2015/05/31</bottling_date>
<color>red</color>
<bac>5.7</bac>
<acquisition_date>2015/06/16</acquisition_date>
<image>http://lol.com/nebbiolo.jpg</image>
</wine>' 'http://api.winescollection.com:3000/v1/wines'
curl -X POST -H "Content-Type: text/plain" 'http://api.winescollection.com:3000/v1/w
ines?name=Nebbiolo&bottling_date=red&color=5.7&acquisition_date=2015%2F06%2F16&image
=http%3A%2F%2Flol.com%2Fnebbiolo.jpg'
Decleared params
desc 'Create a new wine.'
params do
requires :wine, type: Hash do
requires :name, type: String
requires :bottling_date, type: Date
optional :image, type: String, regex: /(https?\:\/\/)?([a-zA-Z]+\.)+(org|com|it)/
optional :bac, type: MyCustomType
given :bac do
requires :color, values: [:red, :white, :rose]
end
end
requires :liquor, type: Hash do
# ...
end
mutually_exclusive :liquor, :wine
# exactly_one_of :wine
# at_least_one_of :liquor
# all_or_none_of :wine, :liquor
end
post do
decleared_params = decleared(params) # params.slice(...)
end
Custom Validators
class AlcoholLevel < Grape::Validations::Base
def validate_param!(attr_name, params)
if params[attr_name] <= @option
fail Grape::Exceptions::Validation,
params: [@scope.full_name(attr_name)],
message: "This is not enough alcoholic!"
end
end
end
params do
requires :bac, alcohol_level: 10.0
end
class WinesCollection::V1::Wines < Grape::API
# ...
before do
# ...
end
before_validation do
# ...
end
# parameter validations
after_validation do
# ...
end
get do # the actual API call
# ...
end
after do
# ...
end
# ...
end
class WinesCollection::V1::Wines < Grape::API
# ...
version 'v1', using: :path
# ...
end
curl 'http://api.winescollection.com:3000/v1/wines'
class WinesCollection::V1::Wines < Grape::API
# ...
version 'v1', using: :header, vendor: 'oc-agricola'
# ...
end
curl -H Accept:application/vnd.oc-agricola-v1+json http://api.winescollection.com:3000/wines
class WinesCollection::V1::Wines < Grape::API
# ...
version 'v1', using: :accept_version_header
# ...
end
curl -H Accept-Version:v1 http://api.winescollection.com:3000/wines
class WinesCollection::V1::Wines < Grape::API
# ...
version 'v1', using: :param, parameter: 'ver'
# ...
end
curl http://api.winescollection.com:3000/wines?ver=v1
class WinesCollection::V1::Wines < Grape::API
# ...
format :xml
default_format :json
content_type :xml, 'application/xml'
content_type :json, 'application/json'
formatter :json, ->(object, env) { object.to_js_case }
formatter :json, JavascriptCaseFormatter # self.call
# ...
end
class WinesCollection::V1::Wines < Grape::API
# ...
get ':user_id' do
wine = Wine.find(params[:id])
# ...
present wine, with: WinesCollection::V1::Entities::Wine
end
# ...
end
module WinesCollection::V1::Entities
class Wine < Grape:Entity
expose :name
expose :bac, as: :blood_alcohol_content
expose :dates do
expose :bottling_date, if: lambda { |instance, options|
# ...
}
expose :acquisition_date, safe: true
end
expose :image do |instance, options|
downcase_file_format(instance.image)
end
expose :color, using: WineColor # < Grape::Entity
def downcase_file_format(image)
# ...
end
end
end
config.paths.add File.join('app', 'api'), glob: File.join('**', '*.rb') config.autoload_paths += Dir[Rails.root.join('app', 'api', '*')]
mount WinesCollection::API => '/'
gem 'grape' gem 'hashie-forbidden_attributes'
require 'sinatra' require 'grape' class WinesCollection::API < Grape::API # ... end class WinesCollectionApp < Sinatra::Base get '/' do # ... end end use Rack::Session::Cookie run Rack::Cascade.new [WinesCollection::API, WinesCollectionApp]
use ActiveRecord::ConnectionAdapters::ConnectionManagement run WinesCollection::API
run WinesCollection::API
Take a look at http://www.ruby-grape.org/projects/
Questions?
Wine Time!