Rails APIプロジェクトを新規作成し、API疎通確認してherokuにdeployまでの備忘録


構築するRails環境

  • Github
  • ruby 2.5.8
  • rails 5.1.6
  • apiモードで作成
  • local DBはsqlite, productionはpostgresqlを使用
  • global領域にrails等はインストールしたくない
  • herokuのsslに対応させる
  • herokuにdeploy

でき上がるrailsアプリができること

  • ユーザ作成
  • ログイン
  • ログインしてるユーザーはルアーの使い方を投稿できる(微妙にマニアックですみません)
  • ユーザーとルアーの使い方のデータは一対多

Githubでプロジェクト作成

  • GithubでRepository新規作成
  • git clone

環境を整える

  • git cloneしたディレクトリ配下に移動

  • rubyのバージョンを設定

rbenv local 2.5.8

  • rubyのバージョンを確認

rbenv version

  • bundle initでGemfileを作成

bundle init

  • Gemfileの編集 ここで今回使いたいrubyバージョンとrailsバージョンを指定
ruby '2.5.8'
gem 'rails', '5.1.6'
  • グローバルではなくプロジェクトの --path vendor/bundle 配下にrailsをinstall

bundle install --path vendor/bundle

[DEPRECATED] The--pathflag is deprecated because it relies on being remembered across bundler invocations, which bundler will no longer do in future versions. Instead please usebundle config set path 'vendor/bundle', and stop using this flag

globalに設定

bundle config set path 'vendor/bundle'

新規Rails Appの作成

  • プロジェクトディレクトリの--path vendor/bundle配下にrailsが入ったので、rails new でrails appを新規作成。
  • git repositoryと同じ名前のプロジェクト名にしたいときは rails new .
  • 今回はAPIモードで作りたいので --api オプション
  • -B オプションをつけて、このタイミングでは bundle install しない

bundle exec rails new . --api -B

MySQLではじめたい場合
bundle exec rails new . --api -B --database=mysql

  • Gemfileを上書きしていいか聞かれるので Y (yes)で上書き

  • Railsプロジェクトに必要なpumaとかGemfileが入る

  • 作成されたGemfileを編集

ruby '2.5.8'
gem 'rails', '5.1.6'
gem 'puma', '~> 3.7'

group :development, :test do
  gem 'sqlite3', '1.3.13'
  gem 'byebug', '9.0.6', platform: :mri
end

group :development do
  gem 'listen', '>= 3.0.5', '< 3.2'
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
end

group :production do
  gem 'pg', '0.20.0'
end

  • 適用したいGemfileがようやくできたので、このGemfileで bundle instal
  • macにpostgresqlをインストールしていない&localではsqliteを使うので、--without production オプションをつけて本番環境はローカルmacに構築されないようにする

bundle install --path vendor/bundle --without production

Rails Appの機能作成

  • scaffold, referenceは使わずにやってます。

  • user modelの作成

bundle exec rails g model user name:string password:string token:token

  • lure modelの作成

bundle exec rails g model lure name:string usage:string

  • DBの作成

bundle exec rails db:migrate

  • 一対多関係を作成

bundle exec rails g migration add_user_id_to_lures user_id:integer

bundle exec rails db:migrate

  • User Modelの編集
class User < ApplicationRecord
  has_secure_token
  has_many :lures, dependent: :destroy
end
  • Lure Modelの編集
class Lure < ApplicationRecord
  belongs_to :user
end
  • User Controllerの作成

bundle exec rails g controller users

  • Lure Controllerの作成

bundle exec rails g controller lures

  • routeを設定
  • ~~/api/v1/login みたいにして、APIバージョンが上がることを想定した作りにしておきたいのでrouteは以下のように
Rails.application.routes.draw do
  namespace 'api' do
    namespace 'v1' do
      resources :users
      resources :lure
    end
  end
end
  • UsersControllerの編集
  • この時、app -> controllers ディレクトリ配下にapiディレクトリを作成
  • さらにapiディレクトリ配下にv1ディレクトリを作成
  • UsersController LuresControllerファイルをapp/controllers/api/v1ディレクトリ配下に移動
  • class Api::V1::UsersControllerはAPIモジュール配下のv1モジュール配下のUsersControllerという意味(モジュール名は大文字始まり)
class Api::V1::UsersController < ApplicationController
  # 
  before_action :set_user, only: [:show, :edit, :update, :destroy]

  def index
    users = User.order(created_at: :desc)
    render json: { status: 'SUCCESS', message: 'loaded users', data: users }
  end

  def show
    user = User.find(params[:id])
    render json: { status: 'SUCCESS', message: 'loaded the user', data: user }
  end

  def create
    user = User.new(user_params)
    if user.save
      render json: { status: 'SUCCESS', message: 'User was successfully created.', data: user }
    else
      render json: { status: :unprocessable_entity, message: user.errors }
    end
  end

  def destroy
    user = User.find(params[:id])
    user.destroy
    render json: { status: 'SUCCESS', message: 'deleted the user', data: user }
  end

  def update
    user = User.find(params[:id])
    if user.update(user_params)
      render json: { status: 'SUCCESS', message: 'updated the user', data: user }
    else
      render json: { status: 'SUCCESS', message: 'loaded the user', data: user }
    end
  end

    private

    def set_user
      @user = User.find(params[:id])
    end

    def user_params
      params.require(:user).permit(:name, :password, :token)
    end
end
class Api::V1::LuresController < ApplicationController
  include ActionController::HttpAuthentication::Token::ControllerMethods
  # tokenある人だけ以下を実行できます
  before_action :authenticate

  def index
    lures = Lure.order(created_at: :desc)
    render json: { status: 'SUCCESS', message: 'loaded lures', data: lures }
  end

  def show
    lure = Lure.find(params[:id])
    render json: { status: 'SUCCESS', message: 'loaded the lure', data: lure }
  end

  def create
    lure = @auth_user.lures.build(lure_params)
    logger.debug("lure_params:::")
    logger.debug(lure_params)
    if lure.save
        render json: { status: 'SUCCESS', message: 'Lure was successfully created.', data: lure }
      else
        render json: { status: :unprocessable_entity, message: lure.errors }
      end
  end

  def destroy
    lure = Lure.find(params[:id])
    lure.destroy
    render json: { status: 'SUCCESS', message: 'deleted the lure', data: lure }
  end

  def update
    lure = Lure.find(params[:id])
    if lure.update(lure_params)
      render json: { status: 'SUCCESS', message: 'updated the lure', data: lure }
    else
      render json: { status: 'SUCCESS', message: 'loaded the lure', data: lure }
    end
  end

    private
    def authenticate
      authenticate_or_request_with_http_token do |token,options|
        @auth_user = User.find_by(token: token)
        @auth_user != nil ? true : false
      end
    end

    def lure_params
      params.require(:lure).permit(:name, :usage)
    end
end
  • rails 起動

bundle exec rails s

  • curlコマンドでuser作成

curl -X POST -H 'Content-Type:application/json' -d '{ "name": "bright", "password": "password" }' http://0.0.0.0:3000/api/v1/users

  • レスポンスが以下のようであればOK
{"status":"SUCCESS","message":"User was successfully created.","data":{"id":4,"name":"bright","password":"password","token":"UK789txu3ZsripoXnugPjVG3","created_at":"2019-06-13T11:41:23.017Z","updated_at":"2019-06-13T11:41:23.017Z"
  • ログインの作成
  • ログインはアクションがあればいいのでコントローラだけ作成
bundle exec rails g controller login login
  • routesの編集
Rails.application.routes.draw do
  get 'login/login'
  namespace 'api' do
    namespace 'v1' do
      resources :users
      resources :lure
      post 'login/login'
    end
  end
end
  • ログインコントローラの編集
class Api::V1::LoginController < ApplicationController
  def login
    login_user = User.find_by(name:params[:name], password:params[:password])
    if login_user != nil
      render json: { status: 'SUCCESS', message: 'user logged in', data: login_user.token }
    else
      render json: { status: 'FAILER', message: 'no auth' }
    end
  end
end
  • 作成したユーザでログインできるか疎通確認

curl -X POST -H 'Content-Type:application/json' -d '{ "name": "bright", "password": "password" }' http://0.0.0.0:3000/api/v1/login/login

  • トークンが返って来ればOK

  • 返ってきたtokenをAuthorization HEADERに加えて、ルアーを作成してみる

curl -X POST -H 'Authorization: Token UK789txu3ZsripoXnugPjVG3' -H 'Content-Type:application/json' -d '{ "name": "Spinnerbait", "usage": "it can be used as a fish finder, a deep water bait, a shallow water bait, a bait that covers a lot of area in a short time." }' http://0.0.0.0:3000/api/v1/lures

  • 以下のレスポンスが返って来ればOK
{"status":"SUCCESS","message":"Lure was successfully created.","data":{"id":1,"name":"Spinnerbait","usage":"it can be used as a fish finder, a deep water bait, a shallow water bait, a bait that covers a lot of area in a short time.","created_at":"2019-06-13T13:23:41.359Z","updated_at":"2019-06-13T13:23:41.359Z","user_id":4}}
  • ルアーをGETしてみる

curl -X GET -H 'Authorization: Token UK789txu3ZsripoXnugPjVG3' -H 'Content-Type:application/json' http://0.0.0.0:3000/api/v1/lures/1

{"status":"SUCCESS","message":"loaded the lure","data":{"id":1,"name":"Spinnerbait","usage":"it can be used as a fish finder, a deep water bait, a shallow water bait, a bait that covers a lot of area in a short time.","created_at":"2019-06-13T13:23:41.359Z","updated_at":"2019-06-13T13:23:41.359Z","user_id":4}}

herokuにdeploy

  • herokuにプロジェクト作成

heroku create

  • herokuにpush

git push heroku master

  • heroku環境でmigrate

heroku run rails db:migrate

curl -X POST -H 'Content-Type:application/json' -d '{ "name": "bright", "password": "password" }' https:// [herokuに発行されたAPP ID] .herokuapp.com/api/v1/users

{"status":"SUCCESS","message":"User was successfully created.","data":{"id":1,"name":"bright","password":"password","token":" [token]","created_at":"2019-06-13T13:42:44.413Z","updated_at":"2019-06-13T13:42:44.413Z"}}
  • ログインできるか確認

curl -X POST -H 'Content-Type:application/json' -d '{ "name": "bright", "password": "password" }' https://[herokuに発行されたAPP ID].herokuapp.com/api/v1/login/login

{"status":"SUCCESS","message":"user logged in","data":" [herokuに発行されたAPP ID]"}
  • token使ってルアーの使い方を作成

curl -X POST -H 'Authorization: Token [herokuに発行されたAPP ID]' -H 'Content-Type:application/json' -d '{ "name": "Spinnerbait", "usage": "it can be used as a fish finder, a deep water bait, a shallow water bait, a bait that covers a lot of area in a short time." }' https://[herokuに発行されたAPP ID].herokuapp.com/api/v1/lures

  • responseが以下であればOK
{"status":"SUCCESS","message":"Lure was successfully created.","data":{"id":1,"name":"Spinnerbait","usage":"it can be used as a fish finder, a deep water bait, a shallow water bait, a bait that covers a lot of area in a short time.","created_at":"2019-06-13T13:45:27.939Z","updated_at":"2019-06-13T13:45:27.939Z","user_id":1}}

参考

https://www.gitignore.io/api/rails
https://www.sejuku.net/blog/26617
https://qiita.com/ochiochi/##items/966b884eb17045dfb929