preloader
軟體工程

Configurations and programming examples about Grape, Doorkeeper and Wine_bouncer in a rails 4 app

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:

  1. app/controllers/api/base.rb
  2. app/controllers/api/v1/overview_vegetables.rb
  3. 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

 

  1. app/controllers/api/base.rb
  2. app/controllers/api/v1/overview_vegetables.rb
  3. 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

  1. app/controllers/api/base.rb
  2. app/controllers/api/v1/overview_vegetables.rb
  3. 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:

  1. Codetunes Introduction to Grape http://codetunes.com/2014/introduction-to-building-apis-with-grape/

  2. Codetunes Oauth grape, doorkeeper, angularjs http://codetunes.com/2014/oauth-implicit-grant-with-grape-doorkeeper-and-angularjs/

  3. OAuth2 tutorial: Grape API 整合 Doorkeeper https://blog.yorkxin.org/posts/2013/10/10/oauth2-tutorial-grape-api-doorkeeper/

  4. OAuth2 tutorial: Protect Grape API with doorkeeper https://blog.yorkxin.org/posts/2013/11/05/oauth2-tutorial-grape-api-doorkeeper-en/

  5. Wine_bouncer gem https://github.com/antek-drzewiecki/wine_bouncer

  6. Grape gem and its wiki https://github.com/ruby-grape/grape

  7. Doorkeeper gem and its wiki https://github.com/doorkeeper-gem/doorkeeper

  8. Devise gem and its wiki https://github.com/plataformatec/devise