Dockerのコマンドが写経になってしまうので、各コマンドの意味を理解するために記事化。
Docker上でGemのインストール、DBマイグレート、ルーティングと、一通りRailsでWebアプリケーションを作成する上で必要になる操作に応用できるということで、deviseの導入までの手順を記述します。
※最近の現場はDockerで開発環境を作っていることが多いので、Dockerで一から開発環境作成まで出来た方が捗るかと。
※途中参加のプロジェクトが多いので、Docker自体は触ったことあるけど、最初から作るのはやったことがないな、という人向け(つまり自分需要です)。
開発環境
- Docker
- Rails 5.2.4.1
- Ruby 2.5.3
こちらの記事を参考にして、Railsプロジェクトが起動するところまでは持っていきます。
DockerでRuby on Railsの開発をしよう
Gemインストール
こちらの記事を参照しながら設定開始。
[Rails] deviseの使い方(rails5版)
1.Gemのインストール
Gemfileにdeviseの記述を追加したら、以下のコマンドを叩く。
$ docker-compose exec web bundle install
コマンドの意味を紐解くと、以下の通り。
docker-compose
各コンテナに対してコマンド発行する。詳しくは以下。
Docker Compose
exec web
対象のコンテナにコマンドを送る。
この場合「webコンテナに対し、コマンドを送る」という意味になる。
bundle install
実行されるコマンド
これでGemが入ります。
$ docker-compose build
2.deviseの設定
まずはdeviseをインストール。
$ docker-compose exec web rails g devise:install
「rails g devise:install」のコマンドをwebコンテナに送る。
以下のようなログが流れたら成功。
unning via Spring preloader in process 222
create config/initializers/devise.rb
create config/locales/devise.en.yml
===============================================================================
Some setup you must do manually if you haven't yet:
1. Ensure you have defined default url options in your environments files. Here
is an example of default_url_options appropriate for a development environment
in config/environments/development.rb:
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
In production, :host should be set to the actual host of your application.
2. Ensure you have defined root_url to *something* in your config/routes.rb.
For example:
root to: "home#index"
3. Ensure you have flash messages in app/views/layouts/application.html.erb.
For example:
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>
4. You can copy Devise views (for customization) to your app by running:
rails g devise:views
===============================================================================
3.Userモデル生成
deviseを使用するための画面を生成。
$ docker-compose exec web rails g devise:views
以下のようにログが流れて、生成されたViewが確認できる。
Running via Spring preloader in process 140
invoke Devise::Generators::SharedViewsGenerator
create app/views/devise/shared
create app/views/devise/shared/_error_messages.html.erb
create app/views/devise/shared/_links.html.erb
invoke form_for
create app/views/devise/confirmations
create app/views/devise/confirmations/new.html.erb
create app/views/devise/passwords
create app/views/devise/passwords/edit.html.erb
create app/views/devise/passwords/new.html.erb
create app/views/devise/registrations
create app/views/devise/registrations/edit.html.erb
create app/views/devise/registrations/new.html.erb
create app/views/devise/sessions
create app/views/devise/sessions/new.html.erb
create app/views/devise/unlocks
create app/views/devise/unlocks/new.html.erb
invoke erb
create app/views/devise/mailer
create app/views/devise/mailer/confirmation_instructions.html.erb
create app/views/devise/mailer/email_changed.html.erb
create app/views/devise/mailer/password_change.html.erb
create app/views/devise/mailer/reset_password_instructions.html.erb
create app/views/devise/mailer/unlock_instructions.html.erb
Deviseの画面は作ったので、rootの画面を生成する。
Authコントローラーを生成し、indexとshow画面を作成するためのコマンドを実行。
$ docker-compose exec web rails g controller Pages index show
画面が生成されたら、ログイン画面に相当するindexをrootに設定。
showはログイン後に遷移する画面となるので、こちらもルーティングに記述。
Rails.application.routes.draw do
root 'pages#index'
get 'pages/show'
end
余談ですが、rootは「page/index」のように記述すると以下のエラーが出て、Dockerが立ち上がらないです。
roller key on routes definition, please check your routes
また、ここまでで生成したルーティングを確認する場合のコマンドはこちら。
docker-compose exec web rails routes
ログイン画面と、ログイン後の画面を作ったので、application.html.erbに「サインインしていなければログイン画面に飛ばす」という設定を施します。
<!DOCTYPE html>
<html>
<head>
<title>App</title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>
<body>
<header>
<nav>
<% if user_signed_in? %>
<strong><%= link_to current_user.username, auth_show_path %></strong>
<%= link_to 'ログアウト', destroy_user_session_path, method: :delete %>
<% else %>
<%= link_to 'ログイン', new_user_session_path %>
<% end %>
</nav>
</header>
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>
<%= yield %>
</body>
</html>
次にユーザー管理をするUserモデルを生成。
$ docker-compose exec web rails g devise User
以下のようにログが流れる。
Running via Spring preloader in process 299
invoke active_record
create db/migrate/20200130213032_devise_create_users.rb
create app/models/user.rb
invoke test_unit
create test/models/user_test.rb
create test/fixtures/users.yml
insert app/models/user.rb
route devise_for :users
app/models/user.rbが生成されるので、ここを編集。
twitter認証を使いたいので、こんな感じ。
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable,
:omniauthable, omniauth_providers: [:twitter]
end
※認証はtwitter認証以外を通さない設定なので、omiauth以外のモジュールは消して良いと思うのですが、今回はこのまま。
次にTwitter認証で取得されるuidとusernameを保存するカラム、そして将来、他の認証を追加した時に「なんの認証を利用したのか」を保存するproviderカラムをusersテーブルに追加。
$ docker-compose exec web rails g migration add_columns_to_users provider uid username
マイグレーションファイルができるので確認。
class AddColumnsToUsers < ActiveRecord::Migration[5.2]
def change
add_column :users, :provider, :string
add_column :users, :uid, :string
add_column :users, :username, :string
end
end
追加するカラムは全て文字列で良いので、変更なし。
DBを変更するため、マイグレーションを実行。
$ docker-compose exec web rails db:migrate
Twitter認証を通す
TwitterのDeveloperセンターで、開発者の登録をして、アプリ作成を申請。
2017年くらいに一回やったことがあったんですが、登録がめっちゃ大変になってました。
手順についてはこちらの記事を参照。
Twitter API 登録 (アカウント申請方法) から承認されるまでの手順まとめ
トークンが発行されたら、コールバック先の登録を行います。
ルーティングを確認し、Twitterに設定。
$ docker-compose exec web rails routes
Prefix Verb URI Pattern Controller#Action
new_user_session GET /users/sign_in(.:format) devise/sessions#new
user_session POST /users/sign_in(.:format) devise/sessions#create
destroy_user_session DELETE /users/sign_out(.:format) devise/sessions#destroy
user_twitter_omniauth_authorize GET|POST /users/auth/twitter(.:format) omniauth_callbacks#passthru
user_twitter_omniauth_callback GET|POST /users/auth/twitter/callback(.:format) omniauth_callbacks#twitter
(略)
確認したルーティングをTwitterのコールバックに列挙します。
deviseのconfigにはAPIキーとコールバックを記述。
Devise.setup do |config|
(略)
config.omniauth :twitter, "API Key", "API secret key", "https://localhost:3000/users/auth/twitter/callback"
end
コールバック先を記述しておかないと、raise OAuth::Unauthorized, response 403
と怒られます。
コールバックの設定
Twitterからコールバックがきたら受け取るコントローラーの設定をします。
まずはコールバック用のコントーラー作成。
$ docker-compose exec web rails g controller omniauth_callbacks
作られたコントーラーに対して、コールバックメソッドを記述。
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
# Twitter認証
def twitter
@user = User.from_omniauth(request.env["omniauth.auth"].except("extra"))
if @user.persisted?
sign_in_and_redirect @user
else
session["devise.user_attributes"] = @user.attributes
redirect_to new_user_registration_url
end
end
end
ポイントはDevise::OmniauthCallbacksController
を継承すること。
継承することで、Deviseがコールバックで受け取った諸々を使うことができる。
User.from_omniauth(request.env["omniauth.auth"].except("extra"))
「Userモデルのfrom_omniauthに、request.envに入った値を引数で渡して処理してもらう」ということなので、userモデルに以下を実装。
class User < ApplicationRecord
(略)
def self.from_omniauth(auth)
find_or_create_by(provider: auth["provider"], uid: auth["uid"]) do |user|
user.provider = auth["provider"]
user.uid = auth["uid"]
user.username = auth["info"]["nickname"]
end
end
def self.new_with_session(params, session)
if session["devise.user_attributes"]
new(session["devise.user_attributes"]) do |user|
user.attributes = params
end
else
super
end
end
end
from_omniauth
では、providerとuidの組み合わせで、同一があればupdate、無ければinsertを実行する。