Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Railsを使用したAPIの作成

今日では、API(アプリケーションプログラミングインターフェイス)に大きく依存するのが一般的な方法です。 FacebookやTwitterなどの大規模なサービスだけでなく、APIは、React、Angularなどのクライアント側フレームワークが普及しているため非常に人気があります。 Ruby on Railsはこの傾向に従っており、最新バージョンでは、APIのみのアプリケーションを作成できる新機能が提供されています。

当初、この機能はrails-apiと呼ばれる別のgemにパックされていましたが、Rails 5のリリース以降、フレームワークのコアの一部になりました。この機能とActionCableはおそらく最も期待されていたので、今日はそれについて説明します。

この記事では、APIのみのRailsアプリケーションを作成する方法と、ルートとコントローラーを構造化する方法、JSON形式で応答する方法、シリアライザーを追加する方法、CORS(クロスオリジンリソースシェアリング)を設定する方法について説明します。また、APIを保護し、悪用から保護するためのいくつかのオプションについても学びます。

この記事のソースはGitHubで入手できます。

APIのみのアプリケーションの作成

開始するには、次のコマンドを実行します。

rails new RailsApiDemo --api

RailsApiDemoと呼ばれる新しいAPIのみのRailsアプリケーションを作成します 。 --apiのサポートを忘れないでください オプションはRails5でのみ追加されたため、このバージョンまたは新しいバージョンがインストールされていることを確認してください。

Gemfileを開きます そして、それは通常よりもはるかに小さいことに注意してください:coffee-railsのような宝石 、turbolinks 、およびsass-rails なくなった。

config / application.rb ファイルに新しい行が含まれています:

config.api_only = true

これは、Railsがより小さなミドルウェアのセットをロードすることを意味します。たとえば、Cookieとセッションのサポートはありません。さらに、スキャフォールドを生成しようとすると、ビューとアセットは作成されません。実際、ビュー/レイアウトを確認すると ディレクトリの場合、 application.html.erb ファイルもありません。

もう1つの重要な違いは、ApplicationController ActionController::APIから継承します 、ActionController::Baseではありません 。

これでほぼ完了です。全体として、これは何度も見た基本的なRailsアプリケーションです。次に、いくつかのモデルを追加して、作業できるようにします。

rails g model User name:string
rails g model Post title:string body:text user:belongs_to
rails db:migrate

ここでは特別なことは何も起こっていません。タイトルと本文がユーザーに属している投稿です。

適切な関連付けが設定されていることを確認し、いくつかの簡単な検証チェックも提供します。

models / user.rb

  has_many :posts

  validates :name, presence: true

models / post.rb

  belongs_to :user

  validates :title, presence: true
  validates :body, presence: true

素晴らしい!次のステップは、新しく作成されたテーブルにいくつかのサンプルレコードをロードすることです。

デモデータの読み込み

一部のデータを読み込む最も簡単な方法は、 seeds.rbを利用することです。 db内のファイル ディレクトリ。しかし、私は(多くのプログラマーがそうであるように)怠惰であり、サンプルコンテンツについて考えたくありません。したがって、名前、電子メール、流行に敏感な単語、「loremipsum」テキストなどのさまざまな種類のランダムデータを生成できる偽物の宝石を利用してみませんか。

ジェムファイル

group :development do
    gem 'faker'
end

宝石をインストールします:

bundle install

次に、 seeds.rbを微調整します :

db / seeds.rb

5.times do
  user = User.create({name: Faker::Name.name})
  user.posts.create({title: Faker::Book.title, body: Faker::Lorem.sentence})
end

最後に、データを読み込みます:

rails db:seed

JSONで応答する

もちろん、APIを作成するためにいくつかのルートとコントローラーが必要です。 api/の下にAPIのルートをネストするのが一般的な方法です 道。また、開発者は通常、パスにAPIのバージョンを提供します(例:api/v1/)。 。後で、いくつかの重大な変更を導入する必要がある場合は、新しい名前空間(v2)を作成するだけです。 )と別のコントローラー。

ルートは次のようになります。

config / routers.rb

namespace 'api' do
    namespace 'v1' do
      resources :posts
      resources :users
    end
end

これにより、次のようなルートが生成されます:

api_v1_posts GET    /api/v1/posts(.:format)     api/v1/posts#index
             POST   /api/v1/posts(.:format)     api/v1/posts#create
 api_v1_post GET    /api/v1/posts/:id(.:format) api/v1/posts#show

scopeを使用できます namespaceの代わりにメソッド 、ただし、デフォルトではUsersControllerを検索します およびPostsController コントローラーの内部 controllers / api / v1内ではなくディレクトリ 、注意してください。

apiを作成します ネストされたディレクトリを持つフォルダv1 コントローラーの内部 。コントローラを追加します:

controllers / api / v1 / users_controller.rb

module Api
    module V1
        class UsersController < ApplicationController
        end
    end
end

controllers / api / v1 / posts_controller.rb

module Api
    module V1
        class PostsController < ApplicationController
        end
    end
end

api / v1の下にコントローラーのファイルをネストする必要があるだけではないことに注意してください。 パスですが、クラス自体もApi内で名前空間を設定する必要があります およびV1 モジュール。

次の質問は、JSON形式のデータで適切に応答する方法です。この記事では、jBuilderとactive_model_serializersのgemというソリューションを試してみます。したがって、次のセクションに進む前に、それらを Gemfileにドロップしてください。 :

ジェムファイル

gem 'jbuilder', '~> 2.5'
gem 'active_model_serializers', '~> 0.10.0'

次に実行します:

bundle install

jBuilderGemの使用

jBuilderは、Railsチームによって維持されている人気のあるgemであり、ビューでJSON構造を定義できるシンプルなDSL(ドメイン固有言語)を提供します。

ユーザーがindexを押したときに、すべての投稿を表示したいとします。 アクション:

controllers / api / v1 / posts_controller.rb

 def index
    @posts = Post.order('created_at DESC')
end

.json.jbuilder を使用して、対応するアクションにちなんで名付けられたビューを作成するだけです。 拡大。ビューはapi/ v1の下に配置する必要があることに注意してください パスも:

views / api / v1 / posts / index.json.jbuilder

json.array! @posts do |post|
  json.id post.id
  json.title post.title
  json.body post.body
end

json.array! @postsをトラバースします 配列。 json.idjson.title およびjson.body 引数を値として設定する対応する名前でキーを生成します。 http:// localhost:3000 / api / v1 / posts.jsonに移動すると、次のような出力が表示されます。

[
    {"id": 1, "title": "Title 1", "body": "Body 1"},
    {"id": 2, "title": "Title 2", "body": "Body 2"}
]

各投稿の作成者も表示したい場合はどうなりますか?簡単です:

json.array! @posts do |post|
  json.id post.id
  json.title post.title
  json.body post.body
  json.user do
    json.id post.user.id
    json.name post.user.name
  end
end

出力は次のように変わります:

[
    {"id": 1, "title": "Title 1", "body": "Body 1", "user": {"id": 1, "name": "Username"}}
]

.jbuilderの内容 ファイルはプレーンなRubyコードであるため、通常どおりすべての基本操作を利用できます。

jBuilderは、通常のRailsビューと同じようにパーシャルをサポートしているため、次のように言うこともできます。

json.partial! partial: 'posts/post', collection: @posts, as: :post

次に、 views / api / v1 / posts / _post.json.jbuilderを作成します 次の内容のファイル:

json.id post.id
json.title post.title
json.body post.body
json.user do
    json.id post.user.id
    json.name post.user.name
end

ご覧のとおり、jBuilderは簡単で便利です。ただし、別の方法として、シリアライザーを使用することもできるので、次のセクションでそれらについて説明しましょう。

シリアライザーの使用

rails_model_serializers gemは、最初にrails-apiを管理したチームによって作成されました。ドキュメントに記載されているように、rails_model_serializersは、設定より規約をJSON生成にもたらします。基本的に、シリアル化(つまり、JSON生成)時に使用するフィールドを定義します。

これが最初のシリアライザーです:

serializers / post_serializer.rb

class PostSerializer < ActiveModel::Serializer
  attributes :id, :title, :body
end

ここでは、これらすべてのフィールドが結果のJSONに存在する必要があると言います。現在、to_jsonのようなメソッド およびas_json 投稿時に呼び出されると、この構成が使用され、適切なコンテンツが返されます。

実際の動作を確認するには、indexを変更します このようなアクション:

controllers / api / v1 / posts_controller.rb

def index
    @posts = Post.order('created_at DESC')
    
    render json: @posts
end

as_json @postsで自動的に呼び出されます オブジェクト。

ユーザーはどうですか?シリアライザーを使用すると、モデルと同じように関係を示すことができます。さらに、シリアライザーはネストできます:

serializers / post_serializer.rb

class PostSerializer < ActiveModel::Serializer
  attributes :id, :title, :body
  belongs_to :user

  class UserSerializer < ActiveModel::Serializer
    attributes :id, :name
  end
end

これで、投稿をシリアル化すると、ネストされたuserが自動的に含まれます。 IDと名前を持つキー。後で:idを使用して、ユーザー用に別のシリアライザーを作成する場合 除外される属性:

serializers / post_serializer.rb

class UserSerializer < ActiveModel::Serializer
    attributes :name
end

次に、@user.as_json ユーザーのIDを返しません。それでも、@post.as_json ユーザーの名前とIDの両方が返されるので、注意してください。

APIの保護

多くの場合、APIを使用してアクションを実行するだけでは不十分です。それでは、簡単なセキュリティチェックを提示し、投稿を作成および削除するときにユーザーにトークンを送信するように強制しましょう。

トークンの有効期間は無制限で、ユーザーの登録時に作成されます。まず、新しいtokenを追加します usersへの列 テーブル:

rails g migration add_token_to_users token:string:index

同じトークンを持つ2人のユーザーは存在できないため、このインデックスは一意性を保証する必要があります。

db / migrate / xyz_add_token_to_users.rb

add_index :users, :token, unique: true

移行を適用します:

rails db:migrate

次に、before_saveを追加します コールバック:

models / user.rb

before_create -> {self.token = generate_token}

generate_token プライベートメソッドは、無限のサイクルでトークンを作成し、それが一意であるかどうかを確認します。一意のトークンが見つかったらすぐに返します:

models / user.rb

private

def generate_token
    loop do
      token = SecureRandom.hex
      return token unless User.exists?({token: token})
    end
end

別のアルゴリズムを使用して、たとえばユーザー名のMD5ハッシュとソルトに基づいてトークンを生成できます。

ユーザー登録

もちろん、ユーザーがトークンを取得できないため、ユーザーが登録できるようにする必要もあります。アプリケーションにHTMLビューを導入したくないので、代わりに新しいAPIメソッドを追加しましょう:

controllers / api / v1 / users_controller.rb

def create
    @user = User.new(user_params)
    if @user.save
      render status: :created
    else
      render json: @user.errors, status: :unprocessable_entity
    end
end

private

def user_params
    params.require(:user).permit(:name)
end

開発者が何が起こっているのかを正確に理解できるように、意味のあるHTTPステータスコードを返すことをお勧めします。これで、ユーザーに新しいシリアライザーを提供するか、 .json.jbuilderを使用することができます。 ファイル。私は後者のバリアントを好みます(そのため、:jsonを渡しません renderのオプション 方法)、しかしあなたはそれらのどれでも自由に選ぶことができます。ただし、トークンはあってはならないことに注意してください。 たとえば、すべてのユーザーのリストを返す場合は、常にシリアル化されます。安全に保管する必要があります。

views / api / v1 / users / create.json.jbuilder

json.id @user.id
json.name @user.name
json.token @user.token

次のステップは、すべてが正しく機能しているかどうかをテストすることです。 curlを使用することもできます コマンドを実行するか、Rubyコードを記述します。この記事はRubyに関するものなので、コーディングオプションを使用します。

ユーザー登録のテスト

HTTPリクエストを実行するには、Faraday gemを使用します。これは、多くのアダプターに共通のインターフェースを提供します(デフォルトはNet::HTTPです)。 )。別のRubyファイルを作成し、Faradayを含めて、クライアントをセットアップします。

api_client.rb

require 'faraday'

client = Faraday.new(url: 'https://localhost:3000') do |config|
  config.adapter  Faraday.default_adapter
end

response = client.post do |req|
  req.url '/api/v1/users'
  req.headers['Content-Type'] = 'application/json'
  req.body = '{ "user": {"name": "test user"} }'
end

これらのオプションはすべて自明です。デフォルトのアダプターを選択し、リクエストURLをhttp:// localhost:300 / api / v1 / usersに設定し、コンテンツタイプをapplication/jsonに変更します。 、リクエストの本文を提供してください。

サーバーの応答にはJSONが含まれるため、解析するにはOjgemを使用します。

api_client.rb

require 'oj'

# client here...

puts Oj.load(response.body)
puts response.status

解析された応答とは別に、デバッグ目的でステータスコードも表示します。

これで、次のスクリプトを実行できます:

ruby api_client.rb

受け取ったトークンをどこかに保存します。次のセクションで使用します。

トークンによる認証

トークン認証を実施するには、authenticate_or_request_with_http_token メソッドを使用できます。これはActionController::HttpAuthentication ::Token ::ControllerMethodsモジュールの一部であるため、忘れずに含めてください:

controllers / api / v1 / posts_controller.rb

class PostsController < ApplicationController
    include ActionController::HttpAuthentication::Token::ControllerMethods
    # ...
end

新しいbefore_actionを追加します および対応する方法:

controllers / api / v1 / posts_controller.rb

before_action :authenticate, only: [:create, :destroy]

# ...

private

# ...

def authenticate
    authenticate_or_request_with_http_token do |token, options|
      @user = User.find_by(token: token)
    end
end

これで、トークンが設定されていない場合、またはそのようなトークンを持つユーザーが見つからない場合、401エラーが返され、アクションの実行が停止します。

クライアントとサーバー間の通信はHTTPSを介して行われる必要があることに注意してください。そうしないと、トークンが簡単にスプーフィングされる可能性があります。もちろん、提供されているソリューションは理想的ではなく、多くの場合、認証にOAuth2プロトコルを使用することをお勧めします。この機能をサポートするプロセスを大幅に簡素化する少なくとも2つのgemがあります。DoorkeeperとoPROです。

投稿の作成

認証の動作を確認するには、createを追加します PostsControllerへのアクション :

controllers / api / v1 / posts_controller.rb

def create
    @post = @user.posts.new(post_params)
    if @post.save
        render json: @post, status: :created
    else
        render json: @post.errors, status: :unprocessable_entity
    end
end

ここではシリアライザーを利用して、適切なJSONを表示します。 @user before_action内にすでに設定されています 。

次に、この単純なコードを使用してすべてをテストします。

api_client.rb

client = Faraday.new(url: 'https://localhost:3000') do |config|
  config.adapter  Faraday.default_adapter
  config.token_auth('127a74dbec6f156401b236d6cb32db0d')
end

response = client.post do |req|
  req.url '/api/v1/posts'
  req.headers['Content-Type'] = 'application/json'
  req.body = '{ "post": {"title": "Title", "body": "Text"} }'
end

token_authに渡された引数を置き換えます 登録時に受け取ったトークンを使用して、スクリプトを実行します。

ruby api_client.rb

投稿の削除

投稿の削除も同様に行われます。 destroyを追加します アクション:

controllers / api / v1 / posts_controller.rb

def destroy
    @post = @user.posts.find_by(params[:id])
    if @post
      @post.destroy
    else
      render json: {post: "not found"}, status: :not_found
    end
end

ユーザーが実際に所有している投稿のみを破棄することを許可します。投稿が正常に削除されると、204ステータスコード(コンテンツなし)が返されます。または、メモリから引き続き利用できるため、削除された投稿のIDで返信することもできます。

この新機能をテストするためのコードは次のとおりです。

api_client.rb

response = client.delete do |req|
  req.url '/api/v1/posts/6'
  req.headers['Content-Type'] = 'application/json'
end

投稿のIDを自分に合った番号に置き換えてください。

CORSの設定

他のWebサービスが(クライアント側から)APIにアクセスできるようにする場合は、CORS(クロスオリジンリソースシェアリング)を適切に設定する必要があります。基本的に、CORSを使用すると、WebアプリケーションがAJAXリクエストをサードパーティのサービスに送信できます。幸いなことに、すべてを簡単にセットアップできるラックコアと呼ばれる宝石があります。 Gemfileに追加します :

ジェムファイル

gem 'rack-cors'

インストールする:

bundle install

次に、 config / initializers / cors.rb内に構成を提供します ファイル。実際、このファイルはすでに作成されており、使用例が含まれています。宝石のページにもかなり詳細なドキュメントがあります。

たとえば、次の構成では、誰でも任意の方法を使用してAPIにアクセスできます。

config / initializers / cors.rb

Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins '*'

    resource '/api/*',
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end

不正使用の防止

このガイドで最後に言及するのは、APIを悪用やサービス拒否攻撃から保護する方法です。ラックアタック(Kickstarterのユーザーによって作成された)と呼ばれる優れた宝石があり、クライアントをブラックリストまたはホワイトリストに登録したり、リクエストによるサーバーのフラッディングを防止したりできます。

宝石をGemfileにドロップします :

ジェムファイル

gem 'rack-attack'

インストールする:

bundle install

次に、 rack_attack.rb内に構成を提供します イニシャライザファイル。 gemのドキュメントには、利用可能なすべてのオプションがリストされており、いくつかのユースケースが提案されています。これは、あなた以外の人がサービスにアクセスすることを制限し、リクエストの最大数を1秒あたり5つに制限するサンプル構成です。

config / initializers / rack_attack.rb

class Rack::Attack
  safelist('allow from localhost') do |req|
    # Requests are allowed if the return value is truthy
    '127.0.0.1' == req.ip || '::1' == req.ip
  end

  throttle('req/ip', :limit => 5, :period => 1.second) do |req|
    req.ip
  end
end

実行する必要があるもう1つのことは、ミドルウェアとしてRackAttackを含めることです。

config / application.rb

config.middleware.use Rack::Attack

結論

この記事は終わりです。うまくいけば、これまでにRailsを使用したAPIの作成に自信が持てるようになりました。利用できるオプションはこれだけではないことに注意してください。かなり前から存在していたもう1つの一般的なソリューションは、Grapeフレームワークであるため、これもチェックしてみてください。

不明な点がある場合は、遠慮なく質問を投稿してください。私と一緒にいてくれてありがとう、そして幸せなコーディング!


  1. Rails5でのAngularの使用

    あなたは前にその話を聞いたことがあります。分散型で完全に機能するバックエンドAPIと、通常のツールセットで作成されたフロントエンドで実行されているアプリケーションがすでにあります。 次に、Angularに移動します。または、AngularをRailsプロジェクトと統合する方法を探しているだけかもしれません。これは、この方法を好むためです。私たちはあなたを責めません。 このようなアプローチを使用すると、両方の世界を活用して、たとえばRailsとAngularのどちらの機能を使用してフォーマットするかを決定できます。 構築するもの 心配する必要はありません。このチュートリアルは、この目的のた

  2. パスワードを使用してLinuxユーザーを作成する

    Linuxを作成する必要がある場合があります バッチモード(完全自動)のユーザーアカウントですが、多くの場合、初心者はパスワードの設定方法を尋ねます。 手動で入力せずに新しいユーザーの場合。天国のコマンドに感謝しますuseradd 入力パラメータとしてパスワードを取得できますが、暗号化する必要があります。 つまり、パスワードを使用してLinuxユーザーアカウントを作成するには、次のコマンドが役立ちます。 useradd -m -pencryptedPass ユーザー名 パスワードを暗号化する方法を少なくとも2つ知っています。 1つ目は、 perlを使用することです。 cry