ユーザー登録
さていよいよ中盤の7章 ユーザー登録です。
この章はなかなか苦い思い出がある章で手順通りに作っているはずなのに
なぜかrails testを実行してもなぜかエラーがでる!!!と時間を要した章です。
チュートリアルやっている方は何かしらどこかでつまづくと思いますが
私も5カ所くらい大きくコケました。
というわけで、改めて本章を理解して、どんなエラーがきても対処できるようになりたいと思います。
では本編へ行きます。
本編
それでは本編を見ていきます。前回までにUserモデルを作成したのでユーザー登録機能を作りましょう
というのが7章のメインです。
ユーザーを表示する
ここでは、まずユーザーの名前とプロフィール写真を表示するためのページを作成とのこと。
ここでの完成モックアップを見ていると完成から逆算して分解化して構造化していかなければいけないと感じました。逆算思考が大事。
デバッグとRails環境
<!DOCTYPE html>
<html>
.
.
.
<body>
<%= render 'layouts/header' %>
<div class="container">
<%= yield %>
<%= render 'layouts/footer' %>
<%= debug(params) if Rails.env.development? %>
</div>
</body>
</html>
本編はじめての動的ページのためdebug
メソッドと``params`メソッドを挿入するようです。
これでプロフィールに各ページの情報が表示されるとのこと。
if Rails.env.development?
さらにこの記述をしておくと本番環境は避け、開発環境のみにdebug情報を表示する。
また開発が終わったら削除するべきコードのようです。
@import "bootstrap-sprockets";
@import "bootstrap";
.
.
.
/* miscellaneous */
.debug_dump {
clear: both;
float: left;
width: 100%;
margin-top: 45px;
}
デバッグ情報をみやすいように整えます。
#<ActionController::Parameters {"controller"=>"static_pages", "action"=>"home"}
permitted: false>
現時点ではこちらが表示されています。
7.1.2Usersリソース
ユーザープロフィールを表示するにや先にユーザーデータの登録が必要とのこと。
Rails.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"
resources :users
end
resources :users
を追加
RESTfulなUsersリソースで必要となるすべてのアクションが利用できるようになった。
route設定はしたが現在ページがない状態なので/users/1と入力してもユーザー情報はでてきません。
そんなわけで先ほどの画像をみてshowアクションである特定のユーザーを表示するページを
作成する必要があります。
<%= @user.name %>, <%= @user.email %>
仮ページです、@user変数から名前とEメールを取得して表示する感じですね。
ただ現状のページだと何も設定してないので
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
end
def new
end
end
user_controllerで@userの情報を探してidを取得する
取得したIDを特定ユーザーのページで表示するって感じですかね
なにはともあれこれで最低限の表示はできるようになりました。
route設定→ページビュー作成→モデル設定って感じですね。
7.1.3debuggerメソッド
debugメソッドで読み取った情報がアプリケーションで何が起きているのかをより直接的に取り出す方法
これエラー出た時にchatGPTにも提案されたのですが、チュートリアルの構築優先していたので
あんまり理解していなかったところです。
改めて理解していきます。
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
debugger
end
def new
end
end
このようにdebugger
をねじ込むんですね。
そうするとターミナルのrgbg
が立ち上がってるとのこと。
そしてrails consoleのようにコマンド入力ができるわけですね。
トラブルが起こってそうなコードで差し込むのがコツらしいので2週目構築の時に改めて
チャレンジしたいと思います。
7.2ユーザー登録フォーム
続いてユーザー登録フォーム作成です。
7.2.1form_withを使用する
ユーザー登録ページで重要な要素は、ユーザー登録に不可欠な情報を入力するフォームです。
Railsではform_withヘルパーメソッドを使い
Active Recordのオブジェクトを取り込み、そのオブジェクトの属性を使ってフォームを構築するとのこと
というわけで見ていきます。
form_withの引数で必要となるUserオブジェクトを作成する
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
end
def new
@user = User.new
end
end
ユーザー登録ページなのでusers_controllerにnewアクションを定義下って感じですね。
すでにrouteにはusers設定しており、userアクションまわりは使用できているので
次のコントローラーに設定したという感じで解釈してます。
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_with(model: @user) do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :email %>
<%= f.email_field :email %>
<%= f.label :password %>
<%= f.password_field :password %>
<%= f.label :password_confirmation, "Confirmation" %>
<%= f.password_field :password_confirmation %>
<%= f.submit "Create my account", class: "btn btn-primary" %>
<% end %>
</div>
</div>
そしてこれがViewページであるnewです。
パッと見た感じこのフォームに入力した内容をform_withで実行ということでしょうか。
@import "bootstrap-sprockets";
@import "bootstrap";
/* mixins, variables, etc. */
$gray-medium-light: #eaeaea;
@mixin box_sizing {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.
.
.
/* forms */
input, textarea {
border: 1px solid #bbb;
width: 100%;
margin-bottom: 15px;
@include box_sizing;
}
input {
height: auto !important;
}
custom.sccsで見た目を整えます。
7.3ユーザー登録失敗
無効なデータ送信を受け付けるユーザー登録フォームを作成し、
ユーザー登録フォームを更新してエラーの一覧も考える。
無効なデータが表示された際エラーメッセージとフォームがどうなっているのかを検討する必要があるってことですね。
7.3.1正しいフォーム
resources :users
をroutes.rb
ファイルに追加すると自動的にRailsアプリケーションがRESTful URIに応答するようになった。
/usersへのPOSTリクエストはcreateアクションに送られます。
ここでは、createアクションでフォーム送信を受け取り、User.newを使って新しいユーザーオブジェクトを作成し、ユーザーを保存(または保存に失敗)し、再送信できるようにユーザー登録ページを表示するという方法で機能を実装するとのこと。
つまり先ほど作ったフォームページに送信実行機能を実装するということですね。
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
end
def new
@user = User.new
end
def create
@user = User.new(params[:user]) # 実装は終わっていないことに注意!
if @user.save
# 保存の成功をここで扱う。
else
render 'new', status: :unprocessable_entity
end
end
end
ここでは@user.newでユーザー情報を作成し
成功したら保存
失敗したらstatus: :unprocessable_entity レスポンスを送るって感じでしょうか。
7.3.2Strong Parameters
@user = User.new(params[:user]) # 実装は終わっていないことに注意!
これは、ユーザーが送信したデータをまるごとUser.newに渡していることになる為
admin属性とかもあったら管理権限も渡してしまう為危険である。
Rails 4.0ではStrong Parametersというテクニックをコントローラ層で使うことが推奨されています。Strong Parametersでは、必須パラメータと許可済みパラメータを指定できます。さらに、上のようにparamsハッシュを丸ごと渡すとエラーが発生するので、Railsはデフォルトでマスアサインメントの脆弱性から守られるようになりました。
とのこと、params渡してもセキュリティは安心ですね。
paramsハッシュでは:user属性を必須とし、名前、メールアドレス、パスワード、パスワードの確認の属性をそれぞれ許可し、それ以外は許可しないようにしたいと思います。これを行うには、次のように記述します。
params.require(:user).permit(:name, :email, :password, :password_confirmation)
class UsersController < ApplicationController
.
.
.
def create
@user = User.new(user_params)
if @user.save
# 保存の成功をここで扱う。
else
render 'new', status: :unprocessable_entity
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
end
ここでprivateを使うことで外部から使えないようになるんですね。
初めて意味を実感しました。
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_with(model: @user) do |f| %>
<%= render 'shared/error_messages' %>
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
<%= f.label :email %>
<%= f.email_field :email, class: 'form-control' %>
<%= f.label :password %>
<%= f.password_field :password, class: 'form-control' %>
<%= f.label :password_confirmation, "Confirmation" %>
<%= f.password_field :password_confirmation, class: 'form-control' %>
<%= f.submit "Create my account", class: "btn btn-primary" %>
<% end %>
</div>
</div>
```app/views/shared/_error_messages.html.erb
ここでは、'shared/error_messages'というパーシャルをrender(描画)している点に注目してください。Rails全般の慣習として、複数のビューで使われるパーシャルは専用のディレクトリ「shared」によく置かれるtのこと。
<% if @user.errors.any? %>
-
<% @user.errors.full_messages.each do |msg| %>
- <%= msg %> <% end %>
続いてパーシャルを作成します。
そしてエラー検証用コードが紹介されてました。
・user.errors.count
: ユーザーの検証エラーの数を取得します。このメソッドを使用することで、特定のユーザーに関連する検証エラーの数を取得できます。
・user.errors.empty?
: ユーザーの検証エラーが空かどうかを確認します。エラーがない場合はtrueを返し、エラーがある場合はfalseを返します。
・user.errors.any?
: ユーザーの検証エラーがあるかどうかを確認します。エラーがある場合はtrueを返し、エラーがない場合はfalseを返します。
・helper.pluralize
(1, "error"): ヘルパーメソッドpluralizeを使用して、単数形の単語を複数形に変換します。第一引数には数を、第二引数には単語を指定します。これにより、1つのエラーに対しては"1 error"、複数のエラーに対しては"5 errors"のように、エラーメッセージを適切に表示することができます。
そして使い分け用途です。
user.errors.count
: 特定のユーザーの検証エラーの数を調べたい場合に使用します。たとえば、フォームの送信後にエラーメッセージを表示する際に、エラーの数をカウントして表示するために利用されます。
user.errors.empty
?およびuser.errors.any?: ユーザーに関連する検証エラーがあるかどうかを確認するために使用します。これにより、フォームの送信後にエラーメッセージを表示する際に、エラーがあるかどうかを確認し、適切な処理を行うことができます。
helper.pluralize(1, "error")
: エラーメッセージを表示する際に、単数形と複数形の両方を適切に扱うために使用されます。これにより、エラーが1つの場合と複数の場合の両方に対応したメッセージを生成することができます。
ユーザー登録の成功
フォームが有効な場合に新規ユーザーを実際のデータベースに保存する。
現時点で基本的には有効な情報で送信するとエラーが発生
これは、Railsはデフォルトのアクションに対応するビューを表示しようとした際に、
createアクションに対応するビューのテンプレートがないことが原因とのこと。
というわけでcreateのアクションが必要みたいです。
#保存とリダイレクトを行う、userのcreateアクション
class UsersController < ApplicationController
.
.
.
def create
@user = User.new(user_params)
if @user.save
redirect_to @user
else
render 'new', status: :unprocessable_entity
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
end
redirect_to @user
=redirect_to user_url(@user)
とのこと。
つまりリダイレクトしたらそのままユーザーページにいくみたいです。
7.4.2flash
ユーザー登録フォームが実際に動くようになりました。これでブラウザから正しいユーザー情報を登録できるようになったが、その前にWebアプリケーションに常識的に備わっている機能を追加していく。
class UsersController < ApplicationController
.
.
.
def create
@user = User.new(user_params)
if @user.save
flash[:success] = "Welcome to the Sample App!"
redirect_to @user
else
render 'new', status: :unprocessable_entity
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
end
Railsでこういった情報を表示するには、flashという特殊な変数を使う。
Railsの一般的な慣習に沿って、:successというキーには成功時のメッセージを代入。
つまりuser.saveが成功したら表示させる機能のようです。
#コンソールでflashハッシュをイテレート(each do ... end)する
rails console
>> flash = { success: "It worked!", danger: "It failed." }
=> {:success=>"It worked!", danger: "It failed."}
>> flash.each do |key, value|
?> puts "#{key}"
?> puts "#{value}"
>> end
success
It worked!
danger
It failed.
イテレートってなんだ?と思い調べてみました。
flash = { success: "It worked!", danger: "It failed." }: flash
という名前のハッシュを定義し、それぞれのキーに成功と失敗のメッセージが格納されています。
flash.each do |key, value|: flash
ハッシュの各要素に対して繰り返し処理を行うためのeachメソッドが呼び出されています。|key, value|はブロックパラメーターであり、各要素のキーと値を受け取ります。
puts"#{key}"がsuccessにくる箇所
puts"#{value}"がdangerに来る部分のようですね。
これでuser.saveしたときの結果によって条件が分岐されるようです。
<!DOCTYPE html>
<html>
.
.
.
<body>
<%= render 'layouts/header' %>
<div class="container">
<% flash.each do |message_type, message| %>
<div class="alert alert-<%= message_type %>"><%= message %></div>
<% end %>
<%= yield %>
<%= render 'layouts/footer' %>
<%= debug(params) if Rails.env.development? %>
</div>
.
.
.
</body>
</html>
flashを埋め込みました。
7.5.1本番環境でのTLS
TLSは、ローカルのサーバーからネットワークにデータを送信する前に大事な情報を暗号化する技術。
Railsでは1行変更するだけで適用できる優れもの。
Rails.application.configure do
.
.
.
# Force all access to the app over SSL, use Strict-Transport-Security,
# and use secure cookies.
config.force_ssl = true
.
.
.
end
config.force_ssl = true
これをtrue設定にしていればいいだけです。
7.5.2本番環境用のWebサーバー
# Pumaの設定ファイル
max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
threads min_threads_count, max_threads_count
port ENV.fetch("PORT") { 3000 }
environment ENV.fetch("RAILS_ENV") { "development" }
pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }
workers ENV.fetch("WEB_CONCURRENCY") { 4 }
preload_app!
plugin :tmp_restart
こちらのソースコードへ置き換え
Renderのsetting commandを`bundle exec puma -C config/puma.rb`へ書き換え
#Gemfile
.
.
.
group :production do
gem "pg", "1.3.5"
end
Windows ではタイムゾーン情報用の tzinfo-data gem を含める必要があります
gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby]
Gemfileを追加
$ bundle config set --local without 'production'
$ bundle install
bundle config setという見慣れないコードがあったので調べました。
具体的には、bundle config setは、Bundleの構成を設定するためのコマンドです。--localオプションは、プロジェクトのディレクトリ内にのみ適用されるローカルな構成を設定することを意味します。そして、without 'production'は、プロジェクトの依存関係のインストール時に、productionグループに含まれるgemをインストールしないようにするための設定です。
つまり、このコマンドは、プロジェクト内の依存関係のインストール時に、productionグループに含まれるgemをインストールしないようにするためのローカルなBundleの設定を行っています。
# SQLite. Versions 3.8.0 and up are supported.
# gem install sqlite3
#
# Ensure the SQLite 3 gem is defined in your Gemfile
# gem "sqlite3"
#
default: &default
adapter: sqlite3
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
timeout: 5000
development:
<<: *default
database: db/development.sqlite3
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
<<: *default
database: db/test.sqlite3
production:
<<: *default
adapter: postgresql
encoding: unicode
url: <%= ENV['DATABASE_URL'] %>
データベース設定ファイルconfig/database.ymlのproductionセクションを更新
以下割愛
本章のまとめ(引用)
・debugメソッドを使うことで、役立つデバッグ情報を表示できる
・Sassのmixin機能を使うと、CSSのルールをまとめたり他の場所で再利用できるようになる
・Railsには標準で3つの環境が備わっており、それぞれdevelopment(開発)環境、test(テスト)環境、production(本番)環境と呼ぶ
・標準的なRESTful URLを通して、ユーザー情報をリソースとして扱えるようになった
・Gravatarを使うと、ユーザーのプロフィール画像を簡単に表示できるようになる
・form_withヘルパーは、Active Recordのオブジェクトに対応したフォームを生成する
・ユーザー登録に失敗した場合はnewビューを再びレンダリングするようにした。その際、Active Recordが自動的に検知したエラーメッセージを表示できるようにした
・flash変数を使うと、一時的なメッセージを表示できるようになる
・ユーザー登録に成功すると、「データベース上にユーザーを追加」「プロフィールページにリダイレクト」「ウェルカムメッセージの表示」の順で処理が進む
・統合テストを使うことで送信フォームの振る舞いを検証したり、バグの発生を検知したりできる
感想
debugメソッドの入れ方はすごい便利だと感じた。またtest環境の概念等使い分けの部分でイメージが湧いた。
user createアクションなどuserの追加により広がる拡張性とセキュリティの危険性を認識
仕様設計の上で注意していかなければいけないと感じた。
引き続き習得していきたい。