2番煎じどころか100番煎じくらいだと思いますが、Ruby on Rails+Deviseで認証機能付きTodoアプリを作ってみました。
今回やることは以下です。
- Railsアプリのプロジェクトを作成
- Deviseインストール&設定
- 各種モデル作成+マイグレーション
- ルーティング設定
- コントローラ作成
- メール送信設定
- Herokuアプリ作成+設定+デプロイ
Railsアプリのプロジェクト作成
雛形となるプロジェクトを以下のコマンドで作成(rails newはジェネレータなコマンド)$ rails new {APP_NAME} --skip-bundle
$ cd {APP_NAME}
Railsにユーザ認証機能を追加してくれるDevise、環境変数を良い感じに操作できるDotEnv、PostgreSQL用のライブラリpgをGemfileに追加してインストール
group :development, :test do
gem 'dotenv-rails'
gem 'sqlite3' # development,testだけで利用されるように変更
end
gem 'devise'
gem 'pg'
パス指定してインストール(システムのgemに影響を与えない)
$ bundle install --path=vendor/bundle
Deviseのインストール&設定
Devise用のファイルをジェネレータで作成$ rails g devise:install
表示される注記の通りに変更する
config/environments/development.rbは以下を追加。アクティベーションのリンク等、認証系のメールに記載されるURLを設定しています。
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
ホーム画面のコントローラ(home)+メソッド(index)を作成してルーティングを追加
$ rails g controller home index
config/routes.rbに以下を追記
root to: "home#index"
全ビューに適用されるテンプレートに、flash messageを追加
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>
Devise用のビューをカスタマイズするために、Deviseのviewファイルをジェネレータで作成
$ rails g devise:views
Devise用のユーザモデルを作成
$ rails g devise User
Deviseの機能をフルで使うので、コメントアウトを解除
class DeviseCreateUsers < ActiveRecord::Migration[5.0]
def change
create_table :users do |t|
## Database authenticatable
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
t.integer :sign_in_count, default: 0, null: false
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string :current_sign_in_ip
t.string :last_sign_in_ip
## Confirmable
t.string :confirmation_token
t.datetime :confirmed_at
t.datetime :confirmation_sent_at
t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
t.string :unlock_token # Only if unlock strategy is :email or :both
t.datetime :locked_at
t.timestamps null: false
end
add_index :users, :email, unique: true
add_index :users, :reset_password_token, unique: true
add_index :users, :confirmation_token, unique: true
add_index :users, :unlock_token, unique: true
end
end
とりあえず、database.ymlはsqlite3のままでマイグレーション
$ bundle exec rake db:migrate
Userモデルのコメントアウトも解除しておく
class User < ApplicationRecord
# Include default devise modules. Others available are:
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:confirmable, :lockable, :timeoutable # , :omniauthable
end
sqlite3の確認はDB Browser for SQLiteを使うと良い
ルーティングの確認は以下のコマンドで
$ 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_password POST /users/password(.:format) devise/passwords#create
new_user_password GET /users/password/new(.:format) devise/passwords#new
edit_user_password GET /users/password/edit(.:format) devise/passwords#edit
PATCH /users/password(.:format) devise/passwords#update
PUT /users/password(.:format) devise/passwords#update
cancel_user_registration GET /users/cancel(.:format) devise/registrations#cancel
user_registration POST /users(.:format) devise/registrations#create
new_user_registration GET /users/sign_up(.:format) devise/registrations#new
edit_user_registration GET /users/edit(.:format) devise/registrations#edit
PATCH /users(.:format) devise/registrations#update
PUT /users(.:format) devise/registrations#update
DELETE /users(.:format) devise/registrations#destroy
user_confirmation POST /users/confirmation(.:format) devise/confirmations#create
new_user_confirmation GET /users/confirmation/new(.:format) devise/confirmations#new
GET /users/confirmation(.:format) devise/confirmations#show
user_unlock POST /users/unlock(.:format) devise/unlocks#create
new_user_unlock GET /users/unlock/new(.:format) devise/unlocks#new
GET /users/unlock(.:format) devise/unlocks#show
home_index GET /home/index(.:format) home#index
root GET / home#index
この時点でDevise部分は確認可能なので、一旦Appサーバ立ち上げて確認
$ rails s
http://localhost:3000/users/sign_upでサインアップできることを確認する
<% if user_signed_in? %>
<p>
<%= link_to 'ログアウト', destroy_user_session_path, method: :delete %> |
<%= link_to 'ユーザ情報変更', edit_user_registration_path %>
</p>
<p>Your are <%= current_user.email %></p>
<% else %>
<p>
<%= link_to 'ユーザ登録', new_user_registration_path %> |
<%= link_to 'ログイン', new_user_session_path %> |
<%= link_to 'パスワード変更', new_user_password_path %>
</p>
<% end %>
タスク用CRUD画面の作成
まずはモデルを作成$ rails g model task
マイグレーションファイルはこんな感じで。Userモデルと紐付けて管理するので、user_idをIntegerで定義しています。
class CreateTasks < ActiveRecord::Migration[5.0]
def change
create_table :tasks do |t|
t.string :title, null: false
t.text :description, null: true
t.datetime :target_at, null: true
t.datetime :completed_at, null: true
t.boolean :completed, null: false, default: false
t.integer :user_id, null: true
t.timestamps
end
end
end
項目定義したのち、マイグレーション
$ bundle exec rake db:migrate
Userモデルクラス、Taskモデルクラスにアソシエーションの設定をする
class User < ApplicationRecord
has_many :tasks, dependent: :destroy
# Include default devise modules. Others available are:
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:confirmable, :lockable, :timeoutable # , :omniauthable
end
Taskモデル↓
class Task < ApplicationRecord
belongs_to :user
end
次にタスクCRUD画面のコントローラ+メソッドを作成
$ rails g controller tasks index show new edit
中身はこんな感じで。ユーザに紐づくTaskを取得したり、before_actionでDeviseの認証ヘルパーを噛ませてます。
class TasksController < ApplicationController
before_action :authenticate_user!
def index
@tasks = current_user.tasks
end
def show
@task = target_task params[:id]
end
def new
@task = Task.new
end
def create
@task = current_user.tasks.new task_params
@task.save!
redirect_to @task
end
def edit
@task = target_task params[:id]
end
def update
@task = target_task params[:id]
@task.update(task_params)
redirect_to @task
end
def destroy
@task = target_task params[:id]
@task.destroy
redirect_to tasks_url
end
private
def target_task task_id
current_user.tasks.where(id: task_id).take
end
def task_params
params.require(:task).permit(:title, :description)
end
end
ビューはこんな感じで
app/views/tasks/index.html.erb
<h1>Tasks#index</h1>
<p>Find me in app/views/tasks/index.html.erb</p>
<table>
<tr>
<th>Id</th><th>Title</th><th>Description</th><th></th><th></th><th></th>
</tr>
<% @tasks.each do |task| %>
<tr>
<td><%= task.id %></td>
<td><%= task.title %></td>
<td><%= task.description %></td>
<td><%= link_to 'Show', task_path(task) %></td>
<td><%= link_to 'Edit', edit_task_path(task) %></td>
<td><%= link_to 'Delete', task_path(task), method: :delete %></td>
</tr>
<% end %>
</table>
<p><%= link_to 'ToDoの作成', new_task_path %></p>
app/views/tasks/edit.html.erb
<h1>Tasks#edit</h1>
<p>Find me in app/views/tasks/edit.html.erb</p>
<div style="width: 300px;">
<%= form_for(@task) do |f| %>
<div>
<%= f.label :title %>
<%= f.text_field :title, size: 50 %>
</div>
<div>
<%= f.label :description %>
<%= f.text_area :description %>
</div>
<%= f.submit 'Submit' %>
<% end %>
</div>
<p><%= link_to '一覧に戻る', tasks_url %></p>
app/views/tasks/new.html.erb
<h1>Tasks#new</h1>
<p>Find me in app/views/tasks/new.html.erb</p>
<div style="width: 300px;">
<%= form_for(@task) do |f| %>
<div>
<%= f.label :title %>
<%= f.text_field :title, size: 50 %>
</div>
<div>
<%= f.label :description %>
<%= f.text_area :description %>
</div>
<%= f.submit 'Submit' %>
<% end %>
</div>
<p><%= link_to '一覧に戻る', tasks_url %></p>
app/views/tasks/show.html.erb
<h1>Tasks#show</h1>
<p>Find me in app/views/tasks/show.html.erb</p>
<table>
<tr>
<th>Title</th>
<td><%= @task.title %></td></tr>
<tr>
<th>Description</th>
<td><%= @task.description %></td>
</tr>
<tr>
<th>User.Email</th>
<td><%= @task.user.email %></td>
</tr>
</table>
<p><%= link_to '一覧に戻る', tasks_url %></p>
config/routes.rbに以下を追加
resources :tasks
そうすると以下のようなルーティングが自動的に実装される
tasks GET /tasks(.:format) tasks#index
POST /tasks(.:format) tasks#create
new_task GET /tasks/new(.:format) tasks#new
edit_task GET /tasks/:id/edit(.:format) tasks#edit
task GET /tasks/:id(.:format) tasks#show
PATCH /tasks/:id(.:format) tasks#update
PUT /tasks/:id(.:format) tasks#update
DELETE /tasks/:id(.:format) tasks#destroy
本番環境用にconfig/database.ymlを設定(Herokuにデプロイする場合は、環境変数DATABASE_URLを見てくれるのでクレデンシャルの設定は環境変数に任せればOK)
...
production:
adapter: postgresql
encoding: unicode
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
Herokuのアプリ作成+設定
$ git init
$ echo "vendor" >> .gitignore
$ heroku login
$ heroku apps:create {HEROKU_APP_NAME}
$ heroku addons:create sendgrid:starter
$ heroku addons:create heroku-postgresql:hobby-dev
$ heroku config:set HEROKU_APP_DOMAIN={HEROKU_APP_NAME}.herokuapp.com
config/environments/production.rbに以下を追加
config.action_mailer.default_url_options = { :host => ENV['HEROKU_APP_DOMAIN']}
SendGridのメール設定
以下のコマンドでSendGridのHerokuの環境変数(=Addonで割り当てられたSendGridのクレデンシャル)を抽出する。
$ heroku config | grep SENDGRID
config/environment.rbに以下を追記
ActionMailer::Base.smtp_settings = {
:address => 'smtp.sendgrid.net',
:port => '587',
:authentication => :plain,
:user_name => ENV['SENDGRID_USERNAME'],
:password => ENV['SENDGRID_PASSWORD'],
:domain => 'heroku.com',
:enable_starttls_auto => true
}
ローカル開発でもSENDGRID使う場合は以下のように.envファイルをアプリケーションのルートに設置
SENDGRID_USERNAME={SENDGRID_USERNAME}
SENDGRID_PASSWORD={SENDGRID_PASSWORD}
Herokuにデプロイ
$ git add .
$ git commit -m "first commit"
$ git push heroku master # heroku apps:createの時点でremoteリポジトリに追加されている
$ heroku run rake db:migrate
ハマりの備忘
【事象】以下のsign_outのリンククリックでActionController::InvalidAuthenticityTokenのエラーになる<%= link_to "ログアウト", destroy_user_session_path, method: :delete %>
【原因】csrfのメタタグが入っていないだけだった(なぜか削除していた)
<%= csrf_meta_tag %>
method: :deleteを設定するとjQueryでAjax通信するが、そのなかでmeta[]要素を取得できずエラーになっていた