はじめに
Railsチュートリアル11章の内容を、少しでも理解の助けとなればと思い、割としっかり目に整理しました!
備忘録です。
前提
Railsチュートリアル1〜10章までの内容が完了していること。
内容
Railsチュートリアル11章のアカウント有効化機能の実装手順を前中後半の3回に分けて整理していきます。
前編である今回は、アカウント有効化機能を実装するのに必要となるリソースや、データモデルを作成していきます!
中編→アカウント有効化メール送信機能の実装
後編→アカウント有効化機能の実装
1.AccountACctivationsリソースを作成する
●セッション機能を使って、アカウントの有効化リソースを作成していく。
➡︎アカウント有効化リソースを作成するにあたり、Userモデルに必要なデータを追加していくことが必要となる。
➡︎注意しなければならない点が1つあって、アカウント有効化リソースはユーザーがアプリから受け取ったメールに貼られたリンクを単にクリックした際に発動する。よってこの場合、ユーザーが送信するリクエストはGETリクエストになるため、updateアクションではなくeditアクションを使う必要がある。
1-1.AccountActivationsコントローラの作成
●AccountActivationsリソースを作成するために、まずはAccountActivationsコントローラを生成していく。
➡︎$ rails g controller AccountActivations
●editアクションへアクセスするのに名前付きルートが必要となるため、ルーティングにアカウント有効化に使うリソースを追加する。
➡︎resources :account_activations, only: [:edit]
→︎GETリクエストを受け取り、editアクションへアクセスさせる。
→URLは「/account_activation//edit」を受け取る。
→名前付きルートはedit_account_activation_url(token)。
→_pathではなく、_urlを使うのは、メールからこのURLへアクセスするため。
congig/routes.rbRails.application.routes.draw do root 'static_pages#home' get '/help', to: 'static_pages#help' get '/about', to: 'static_pages#about' get '/contact', to: 'static_pages#contact' get '/signup', to: 'users#new' get '/login', to: 'sessions#new' post '/login', to: 'sessions#create' delete '/logout', to: 'sessions#destroy' resources :users #1-1.AccountActivationsリソース追加。 resources :account_activations, only: [:edit] end
1-2.AccountActivationのデータモデルを作成していく。
●前提として、有効化のメールには一意の有効化トークンが必要である。
●有効化トークン作成の方法としては、送信メールとDBの両方に同じ文字列を保存させる方法があるが、DBのデータが漏れた場合の被害が甚大であり危険。
➡︎例えば、第三者が新規登録したユーザーアカウントの有効化トークンを盗み、そのユーザーとしてログインしてしまうなどの危険がある。
●なので今回もパスワードや記憶トークンの実装で行なったように、仮想の属性とハッシュ化した文字列を使って有効化トークンを作成し、DBに保存されるようにする方法をとることにする。
➡︎まずは仮想の有効化トークン属性を追加するために、attr_accessor :actvation_tokenというコードを用意しておく。
➡︎次に、DBにactivated属性を追加して、データ型をbooleanとして論理値を取れるようにしたい。
➡︎user.activated?と記述した時に、ユーザーが有効化かどうかをテストできるようにするため。
➡︎上述した必要なデータモデルをDBへ追加ししたいので、マイグレーションを作成するコマンドを実行する。
→$ rails g migration add_activation_to_users ¥
> activation_digest:string activated:boolean activated_at:datetime
→activation_digest属性、データ型string。
→activated属性、データ型boolean。
→activated_at属性、データ型datetime(ユーザーを有効にした時の日時)。
➡︎db/migrate/_add_activation_to_users.rbファイルへ移動し、activated属性のデフォルト値をfalseに設定しておく。
→デフォルトではユーザーを有効化させないため。
➡︎コマンドを実行して、マイグレーションの変更をDBに保存。
●Activationトークンのコールバックを記述する。
➡︎ユーザーが新規登録を完了するには必ずアカウントの有効化が必要になる。
➡︎ということは、ユーザーオブジェクトが作成される前にはもう、有効化トークンと有効化ダイジェストが作成されていなければならない。
➡︎オブジェクトが"作成される直前"に何らかの処理を実行したい場合、Userモデル内でコールバックを使用しよう。
→例えばbefore_saveというコールバックを使用すれば、オブジェクトが"保存される直前"、すなわちオブジェクトが作成や更新される瞬間にコールバックが呼び出され、処理を実行してくれる。
➡今回は、ユーザーが"作成される直前"なので、before_createコールバックを使用する。
→コールバックの引数にメソッドを渡すことで、Railsはそのメソッドを呼び出し、ユーザーオブジェクトが作成される前にそのメソッドを実行する。
●有効化トークンと有効化ダイジェストを生成するメソッドの定義。(コールバックで使うやつ)
→メソッド名はcreate_activation_digestとする。
→ランダムな文字列を生成し、仮想の属性である有効化トークンへ代入する処理を記述する。
→その有効化トークンをハッシュ化して、有効化ダイジェストへ保存する処理を記述する。
(→永続セッション作成用に定義したrememberメソッドでは、記憶ダイジェストに値を保存する際update_attributeメソッドを使ったが、今回は何故update_attributeを使わないのかをここで説明する。)
(→create_activation_digestの場合は、ユーザーが作成される直前に呼び出されるので、更新するダイジェスト属性がそもそも存在しない。要するにユーザーが保存される際、ダイジェストの値も一緒に保存される。)
(→rememberの場合は、記憶トークンや記憶ダイジェストが、既にDBに存在するユーザーのために作成されるから。)
●Userモデルに、上記で示したアカウント有効化用のトークンを生成する処理を行うコードを記述する。
➡︎app/models/user.rbファイルヘ移動。
➡︎仮想の有効化トークン属性を、attr_accessorへ適用させたコードを記述。
→attr_accessor :activation_token
➡︎有効化トークンと有効化ダイジェストを作成するためのメソッドを記述。
→Userモデル内でしか使用しないので、外部に公開されないようprivateキーワード内で記述していく。
→上述した通り、メソッド名はcreate_activation_digestとする。
→ランダムな文字列を作成するUser.new_tokenメソッドを呼び出し、有効化トークンへ代入する処理を記述。
→上記の有効化トークンを有効化ダイジェストへ代入する処理を記述。
➡︎before_createコールバックに、create_activation_digestメソッドを適用させる記述を行う。
●ここで一旦、後々テストする際に必要となるサンプルデータとfixtureに変更を加え、事前にサンプルとユーザーを有効化しておく。
➡︎サンプルユーザーを有効化する。
→db/seeds.rbへ移動。
→それぞれのユーザーデータにactivated: trueと、activated_at: Time.zone.nowを追加。
➡︎fixtureのユーザーを有効化する。
→test/fixtures/users.ymlへ移動。
→それぞれのユーザーデータにactivated: trueと、activated_at: <%= Time.zone.now %>を追加。
➡︎変更したら、データベースを初期化して、サンプルデータを再度生成し直す。
→$rails db:migrate:reset
→$rails db:seed
ターミナル#1-2.マイグレーションの作成。 $ rails g migration add_activation_to_users ¥ activation_digest:string activated:boolean activated:datetime
db/migrate/[timestamp]_add_activation_to_users.rb#1-2.Usersモデルにデータを追加。 class AddActivationToUsers < ActiveRecord::Migration[5.0] def change add_column :users, :activation_digest, :string add_column :users, :activated, :boolean, default: false add_column :users, :activated_at, :datetime end end
ターミナル#1-2.マイグレーションの変更をDBに反映させる。 $ rails db:migrate
app/models/user.rbclass User < ApplicationRecord attr_accessor :remember_token, :activation_token before_save :downcase_email #コールバックにメソッドを渡す(追加事項)。 before_create :create_activation_digest . . . private #メアドを小文字化するメソッドを定義する(追加事項)。 def downcase_email self.email = email.downcase end #1-2.有効化トークンの作成し、作成した有効化トークンをハッシュ化しダイジェストへ保存。 def create_activation_digest self.activation_token = User.new_token self.activation_digest = User.digest(activation_token) end end
db/seed.rb#1-2.サンプルユーザーを有効化しておく。 User.create!(name: "Example User", . . . activated: true, activated_at: Time.zone.now) 99.times do |n| . . . User.create!(name: name, . . . activated: true, activated_at: Time.zone.now) end
test/fixtures/users.yml#fixtureのテスト用ユーザーを有効化しておく。 michael: . . . activated: true activated_at: <%= Time.zone.now %> archer: . . . activated: true activated_at: <%= Time.zone.now %> lana: . . . activated: true activated_at: <%= Time.zone.now %> malory: . . . activated: true activated_at: <%= Time.zone.now %> <% 30.times do |n| %> user_<%= n %>: . . . activated: true activated_at: <%= Time.zone.now %> <% end %>
最後に
前編のAccountActivationsリソースの作成はここまでになります。
中編→アカウント有効化用メール送信機能の実装
後編のアカウント有効化機能の実装