2016-09-20

初心者がRails+DeviseでTodoアプリを作ってみた【Herokuにデプロイするまで】

2番煎じどころか100番煎じくらいだと思いますが、Ruby on Rails+Deviseで認証機能付きTodoアプリを作ってみました。

今回やることは以下です。

Rubyのバージョンは2.2.4、Railsは5.0.0.1です。

Railsアプリのプロジェクト作成

雛形となるプロジェクトを以下のコマンドで作成(rails newはジェネレータなコマンド) ```bash $ rails new {APP_NAME} --skip-bundle $ cd {APP_NAME} ``` Railsにユーザ認証機能を追加してくれるDevise、環境変数を良い感じに操作できるDotEnv、PostgreSQL用のライブラリpgをGemfileに追加してインストール ```bash group :development, :test do gem 'dotenv-rails' gem 'sqlite3' # development,testだけで利用されるように変更 end gem 'devise' gem 'pg' ``` パス指定してインストール(システムのgemに影響を与えない) ```bash $ bundle install --path=vendor/bundle ```

Deviseのインストール&設定

Devise用のファイルをジェネレータで作成 ```bash $ rails g devise:install ``` 表示される注記の通りに変更する config/environments/development.rbは以下を追加。アクティベーションのリンク等、認証系のメールに記載されるURLを設定しています。 ```ruby config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } ``` ホーム画面のコントローラ(home)+メソッド(index)を作成してルーティングを追加 ```bash $ rails g controller home index ``` config/routes.rbに以下を追記 ```ruby root to: "home#index" ``` 全ビューに適用されるテンプレートに、flash messageを追加 ```erb

<%= notice %>

<%= alert %>

``` Devise用のビューをカスタマイズするために、Deviseのviewファイルをジェネレータで作成 ```bash $ rails g devise:views ``` Devise用のユーザモデルを作成 ```bash $ rails g devise User ``` Deviseの機能をフルで使うので、コメントアウトを解除 ```ruby 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のままでマイグレーション ```bash $ bundle exec rake db:migrate ``` Userモデルのコメントアウトも解除しておく ```ruby 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を使うと良い ルーティングの確認は以下のコマンドで ```bash $ rails routes ``` 出力こんな感じ。色々とできています。 ```text 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サーバ立ち上げて確認 ```bash $ rails s ``` http://localhost:3000/users/sign_upでサインアップできることを確認する ```erb <% if user_signed_in? %>

<%= link_to 'ログアウト', destroy_user_session_path, method: :delete %> | <%= link_to 'ユーザ情報変更', edit_user_registration_path %>

Your are <%= current_user.email %>

<% else %>

<%= link_to 'ユーザ登録', new_user_registration_path %> | <%= link_to 'ログイン', new_user_session_path %> | <%= link_to 'パスワード変更', new_user_password_path %>

<% end %> ```

タスク用CRUD画面の作成

まずはモデルを作成 ```bash $ rails g model task ``` マイグレーションファイルはこんな感じで。Userモデルと紐付けて管理するので、user_idをIntegerで定義しています。 ```ruby 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 ``` 項目定義したのち、マイグレーション ```bash $ bundle exec rake db:migrate ``` Userモデルクラス、Taskモデルクラスにアソシエーションの設定をする ```ruby 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モデル↓ ```ruby class Task < ApplicationRecord belongs_to :user end ``` 次にタスクCRUD画面のコントローラ+メソッドを作成 ```bash $ rails g controller tasks index show new edit ``` 中身はこんな感じで。ユーザに紐づくTaskを取得したり、before_actionでDeviseの認証ヘルパーを噛ませてます。 ```ruby 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 ```erb

Tasks#index

Find me in app/views/tasks/index.html.erb

<% @tasks.each do |task| %> <% end %>
IdTitleDescription
<%= task.id %> <%= task.title %> <%= task.description %> <%= link_to 'Show', task_path(task) %> <%= link_to 'Edit', edit_task_path(task) %> <%= link_to 'Delete', task_path(task), method: :delete %>

<%= link_to 'ToDoの作成', new_task_path %>

``` app/views/tasks/edit.html.erb ```erb

Tasks#edit

Find me in app/views/tasks/edit.html.erb

<%= form_for(@task) do |f| %>
<%= f.label :title %> <%= f.text_field :title, size: 50 %>
<%= f.label :description %> <%= f.text_area :description %>
<%= f.submit 'Submit' %> <% end %>

<%= link_to '一覧に戻る', tasks_url %>

``` app/views/tasks/new.html.erb ```erb

Tasks#new

Find me in app/views/tasks/new.html.erb

<%= form_for(@task) do |f| %>
<%= f.label :title %> <%= f.text_field :title, size: 50 %>
<%= f.label :description %> <%= f.text_area :description %>
<%= f.submit 'Submit' %> <% end %>

<%= link_to '一覧に戻る', tasks_url %>

``` app/views/tasks/show.html.erb ```erb

Tasks#show

Find me in app/views/tasks/show.html.erb

Title <%= @task.title %>
Description <%= @task.description %>
User.Email <%= @task.user.email %>

<%= link_to '一覧に戻る', tasks_url %>

``` config/routes.rbに以下を追加 ```ruby resources :tasks ``` そうすると以下のようなルーティングが自動的に実装される ```text 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) ```text ... 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のクレデンシャル)を抽出する。

```bash $ heroku config | grep SENDGRID ``` config/environment.rbに以下を追記 ```ruby 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ファイルをアプリケーションのルートに設置 ```text 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[]要素を取得できずエラーになっていた

このエントリーをはてなブックマークに追加