Rails 掲示板一覧の作成
モデルの作成
- Boardモデルの作成しtitle,bodyカラムを追加する。
- Boardモデルにuser_idを外部キーとして設定する。
紐づくモデル名+「_id」で外部キーと呼ぶ。
reference型を使いモデルと同時に作成。
$ rails g model board title:string body:text user:references invoke active_record create db/migrate/20210506055856_create_boards.rb create app/models/board.rb
マイグレーションファイルとモデルファイルができる。
UserモデルとBoardモデルにアソシエーションを設定し、バリデーションを追加。
class Board < ApplicationRecord validates :title, presence: true, length: { maximum: 200 } validates :body, presence: true, length: { maximum: 60_000 } belongs_to :user end
dependent: :destroyオプション
has_manyにdependent: :destroyを追加すると、親モデル(user)を削除した時に、その親モデルに紐づく子モデル(board)も同時に削除されるようになる。これを定義しないとUserを削除したときに、boardに投稿した内容が残ってしまうので必須である。
class User < ApplicationRecord authenticates_with_sorcery! validates :password, length: { minimum: 3 }, if: -> #... has_many :boards, dependent: :destroy end
データベースに未入力データの登録を排除するための NOT NULL 制約を追加。
class CreateBoards < ActiveRecord::Migration[5.2] def change create_table :boards do |t| t.string :title, null: false t.text :body, null: false t.references :user, foreign_key: true t.timestamps end end end
マイグレーションを実行。
$ rails db:migrate
gem Fakerで作成したダミーデータをデータベースへ投入する。
$ rails db:seed
掲示板一覧画面の作成
ルーティングを定義
Rails.application.routes.draw do root to: 'users#index' get '/login', to: 'user_sessions#new' post '/login', to: 'user_sessions#create' post '/logout', to: 'user_sessions#destroy' resources :users resources :boards end
コントローラを作成しindexアクションを定義
$ rails g controller boards create app/controllers/boards_controller.rb invoke erb create app/views/boards invoke decorator create app/decorators/board_decorator.rb
includesメソッドとN+1問題
N+1問題とは、データベースからデータを取得する際、必要以上にSQLが発行されてしまいパフォーマンスが低下してしまう問題である。アソシエーションが定義されている場合に発生する。
@boards = Board.allと定義した場合、掲示板一覧ページ遷移時(1回)にユーザーとその掲示板の数(N回)だけSQLが発行されてしまう。
以下のようになる
対策
includesメソッドで関連するテーブルをまとめて取得。
orderメソッドは昇順 ASC、降順 DESCの並び替えができる。
created_atは、作成された日時なので 降順で新しい投稿が上にくるようにできる。
descはdescendingの略で降順という意味。
class BoardsController < ApplicationController skip_before_action :require_login, only: %i[index create] def index if logged_in? @boards = Board.all.includes(:user).order(created_at: :desc) else redirect_to login_path, danger: 'ログインしてください' end end
viewの作成。
パーシャルを使用し掲示板一覧画面を表示させる。
1.eachを使う場合
<%= @boards .each do |board| %> <div class="col-sm-12 col-lg-4 mb-3"> <div id="board-id-<%= board.id %>"> <div class="card"> <%= image_tag 'board_placeholder.png', class: 'card-img-top', size: '300x200' %> #... <% end %>
パーシャルをindex.htmlで読み込む。
部分テンプレート内のboardという変数に@boardが代入される。localsオプションが省略されてる。
<%= render partial: 'board', locals: {board: @board} %>
*localsオプションを使用した場合、partialは省略できない。
#... <% if @boards.present? %> <%= render 'board', {board: @board} %> <%else%> <p><%= '掲示板がありません' %></p> <%end%>
2.eachを使わない場合
<div class="col-sm-12 col-lg-4 mb-3"> <div id="board-id-<%= board.id %>"> <div class="card"> <%= image_tag 'board_placeholder.png', class: 'card-img-top', size: '300x200' %> #...Railsでは、@boardsという変数からよしなに「_board.html.erb」を探してくれる。
@boardsにrenderメソッドを使うことで「_board.html.erb」をboardの数だけ繰り返し表示することができる。<%= render @boards %>は、collectionオプションが省略されている。
<%= render partial: 'board', collection: @boards %>
省略できる条件
- 呼び出す部分テンプレートがviewフォルダ内のboardsフォルダに存在する
- 部分テンプレート名が_board.html.erbである
- 部分テンプレート内で使う変数がboardである
#... <% if @boards.present? %> <%= render @boards %> <%else%> <p><%= '掲示板がありません' %></p> <%end%><%= render @boards %>
このコードは以下の処理とまったく同じである。
<%= render partial: 'board', collection: @boards %> <!--下のコードと全く同じ --> <% @boards.each do |board| %> <%= render partial: 'board', board: board %> <% end %>
タイムゾーンを日本時間にする。
config.time_zone = 'Tokyo' config.active_record.default_timezone = :local
Fakerを利用しダミーデータを作成
掲示板の一覧にダミーデータを投入する。
gem Fakerのインストール。
Fakerはdevelopmentとtest環境にインストールする。
group :development, :test do #... gem 'faker' #... end
bundle installで完了。
データベース上にダミーデータを生成
今回はBoardモデルのダミーデータを20個作る。 データは公式のREADMEからとってくる。
20.times do title = Faker::Games::Pokemon.name body = Faker::Games::Pokemon.move user = User.offset(rand(User.count)).first Board.create!( title: title, body: body, user: user ) end
seedに書いた内容をデータベースへ反映。
$ rails db:seed
データが作成されたか確認。
なぜcreate!を使うのか。
createとcreate!の違い
createの場合はユーザーが無効なときfalseを返す。一方、create!は例外を発生させる(例外が発生した時点で処理が止まる)。よって、デバッグが安易になるのでcreate!を使用したほうがいい。
user = User.offset(rand(User.count)).first
について
- モデル.offset(取得開始位置) 指定位置からレコードを取得。
- rand 引数に整数を渡したら0以上指定した整数未満の整数を返す。
- first 最初のレコードを取得。
20個のダミーデータから0〜19個間でランダムに数字を生成して、最初のレコードを取得している。 User.offset(18).firstなら18以降から最初のUserを取得する。
Decoratorの導入
Decoratorについて
Decoratorとはソフトウェアのデザインパターンのひとつであり、モデルとviewの間に追加する。
「モデル→Decorator→view」のようにviewを装飾するメソッドを追加するときはモデルではなく、デコレーターへ追加する。
Decoratorを導入すればviewにロジックを記述しないことができる。そうすることで、
モデルにはデータベース関連の処理のみを記載し、モデルの肥大化を防ぐことができる。
gem Draper
railsでデコレーター層(プレゼンテーション層)を導入するにはDraperかActive_Decoratorを使用するのが一般的である。
今回はDraperを利用しデコレーター層を導入する。
導入
gem 'Draper' とGemfile記述しbundle installする。bundle後、
$ rails g draper:install create app/decorators/application_decorator.rb
を実行。これでインストールが完了しジェネレーターコマンドが使えるようになる。
UserモデルのDecoratorを生成する。「rails g decorator (モデル名)」
$ rails g decorator User create app/decorators/user_decorator.rb
appにdecoratorsディレクトリとuser_decorator.rbが作成される。
Decorator内にメソッドを追加
class UserDecorator < Draper::Decorator delegate_all def full_name "#{last_name} #{first_name}" end end
delegate_allという記述はUserモデルすべてのメソッドを呼び出せるようにし、UserDecoratorクラスでも使えるようにしている。
これがあることで、Userモデルが所有するメソッド(last_name,first_name)を定義したfull_nameメソッドで使うことができる。
view側でdecoratorメソッドを呼び出す。
<%= current_user.decorate.full_name %>
反映にはサーバーの再起動が必要。
Bootstrapでflashメッセージを実装
概要
以下のflashメッセージをbootstrapで表示させる。
- ログイン成功時・・・「ログインしました」
- ログイン失敗時・・・「ログインに失敗しました」
- ログアウト時・・・「ログアウトしました」
- ユーザー登録時・・・「ユーザー登録が完了しました」
- ユーザー登録失敗時・・・「ユーザー登録に失敗しました」
定義の追加
flashオブジェクトはハッシュ形式で保存されており、viewにメッセージを表示している。デフォルトではnoticeとalertキーしかない。bootstrapを使用する場合、下図のようなキーが使用できる。
これらのキーを使用するにはキーを許可する宣言をしなければならない。
application_controller.rbに以下の記述をする。
add_flash_types :success, :info, :warning, :danger
すると、success, :info, :warning, :dangerのキーが使用可能になる。
flashメッセージの表示をviewに追加
flashメッセージは複数のviewで使用するのでパーシャルを使う。
_flash_message.html.erbを作成しapplication_controllerで読み込む。
<% flash.each do |key, value| %> <div class="alert alert-<%= key %>"><%= value %></div> <% end %>
keyとvalueはコントローラーから指定する。
例1
redirect_back_or_to root_path, success: 'ログインしました'
「success」がkeyで「ログインしました」はvalueで取得される。
例2
flash.now[:danger] = 'ログインに失敗しました'
「danger」がkeyで「ログインに失敗しました」がvalue
flashとflash.nowの違い
flash[:danger]の場合は次のアクションまでメッセージを表示させる。 flash.now[:danger]とすると、次のアクションに遷移した時点でメッセージが消える。
パーシャルを読み込む
#... <body> <% if logged_in? %> <%= render 'shared/header'%> <% else %> <%= render 'shared/before_login_header'%> <% end %> <%= render 'shared/flash_message'%> <%= yield %> <%= render 'shared/footer'%> </body> </html>
ログイン・ログアウト時にflashメッセージを表示させる
ログイン成功、ログアウト時はsuccessキーで表示。 ログイン失敗時はdangerキーで表示させる。
class UserSessionsController < ApplicationController skip_before_action :require_login, only: %i[create new] def new; end def create @user = login(params[:email], params[:password]) if @user redirect_back_or_to root_path, success: 'ログインしました' else flash.now[:danger] = 'ログインに失敗しました' render :new end end def destroy logout redirect_to root_path, danger: 'ログアウトしました' end end
renderとredirect_toの違い
上記を例とすると、
ログインできた場合
users#createアクション→root_path(users#index)→index.html.erbが描画される。
ログインに失敗した場合
users#createアクション→new.html.erbが描画される。という処理が流れる。
この処理から、redirect_to は、view の表示には直接は関係なく、新たな HTTPリクエストが発行される。*GETのみ(下図参照)
renderはcreateアクションに指定した値を直接表示させることがわかる。
ユーザー登録時にflashメッセージを表示させる。
ユーザー登録に成功したときと失敗したときに、メッセージを表示させる。
#... def create @user = User.new(user_params) if @user.save redirect_to login_path, success: 'ユーザー登録が完了しました' else flash.now[:danger] = 'ユーザー登録に失敗しました' render :new end end #...
追記
ログイン前のヘッダーにあるログインボタンのhttpリクエストをgetにする。 ここがpostとかになっているとヘッダーのログインボタンからログインページにいったときにflashメッセージが表示されてしまう。
<%= link_to 'ログインする', login_path, method: :get, class: 'nav-link' %>
link_toメソッド
Railsでは主にviewファイルで使用され、引数を指定することでリンクを生成する。 <%= link_to 'リンクの文言', 'パス', class: 'クラス名', method: :HTTPリクエスト %>
methodでリンク先のルーティングを指定できる。
i18nによる日本語化
i18nを使用しユーザー登録画面とログイン画面を日本語化する。
i18n国際化(Internationalization)はRailsにおいて、言語の翻訳機能があるgemである。
実行環境
- Rails5.2.3
- Ruby2.6.4
gem 'rails-i18n'をGemfileに記述し、bundleする。
# rails5系なら以下を記述 gem 'rails-i18n', '~> 5.1' # rails4系なら以下を記述 gem 'rails-i18n', '~> 4.0'
config/application.rbにデフォルトの言語を日本語にする設定とロケールファイルを読み込むPATHを設定。
config.i18n.default_locale = :ja config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}').to_s]
config/locales以下にロケールファイルを作成する。
モデル名、モデル属性に関する日本語化はactiverecord/ja.ymlに記述。
ビューに関する日本語化はviews/ja.ymlに記述。
モデル側の日本語化
モデルは全て activerecord 以下に記述する。 models以下に日本語化するモデルを指定、attributes以下にはモデルの属性を指定する。
ユーザー登録画面 modelに関する翻訳ファイル
ja: activerecord: models: user: 'ユーザー' attributes: user: last_name: '姓' first_name: '名' email: 'メールアドレス' password: 'パスワード' password_confirmation: 'パスワード確認'
Modelに紐づくform_withのlabelはRailsがロケートファイルの翻訳を適応してくれる。 一方でform_withの引数にModelを渡してないときはモデルと紐付いていないので、label自身に翻訳を適応する記述をする必要がある。また、国際化の際はlazy lookup機能を使用する。
<%= form_with model:@user, local: true do |f| %> <div class="form-group"> <%= f.label :last_name %> <%= f.text_field :last_name, class: 'form-control', id: 'user_last_name' %> </div> <div class="form-group"> <%= f.label :first_name %> <%= f.text_field :first_name, class: 'form-control', id: 'user_first_name' %> </div> <div class="form-group"> <%= f.label :email %> <%= f.text_field :email, class: 'form-control', id: 'user_email' %> </div> <div class="form-group"> <%= f.label :password %> <%= f.password_field :password, class: 'form-control', id: 'user_password' %> </div> <div class="form-group"> <%= f.label :password_confirmation %> <%= f.password_field :password_confirmation, class: 'form-control', id: 'user_password_confirmation' %> </div> <%= f.submit (t 'defaults.register'), class: 'btn btn-primary'%> <%end%> <div class='text-center'> <%= link_to (t 'defaults.to_login_page'), login_path%>
view側の日本語化
ログイン画面 viewに関する翻訳ファイル
ja: defaults: login: 'ログイン' register: '登録' title: 'ユーザー登録' to_login_page: 'ログインページへ' user_sessions: new: Back: '戻る' title: 'ログイン' email: 'メールアドレス' password: 'パスワード' to_register_page: '登録ページへ'
ログイン画面はform_withの引数がurlでモデルと紐付いていないのでlabelに翻訳を適応させる記述をする。 Model.human_attribute_name(:カラム名)で属性名を取得する。
<%= link_to (t '.Back'), users_path %> <div class="container"> <div class="row"> <div class=" col-md-10 offset-md-1 col-lg-8 offset-lg-2"> <h1><%= t '.title' %></h1> <%= form_with url: login_path, local: true do |f| %> <div class="form-group"> <%= f.label :email, User.human_attribute_name(:email) %> <%= f.text_field :email, class: "form-control" %> </div> <div class="form-group"> <%= f.label :password, User.human_attribute_name(:password) %> <%= f.password_field :password, class:"form-control" %> </div> <div class="actions"> <%= f.submit (t 'defaults.login'), class: "btn btn-primary" %> </div> <% end %> <div class='text-center'> <%= link_to (t '.to_register_page'), new_user_path %>
gem sorceryを使用し、ユーザー登録、ログイン機能を実装
概要
sorceryとは、railsで作成されたアプリケーションに対し、ログイン機能を実装させるgem。
認証用gemであるdeviseと比較すると、sorceryはある程度のコードは自分で書かなければいけなく、deviseよりコードを書く量が多いがその分カスタマイズ性があり、エラーが発生したときなど自分で書いたコードなのでエラーの特定がしやすい。
導入
sorcery wikiを参考にし、gemの導入からユーザー登録、ログイン、ログアウト機能を実装する。
実行環境
まずは、Gemfileにgem 'sorcery'を追加し、bundle installする。
gem 'sorcery'
sorceryによって追加されたジェネレータを実行しrails db:migrateすると、 userモデルとマイグレーションファイルが作成され、rails db:migrateによりusersテーブルが作成される。
$ rails g sorcery:install create config/initializers/sorcery.rb generate model User --skip-migration invoke active_record create app/models/user.rb insert app/models/user.rb insert app/models/user.rb create db/migrate/20210429103839_sorcery_core.rb $rails db:migrate == 20210429103839 SorceryCore: migrating ====================================== -- create_table(:users) -> 0.0016s -- add_index(:users, :email, {:unique=>true}) -> 0.0006s == 20210429103839 SorceryCore: migrated (0.0024s) =============================
今回は、ユーザ登録に姓名、email、password、password confirmationを入力できるようにしたいので、データベースにlast_name、first_nameカラムをstring型で追加する。
$ rails g migration AddLastNameToUsers last_name:string #マイグレーションファイルを編集 def change add_column :users, :last_name, :string end rails db:migrate #first_nameも同様に作成する。
SQLで作成されているか確認。
sqlite> PRAGMA TABLE_INFO(users); 0|id|integer|1||1 1|email|varchar|1||0 2|crypted_password|varchar|0|NULL|0 3|salt|varchar|0|NULL|0 4|created_at|datetime|1||0 5|updated_at|datetime|1||0 6|last_name|varchar|0||0 7|first_name|varchar|0||0
ユーザーの作成
ジェネレータを使用し、usersコントローラとビューの作成。
$rails g scaffold_controller user email:string crypted_password:string salt:string create app/controllers/users_controller.rb invoke erb create app/views/users create app/views/users/index.html.erb create app/views/users/edit.html.erb create app/views/users/show.html.erb create app/views/users/new.html.erb create app/views/users/_form.html.erb
viewの作成
form_withについて
form_withとは、モデルオブジェクトを使ってhtmlのフォームを作成するためのヘルパーメソッドである。 form_withにはmodel:とurl:オプションがあり使い分ける必要がある。 入力された情報がデータベースへ保存しない(モデルとフォームが紐付かない)場合はurl:オプションを使う。
<%= form_with url: "パス" do |form| %> <% end %>
データベースへ保存するときは、model:オプションを使う。
<%= form_with model: モデルクラスのインスタンス do |form| %> <% end %>
今回は、新規登録内容をデータベースへ保存する必要があるのでmodel:オプションを使用する。form_withにmodel: @userという引数を渡すことで、モデルとフォームを対応づける。また、form_withはデフォルトでajax通信になるのでlocal: trueをつける。
<div class="container"> <div class="row"> <div class="col-md-10 offset-md-1 col-lg-8 offset-lg-2"> <h1>ユーザー登録</h1> <%= form_with model:@user, local: true do |f| %> <div class="form-group"> <%= f.label :last_name %> <%= f.text_field :last_name, class: 'form-control', id: 'user_last_name' %> </div> <div class="form-group"> <%= f.label :first_name %> <%= f.text_field :first_name, class: 'form-control', id: 'user_first_name' %> </div> <div class="form-group"> <%= f.label :email %> <%= f.text_field :email, class: 'form-control', id: 'user_email' %> </div> <div class="form-group"> <%= f.label :password %> <%= f.password_field :password, class: 'form-control', id: 'user_password' %> </div> <div class="form-group"> <%= f.label :password_confirmation %> <%= f.password_field :password_confirmation, class: 'form-control', id: 'user_password_confirmation' %> </div> <%= f.submit '登録する', class: 'btn btn-primary'%> <%end%> <div class='text-center'> <%= link_to 'ログインページへ', login_path%> </div> </div> </div> </div>
ログイン状態に応じてヘッダー表示を切り替える。
logged_in?メソッドを使用する。 ログイン前はログインボタン、ログイン後は一覧やログアウトボタンを表示するようになる。
#... <body> <% if logged_in? %> <%= render 'shared/header'%> <% else %> <%= render 'shared/before_login_header'%> <% end %> <%= yield %> <%= render 'shared/footer'%> </body> </html>
_form.html.erbにパスワードを保持する仮想フィールドを追加
<div class="field"> <%= form.label :password %><br /> <%= form.password_field :password %> </div> <div class="field"> <%= form.label :password_confirmation %><br /> <%= form.password_field :password_confirmation %> </div>
controllerの作成
ユーザー登録後はログイン画面へリダイレクトするようにし、ストロングパラメータでは、last_name,first_name,password,password_confirmationを受け取れるようにする。
ストロングパラメータについて
Rails4から導入された「Mass-assignment」という脆弱性へのセキュリティ対策である。テーブルに登録されるすべてのデータはストロングパラメータによる検証をクリアしなければ登録できなくなっている。例えば、@user =User.new(user_params)を(params[:user])に変更して実行すると「ActiveModel::ForbiddenAttributesError 」という例外が発生してしまう。user_paramsメソッドに記載されたコードは「paramsが:userというキーを持っていて、かつparams[:user]が、:email, :last_name, :first_name, :password, :password_confirmation, :crypted_password, :saltというキーを持つハッシュである」ことを確認し、登録できるカラムを特定し、それ以外のカラムへのアクセスを禁止している。よって、許可されたカラムしか登録、更新ができなくなる。
class UsersController < ApplicationController before_action :set_user, only: %i[show edit update destroy] skip_before_action :require_login, only: %i[new create index] #... def new @user = User.new #インスタンス変数にuser#newアクションからviewに渡したいデータを入れる end def create @user = User.new(user_params) #user#createアクションで@userにuser_paramsに入ったデータを代入。 if @user.save #データを保存 redirect_to login_path, notice: 'success' #login_path ヘルパーメソッドを使用。保存が成功したらログインページへ else render :new #保存が失敗したら登録画面へ end end #... private def set_user @user = User.find(params[:id]) end def user_params params.require(:user).permit(:email, :last_name, :first_name, :password, :password_confirmation, :crypted_password, :salt) end end
Modelの作成
モデルファイルにバリデーションを設定する。authenticates_with_sorcery!はuserモデルにsorceryによる認証機能をもたせている。 passwordとpassword_confirmationはuserモデルのカラムには存在しない。crypted_passwordカラムの仮想属性である。sorceryによってcrypted_passwordカラムの内容であると認識される。
class User < ApplicationRecord authenticates_with_sorcery! validates :password, length: { minimum: 3 }, if: -> { new_record? || changes[:crypted_password] } validates :password, confirmation: true, if: -> { new_record? || changes[:crypted_password] } #ここでcrypted_passwordとして認識される。 validates :password_confirmation, presence: true, if: -> { new_record? || changes[:crypted_password] } validates :email, presence: true, uniqueness: true validates :last_name, presence: true, length: { maximum: 250 } validates :first_name, presence: true, length: { maximum: 250 } end
ログイン、ログアウト機能の実装
ジェネレータでuser_sessions_controllerを作成する。
$ rails g controller UserSessions new create destroy create app/controllers/user_sessions_controller.rb invoke erb create app/views/user_sessions create app/views/user_sessions/new.html.erb create app/views/user_sessions/create.html.erb create app/views/user_sessions/destroy.html.erb
Routerを定義する。
ルーティングを以下のように定義。
Rails.application.routes.draw do root to: 'users#index' get '/login', to: 'user_sessions#new', as: :login post '/login', to: 'user_sessions#create' post '/logout', to: 'user_sessions#destroy', as: :logout resources :users end
controllerの作成
redirect_back_or_toメソッドはsorceryでフレンドリーフォーワーディングを実現するための機能である。 ログインが必要なページに未ログイン状態でアクセスした場合、ログイン画面に遷移させてログイン後はログインが必要なページへ飛ばす。
class UserSessionsController < ApplicationController skip_before_action :require_login, only: %i[new create] def new; end def create @user = login(params[:email], params[:password]) #emailによるユーザ検索、passwordの検証を行いUserレコードのid値をセッションに格納。 if @user redirect_back_or_to root_path, notice: 'Login successful!!!' #ログイン後はトップページへリダイレクト else flash.now[:alert] = 'Login failed' render action: 'new' end end def destroy logout redirect_to(root_path, notice: 'Logged out!') #ログアウト後トップページへリダイレクト end end
セッションviewの作成
ログインフォームはモデルと紐付かないので、form_withのオプションはurl:を使用する。フォーム送信先は/loginなのでlogin_pathとする。
<%= link_to 'Back', users_path %> <div class="container"> <div class="row"> <div class=" col-md-10 offset-md-1 col-lg-8 offset-lg-2"> <h1>ログイン</h1> <%= form_with url: login_path, local: true do |f| %> <div class="form-group"> <%= f.label :email %> <%= f.text_field :email, class: "form-control" %> </div> <div class="form-group"> <%= f.label :password %> <%= f.password_field :password, class:"form-control" %> </div> <div class="actions"> <%= f.submit "ログイン", class: "btn btn-primary" %> </div> <% end %> <div class='text-center'> <%= link_to '登録ページへ', new_user_path %> </div> </div> </div> </div>
これでユーザー登録、ログイン・ログアウト機能の完成。
テストバグ修正
環境
- rbenv local 2.6.6
- nodenv local 12.14.0
rails test:systemでテストを実行したところ
sign up success
login
logout
login faild not exist user
visit about pageができない
sign upは検証ツールで確かめると、viewからフォーム送信はできていることがわかる。
users_controller.rbのcreateアクションを確認しbinding.irbをいれたあとブラウザで再度パスワードを入力
def create @user = User.new(user_params) binding.irb if @user.save log_in @user flash[:success] = "成功" redirect_to @user else render :new end end
paramsで検証ツールでみたようにパスワードが送れてることが確認する。
irb(#<UsersController:0x00007fa315031c58>):001:0> params => <ActionController::Parameters {"authenticity_token"=>"dewo4jc5+wMJcaAOyaReiUi+UIiYyCTQe4rrq8Kg3HIYD03MBpptBSOXbOONtPckOTnX5d1SA/u6SZXhM5X9Og==", "user"=><ActionController::Parameters {"name"=>"meo", "email"=>"meo@gmail.com", "password"=>"1111", "password_confirmation"=>"1111"} permitted: false>, "commit"=>"Create my account", "controller"=>"users", "action"=>"create"} permitted: false>
@userが作れてるか確認するとpassword_digestがnilになっているので@user = User.new(user_params)が間違っている
irb(#<UsersController:0x00007fa315031c58>):002:0> @user => #<User id: nil, name: "meo", email: "meo@gmail.com", created_at: nil, updated_at: nil, password_digest: nil> irb(#<UsersController:0x00007fa315031c58>):003:0> @user = User.new(user_params) Unpermitted parameter: :password
パスワードが許可されてないとでるのでuser_paramsをさらに確認するとusers_controller.rbのuser_paramsアクションにpasswordが抜けていることがわかる。
passwordを追加。
def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) end
irbをexitで終了し再度ブラウザでPWを入力し@userを実行すると、[FILTERED]と表示されているので値がはいってることがわかる。
irb(#<UsersController:0x00007fa310d78510>):001:0> @user => #<User id: nil, name: "meo", email: "meo@gmail.com", created_at: nil, updated_at: nil, password_digest: [FILTERED]>
@user.valid?でバリデーションを通るか確認するとtrueになったのでうまくいっている。
irb(#<UsersController:0x00007fa3114c6bc8>):001:0> @user.valid? User Exists? (0.3ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = ? LIMIT ? [["email", "meo@gmail.com"], ["LIMIT", 1]] ↳ (irb):1:in `create' => true
exitで終了するとCompleted 500 Internal Server Error in 30608ms (ActiveRecord: 3.2ms | Allocations: 67204)NoMethodError (undefined method `log_in' for #<UsersController:0x00007fa3114c6bc8> Did you mean? login_url):というエラーが出る。
これは新規作成はできたがlog_inというメソッドがないエラーであり、エディターでlog_inで検索するとsessions_helper.rbに書かれている。 ヘルパーはclass UsersController < ApplicationControllerのどちらかで読み込まれてることが多い application_controller.rbを見ると何も書かれていないので include SessionsHelperを追加しヘルパーを読み込ませる。 再度ブラウザから入力し@userとuser.valid?とlog_inをirbで通ることを確認してexitするとログインした状態になる。
binding.rbを削除しサーバーを再起動 メールアドレスとパスワードを入力しログインできることを確認してテストを実行rails test:system sign up successとlogin logoutは解決した。
login faild not exist user
存在しないユーザーでログインすると失敗する
emailとパスワードを適当にいれてログインすると以下のエラーになる。
authenticateメソッドはユーザーを認証するときのメソッドである
authenticateメソッドは定義されてないとでるので調べるとnilとでてくる
>> user.authenticate(params[:session][:password]) NoMethodError: undefined method `authenticate' for nil:NilClass from /Users/owner/exam1/Exam_RailsBasic_01/app/controllers/sessions_controller.rb:6:in `create' >> user => nil >> user = User.find_by(email: params[:session][:email].downcase) => nil >> params[:session][:email].downcase => "admin@example.com"
find byメソッドは存在しない場合nilをかえす。nilに対してメソッドを実行しようとしているのでエラーになっている。 また、findメソッドの場合はactiverecord例外が発生して500エラーになる。 →ぼっち演算子を使う。 &.(ぼっち演算子)はレシーバーであるオブジェクトに対してあるメソッドを実行した時、そのオブジェクトがnilの場合、nilを返すことでエラーを出さなくする
>> user&.authenticate(params[:session][:password]) => nil
visit about page aboutページへいくとエラーになる
ルーティングエラーなのでrails routesで調べると
static_pages_about GET /static_pages/about(.:format)
static_pages#aboutが間違っている。
localhost:3000/static_pages/aboutへアクセスしてみると static_pages_controllerにaboutアクションがないとでるのでstatic_pages_controller.rbにaboutアクションを作る
class StaticPagesController < ApplicationController def top; end def about; end end ※中身のないメソッドについて def about; end def about end def about render :about end これらはすべて同じ書き方でrenderは省略されている。
aboutアクション作成後、再度アクセスするとaboutアクションはあるがそれに対応するテンプレートがないとでた。 viewsを調べたらファイル形式が違っていて認識されていないことがわかった。
owner@ownernoMacBook-Pro Exam_RailsBasic_01 % ls app/views/static_pages about.rb top.html.erb
about.rb→about.html.erbに修正。
あとはlocalhost:3000/static_pages/aboutからlocalhost:3000/aboutへルーティングを変更するだけ get 'static_pages/about’からget '/about', to: 'static_pages#about'へ変更
テストが通ることを確認。