This article works for both of production and development environment and not like other articles only work for development. The tested environment are Rails 4.2.5.1, Ruby 2.3.0, Doorkeeper 3.1.0, Grape 0.14.0, Grape-kaminari 0.1.8, Wine_Bouncer 0.5.1 and Devise 3.5.5 .
1. Sample configuration for Grape gem in Rails app projects. 列出Grape gem的設定檔範例
In config/application.rb, add below two lines for including path of API files.
config.path.add File.join('app', 'controllers', 'api'), glob: File.join('**', '*.rb')
config.autoload_paths += Dir[Rails.root.join('app', 'controllers', 'api', '*')]
In config/routes.rb
mount API::Base => 'api'
2. Structures and contents of directory and files for building api. 列出使用Grape gem實作的Rails專案檔案內容、目錄與架構
In app/controllers
directory, create a directory named api. We put our APIs' files under this directory. In this example, there are three files:
app/controllers/api/base.rb
app/controllers/api/v1/overview_vegetables.rb
app/controllers/api/v1/specified_vegetables.rb
Content of base.rb
module API
class RecordNotFoundForToday < StandardError ; end
# This is exception you defined and it would be raised by overview_vegetables.rb or specified_vegetables.rb later.
class RecordNotFoundForConditions < StandardError ; end
# This is exception you defined and it would be raised by overview_vegetables.rb or specified_vegetables.rb later.
class Base < Grape::API
format :json
version 'v1', using: :path
rescue_from RecordNotFoundForToday do |e|
error!({"error" => "Found no records. Has not be updated for today, please wait.", "error_code" => 404}, 404)
end
rescue_from RecordNotFoundForConditions do |e|
error!({"error" => "Found no records through query values. Please recheck your parameters", "error_code" => 404}, 404)
end
mount SpecifiedVegetables
mount OverviewVegetables
end
end
Content of overview_vegetables.rb
class OverviewVegetables < Grape::API
format :json default_format :json
version 'v1', using: :path
include Grape::Kaminari # If you want to paginate query results, this gem can help, otherwise remove this line. Besides, you have to Add this statement:"gem 'grape-kaminari'" in your Gemfile and type command: bundle in terminal to install it.
resource :overview_vegetables do
desc "Get all transaction prices of all items today." # Description of /today api
paginate per_page: 50, max_per_page: 100, offset: 0 # Only add this line if you have included Grape::Kaminari in this file.
get :today do
veggies = OverviewVegetable.where(transaction_date: Date.today).find_in_batches.order(:name) do |vegetables|
paginate(vegetables) # If you don't use Grape::Kaminari, you can just leave vegetables alone and remove paginate() .
end
if veggie.empty?
raise API::RecordNotFoundForToday
else
veggies
end
end
desc "Get transaction prices of delegated item in a delegated day."
params do # You can specify what types, range of your parameters can adapted. You can find more details on grape offical website.
requires :transaction_date, type: Date, desc: 'code'
optional :name, type: String, allow_blank: false
optional :item_code, type: String, allow_blank: false
end
get '/' do
conditions = Hash[{date: params[:transaction_date], name: params[:name], code: params[:item_code]}.select{|k,v| v.present?}] # this line simplifies complicated query parameters using Hash tips from Ruby
veggies = OverviewVegetable.where(conditions) do |vegetables|
paginate(vegetables.order(:name, :date)) # Again. If you don't use Grape::Kaminari, you can just leave vegetables alone and remove paginate() .
end
if veggies.empty?
raise API::RecordNotFoundForConditions
else
veggies
end
end
end
end
Content of specified_vegetable.rb
class SpecifiedVegetables < Grape::API default_format :json
format :json
version 'v1', using: :path
include Grape::Kaminari # Again. If you want to paginate query results, this gem can help, otherwise remove this line. Besides, you have to Add this statement:"gem 'grape-kaminari'" in your Gemfile and type command: bundle in terminal to install it.
resource :specified_vegetables do
desc "Get all transaction prices of all items today."
paginate per_page: 50, max_per_page: 100, offset: 0 # Again. Only add this line if you have included Grape::Kaminari in this file.
get :today do
veggies = SpecifiedVegetable.where(transaction_date: Date.today).find_in_batches do |vegetables|
paginate(vegetables) # Again. If you don't use Grape::Kaminari, you can just leave vegetables alone and remove paginate() .
end
if veggies.empty?
raise API::RecordNotFoundForToday
else
veggies
end
end
desc "Get transaction prices of delegated item in a delegated day."
params do # Again. You can specify what types, range of your parameters can adapted. You can find more details on grape offical website.
require :transaction_date, type: Date, desc: 'code', allow_blank: false
optional :name, type: String, allow_blank: false
optional :trade_location, type: String, allow_blank: false
end
get '/' do
conditions = Hash[{transaction_date: params[:transaction_date], name: params[:name], trade_location: params[:trade_location]}.select{|k,v| v.present?}] # Again. this line simplifies complicated query parameters using Hash tips from Ruby
veggies = SpecifiedVegetable.where(conditions)
if veggie.empty?
raise API::RecordNotFoundForConditions
else
paginate(veggies)
end
end
end
end
3. Sample configuration and content for Doorkeeper gem. 列出Doorkeeper gem的設定檔範例
In config/routes.rb
use_doorkeeper # put this line at first
In config/initializers/doorkeeper.rb
. If you use person as table name in database with devise gem instead of user, just change all below user statements to person:
Doorkeeper.configure do
orm :active_record resource_owner_authenticator do
session[:user_return_to] = request.fullpath
current_user || redirect_to(new_user_session_path)
end
admin_authenticator do # If you want to restrict access to the web interface for adding oauth authorized applications, you need to declare the block below.
redirect_to root_url unless (user_signed_in? && current_user.admin?) # You must add a column named admin for user table if you want to use this statement block.
end
authorization_code_expires_in 20.minutes # Authorization code expiration time
access_token_expires_in 30.days # Access token expiration time
enable_application_owner :confirmation => false # default is false. Provide support for an owner to be assigned to each registered application (disabled by default). You can find more information on doorkeeper official website.
# Define access token scopes for your provider. More information at https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Scopes
default_scopes :public, :description => "Access public data."
optional_scopes :write, :description => "Update your data."
optional_scopes :admin, :description => "Do admin things."
access_token_methods :from_bearer_authorization, :from_access_token_param, :from_bearer_param # Change the way access token is authenticated from the request object.
grant_flows %w(authorization_code client_credentials implicit) # Recommend to only use this block you might want to auto-approved.
skip_authorization do
true
end
end
4. Structures and contents of files for building api.列出利用Grape gem的Rails api檔案要修改的地方與內容
Three files' directory structures are same
app/controllers/api/base.rb
app/controllers/api/v1/overview_vegetables.rb
app/controllers/api/v1/specified_vegetables.rb
Modified content of base.rb
(copy from above base.rb). # <- HERE
of text means it’s added.
require 'doorkeeper/grape/helpers'
module API
class RecordNotFoundForToday < StandardError ; end # This is exception you defined and it would be raised by overview_vegetables.rb or specified_vegetables.rb later.
class RecordNotFoundForConditions < StandardError ; end # This is exception you defined and it would be raised by overview_vegetables.rb or specified_vegetables.rb later.
class Base < Grape::API
helpers Doorkeeper::Grape::Helpers # <- HERE
format :json
version 'v1', using: :path
rescue_from RecordNotFoundForToday do |e|
error!({"error" => "Found no records. Has not be updated for today, please wait.", "error_code" => 404}, 404)
end
rescue_from RecordNotFoundForConditions do |e|
error!({"error" => "Found no records through query values. Please recheck your parameters", "error_code" => 404}, 404)
end
mount SpecifiedVegetables
mount OverviewVegetables
end
end
Modified content of overview_vegetables.rb
(copy from above overview_vegetables.rb
). # <- HERE
of text means it’s added.
require 'doorkeeper/grape/helpers'
class OverviewVegetables < Grape::API
format :json
default_format :json
version 'v1', using: :path
helpers Doorkeeper::Grape::Helpers # <- HERE
include Grape::Kaminari # If you want to paginate query results, this gem can help, otherwise remove this line. Besides, you have to Add this statement:"gem 'grape-kaminari'" in your Gemfile and type command: bundle in terminal to install it.
before do # <- HERE
doorkeeper_authorize! # <- HERE
end # <- HERE
resource :overview_vegetables do
desc "Get all transaction prices of all items today." # Description of /today api
paginate per_page: 50, max_per_page: 100, offset: 0 # Only add this line if you have included Grape::Kaminari in this file.
get :today do
veggies = OverviewVegetable.where(transaction_date: Date.today).find_in_batches.order(:name) do |vegetables|
paginate(vegetables) # If you don't use Grape::Kaminari, you can just leave vegetables alone and remove paginate() .
end
if veggie.empty?
raise API::RecordNotFoundForToday
else
veggies
end
end
desc "Get transaction prices of delegated item in a delegated day."
params do # You can specify what types, range of your parameters can adapted. You can find more details on grape offical website.
requires :transaction_date, type: Date, desc: 'code'
optional :name, type: String, allow_blank: false
optional :item_code, type: String, allow_blank: false
end
get '/' do
conditions = Hash[{date: params[:transaction_date], name: params[:name], code: params[:item_code]}.select{|k,v| v.present?}] # this line simplifies complicated query parameters using Hash tips from Ruby
veggies = OverviewVegetable.where(conditions) do |vegetables|
paginate(vegetables.order(:name, :date)) # Again. If you don't use Grape::Kaminari, you can just leave vegetables alone and remove paginate() .
end
if veggies.empty?
raise API::RecordNotFoundForConditions
else
veggies
end
end
end
end
Modified content of specified_vegetables.rb
(copy from above specified_vegetables.rb
) # <- HERE
of text means it’s added.
require 'doorkeeper/grape/helpers' # <- HERE
class SpecifiedVegetables < Grape::API
default_format :json
format :json
version 'v1', using: :path
helpers Doorkeeper::Grape::Helpers # <- HERE
include Grape::Kaminari # Again. If you want to paginate query results, this gem can help, otherwise remove this line. Besides, you have to Add this statement:"gem 'grape-kaminari'" in your Gemfile and type command: bundle in terminal to install it.
before do # <- HERE
doorkeeper_authorize! # <- HERE
end # <- HERE
resource :specified_vegetables do
desc "Get all transaction prices of all items today."
paginate per_page: 50, max_per_page: 100, offset: 0 # Again. Only add this line if you have included Grape::Kaminari in this file.
get :today do
veggies = SpecifiedVegetable.where(transaction_date: Date.today).find_in_batches do |vegetables|
paginate(vegetables) # Again. If you don't use Grape::Kaminari, you can just leave vegetables alone and remove paginate() .
end
if veggies.empty?
raise API::RecordNotFoundForToday
else
veggies
end
end
desc "Get transaction prices of delegated item in a delegated day."
params do # Again. You can specify what types, range of your parameters can adapted. You can find more details on grape offical website.
require :transaction_date, type: Date, desc: 'code', allow_blank: false
optional :name, type: String, allow_blank: false
optional :trade_location, type: String, allow_blank: false
end
get '/' do
conditions = Hash[{transaction_date: params[:transaction_date], name: params[:name], trade_location: params[:trade_location]}.select{|k,v| v.present?}] # Again. this line simplifies complicated query parameters using Hash tips from Ruby
veggies = SpecifiedVegetable.where(conditions)
if veggie.empty?
raise API::RecordNotFoundForConditions
else
paginate(veggies)
end
end
end
end
5. Sample configuration of wine_bouncer gem. 列出Wine_bouncer的範例設定檔
In config/initializers/wine_bouncer.rb
WineBouncer.configure do |config|
config.auth_strategy = :default
config.define_resource_owner do
User.find(doorkeeper_access_token.resource_owner_id) if doorkeeper_access_token
end
end
6. Final version files of building api 列出API檔案要修改的地方與內容
Three files' directory structures are same
app/controllers/api/base.rb
app/controllers/api/v1/overview_vegetables.rb
app/controllers/api/v1/specified_vegetables.rb
Modified content of base.rb
(copy from base.rb above section doorkeeper) # <- HERE
of text means it’s added.
require 'doorkeeper/grape/helpers'
module API
class RecordNotFoundForToday < StandardError ; end # This is exception you defined and it would be raised by overview_vegetables.rb or specified_vegetables.rb later.
class RecordNotFoundForConditions < StandardError ; end # This is exception you defined and it would be raised by overview_vegetables.rb or specified_vegetables.rb later.
class Base < Grape::API
helpers Doorkeeper::Grape::Helpers
format :json
version 'v1', using: :path
rescue_from RecordNotFoundForToday do |e|
error!({"error" => "Found no records. Has not be updated for today, please wait.", "error_code" => 404}, 404)
end
rescue_from RecordNotFoundForConditions do |e|
error!({"error" => "Found no records through query values. Please recheck your parameters", "error_code" => 404}, 404)
end
rescue_from WineBouncer::Errors::OAuthUnauthorizedError do |e| # <- HERE
error!({"error" => "OAuth unauthorized error because invalid access token.", "error_code" => 401}, 401) # <- HERE
end # <- HERE
rescue_from WineBouncer::Errors::OAuthForbiddenError do |e| # <- HERE
error!({"error" => "OAuth forbidden error because insufficient scopes", "error_code" => 403},403) # <- HERE
end # <- HERE
use ::WineBouncer::OAuth2 # <- HERE
before do # <- HERE
header['Access-Control-Allow-Origin'] = '*' # <- HERE
header['Access-Control-Request-Method'] = '*' # <- HERE
end # <- HERE
mount SpecifiedVegetables
mount OverviewVegetables
end
end
Modified content of overview_vegetables.rb
(copy from overview_vegetables.rb
above doorkeeper section) # <- HERE
of text means it’s added.
require 'doorkeeper/grape/helpers'
class OverviewVegetables < Grape::API format :json default_format :json version 'v1', using: :path helpers Doorkeeper::Grape::Helpers include Grape::Kaminari # If you want to paginate query results, this gem can help, otherwise remove this line. Besides, you have to Add this statement:"gem 'grape-kaminari'" in your Gemfile and type command: bundle in terminal to install it.
before do
doorkeeper_authorize!
end
resource :overview_vegetables do
desc "Get all transaction prices of all items today." # Description of /today api
paginate per_page: 50, max_per_page: 100, offset: 0 # Only add this line if you have included Grape::Kaminari in this file.
oauth2 'public' # <- HERE
get :today do
veggies = OverviewVegetable.where(transaction_date: Date.today).find_in_batches.order(:name) do |vegetables|
paginate(vegetables) # If you don't use Grape::Kaminari, you can just leave vegetables alone and remove paginate() .
end
if veggie.empty?
raise API::RecordNotFoundForToday
else
veggies
end
end
desc "Get transaction prices of delegated item in a delegated day."
params do # You can specify what types, range of your parameters can adapted. You can find more details on grape offical website.
requires :transaction_date, type: Date, desc: 'code'
optional :name, type: String, allow_blank: false
optional :item_code, type: String, allow_blank: false
end
oauth2 'public' # <- HERE
get '/' do
conditions = Hash[{date: params[:transaction_date], name: params[:name], code: params[:item_code]}.select{|k,v| v.present?}] # this line simplifies complicated query parameters using Hash tips from Ruby
veggies = OverviewVegetable.where(conditions) do |vegetables|
paginate(vegetables.order(:name, :date)) # Again. If you don't use Grape::Kaminari, you can just leave vegetables alone and remove paginate() .
end
if veggies.empty?
raise API::RecordNotFoundForConditions
else
veggies
end
end
end
end
Modified content of overview_vegetables.rb
(copy from overview_vegetables.rb
above doorkeeper section) # <- HERE
of text means it’s added.
require 'doorkeeper/grape/helpers'
class SpecifiedVegetables < Grape::API
default_format :json
format :json
version 'v1', using: :path
helpers Doorkeeper::Grape::Helpers
include Grape::Kaminari # Again. If you want to paginate query results, this gem can help, otherwise remove this line. Besides, you have to Add this statement:"gem 'grape-kaminari'" in your Gemfile and type command: bundle in terminal to install it.
before do
doorkeeper_authorize!
end
resource :specified_vegetables do
desc "Get all transaction prices of all items today."
paginate per_page: 50, max_per_page: 100, offset: 0 # Again. Only add this line if you have included Grape::Kaminari in this file.
oauth2 'public' # <- HERE
get :today do
veggies = SpecifiedVegetable.where(transaction_date: Date.today).find_in_batches do |vegetables|
paginate(vegetables) # Again. If you don't use Grape::Kaminari, you can just leave vegetables alone and remove paginate() .
end
if veggies.empty?
raise API::RecordNotFoundForToday
else
veggies
end
end
desc "Get transaction prices of delegated item in a delegated day."
params do # Again. You can specify what types, range of your parameters can adapted. You can find more details on grape official website.
require :transaction_date, type: Date, desc: 'code', allow_blank: false
optional :name, type: String, allow_blank: false
optional :trade_location, type: String, allow_blank: false
end
oauth2 'public' # <- HERE
get '/' do
conditions = Hash[{transaction_date: params[:transaction_date], name: params[:name], trade_location: params[:trade_location]}.select{|k,v| v.present?}] # Again. this line simplifies complicated query parameters using Hash tips from Ruby
veggies = SpecifiedVegetable.where(conditions)
if veggie.empty?
raise API::RecordNotFoundForConditions
else
paginate(veggies)
end
end
end
end
You can test oauth through software Postman and follow instructions insides reference 3 and 4. Also thanks to id: lukas inside the group techccu at slack for giving a pull request.
Reference:
Codetunes Introduction to Grape http://codetunes.com/2014/introduction-to-building-apis-with-grape/
Codetunes Oauth grape, doorkeeper, angularjs http://codetunes.com/2014/oauth-implicit-grant-with-grape-doorkeeper-and-angularjs/
OAuth2 tutorial: Grape API 整合 Doorkeeper https://blog.yorkxin.org/posts/2013/10/10/oauth2-tutorial-grape-api-doorkeeper/
OAuth2 tutorial: Protect Grape API with doorkeeper https://blog.yorkxin.org/posts/2013/11/05/oauth2-tutorial-grape-api-doorkeeper-en/
Wine_bouncer gem https://github.com/antek-drzewiecki/wine_bouncer
Grape gem and its wiki https://github.com/ruby-grape/grape
Doorkeeper gem and its wiki https://github.com/doorkeeper-gem/doorkeeper
Devise gem and its wiki https://github.com/plataformatec/devise