Rails – 掲示板 – ユーザー属性の追加

概要

ユーザーモデルにプロフィールに使う画像ファイル名とコメントの2つの属性を追加する。

そのために、既にデータが登録されているデータベースのテーブルに、カラムを追加する。

マイグレーションによるカラム追加

データベースにデータが登録されている状態で、データを損なわずにテーブルのカラムを追加する。大まかな流れは以下のとおり。

  1. カラム追加用のマイグレーションファイルを生成する
  2. 追加用マイグレーションファイルの内容を確認する/記述する
  3. マイグレーションを実行する

現在のテーブルをマイグレートした際のマイグレーションファイルの内容は以下のとおり。

db/migrate/20210320060906_create_users.rb

カラム追加用マイグレーションファイルの生成

カラム追加のためのマイグレーションファイルを生成するコマンドは以下のとおり。

rails generate migration AddAnyToTable col:type ...

  • Anyは一般に追加するカラム名を書くが、任意の文字列でよい
  • Tableはカラムを追加するテーブル名
  • col:typeは追加するカラム名と型で複数指定可
    • 指定しなければ空のマイグレーションファイルが生成され、後から追加内容を記述

マイグレーションファイルの記述

コマンド実行の結果、以下のようなマイグレーションファイルが生成される。

20210326083051_add_coumuns_to_users.rb

このファイルのchangeメソッドにカラム追加の命令を記述。

add_column :table, :col, :type

テーブル名、カラム名、型をシンボルで記述する。

マイグレーションの実行

マイグレーションを実行。

マイグレーションの結果、新たにカラムが追加されている。

データが残っていることを確認。追加されたカラムはupdated_atよりも後ろにある。

Rails – 掲示板 – 仮のプロフィールページ

概要

第1段階でユーザーの登録(サインアップ)や認証(サインイン/サインアウト)ができるようになった。これからはサインインしたユーザーに応じた表示や操作を実装していく。

最初に、サインインしているユーザーのプロフィール画像表示を実装する。

ここでは、サインイン中のユーザーがメニューの「プロフィール」をクリックしたときにプロフィールページへ遷移させる。現段階では、サインイン中のユーザー名を表示し、ダミー画像とプレースホルダーのコメントを表示させる。

掲示板の第2段階へ

枠組み

全体の枠組み

全体の枠組み設定は以下のとおりとする。

  • プロフィールページのURLは"ドメイン/profile/ユーザーID"
  • アクションはusers#show(個々のユーザーの情報を表示するためshowを選択)
  • ビューファイルはapp/views/users/show.html.erb

ユーザーに応じた反応

基本の流れは以下の通り。

  1. ルーティングのURIパターンでユーザーIDを付加
  2. 遷移ページのリンク先として、ルーティングの名前でサインイン中のユーザーIDを指定
  3. コントローラーでIDを使ってデータベースからユーザーオブジェクトを取り出す
  4. ビューにおいて、そのユーザーオブジェクトを使う

実装

ルーティング

まず、プロフィールページへのURLがリクエストされたときのルーティングを設定する。

routes.rb

この書き方がユーザーに応じたページへのルーティングの書き方。

  • ユーザーを指定するため、データベース中のusers.idの値をURLで指定
    • (:id)の括弧がなくても問題なさそう
  • プロフィールは各ユーザー固有の情報を表示するため、showアクション
  • Prefixを指定する
    • :idを付加した場合、デフォルトのPrefixは設定されないため

指定されたユーザーの取得

ルーティング先のアクションで、:idを使ってデータベースからユーザーオブジェクトを取り出す。オブジェクトがビューでも使えるよう、インスタンス変数に格納する。

また、サインインしていない状態でshowアクションを読んだ場合にアクセス制限をかけるよう、before_actionを設定する。ただしサインアップ/サインインに関するアクションはexcept:で除外しておく。

users_controller.rb

findはモデルのクラスメソッドで、モデルのIDを指定してデータベースからデータを取得する。引数に指定できるのはIDの値のみ。

ユーザーオブジェクトを使った表示

アクションでセットされたユーザーオブジェクトを使って、ユーザーに応じたページ表示をするためのHTMLを書く。

users/show.html.erb

ここでは@usernameを使ってサインイン中のユーザーの名前を表示させている。画像やコメントはダミーだが、サインイン中のユーザーに応じたページが表示されることが確認できる。

なお4行目で、プロフィール画像のプレースホルダーとしてCDNによるダミー画像を利用している。

サインインユーザーによるページの呼び出し

サインイン中のユーザーがメニューの「プロフィール」をクリックしたときに、そのユーザーのプロフィールが表示されるよう、link_toヘルパーを設定する。

application.html.erb

ルーティングで設定したPrefixを使ったパス名に(ユーザーID)を指定している。

link_to("...", profile_path(user_in_session))

(user_in_session)とユーザーオブジェクトそのものを指定しているが、これはRailsでの省略法で、これで(user_in_session.id)と同じことになる。

パス名ではなく、URIを文字列で指定する方法もある。その場合はルーティングで指定した"/profile/(:id)"の形になるように文字列で指定する。ただしlink_toだと<%= ... %>が入れ子にできないためかエラーになるので、aタグを使うことになる。

<a href="/profile/<%= user_in_session.id %>">プロフィール</a>

スタイル設定

プロフィールページのスタイルを設定する。

users.scss

トップページへのリンク

プロフィール画面からトップページに戻るリンクをヘッダーメニューに設定する。application.html.erbに更に変更を加える。

application.html.erb

8~10行目は、ヘッダーのロゴにトップページへのリンクをセットしている。他の要素にリンクを設定するには、link_to(...) doendのブロックにする。

14行目では、メニューに「トップ」という項目を追加し、トップページへのリンクを設定している。

表示結果

追補~サイズの固定とスクロール表示

その後、コメント表示を以下の様に変更した。

  • p要素の幅と高さを固定
  • overflow-y: autoを追加し、コメントが長くなった場合にスクロールバーを表示

 

Rails – 掲示板 – 第2段階

概要

第1段階でユーザーの取り扱い全般を実装したが、第2段階ではコンテンツの投稿に関する機能を実装していく。

ここでは以下を含む。

  • プロフィールや新規投稿などユーザーに応じたページの表示と操作
  • CDNによるアイコンの導入
  • データベースへのカラムの追加
  • リレーションを持ったテーブル構造の操作
  • 画像のアップロードなどを含む。

掲示板の全体ページへ

要件

画面遷移

サインイン中の画面遷移を対象とする。

  • /top画面で
    • ユーザーの投稿記事(名前、コメント、画面、日付)を表示(上限数付き)
    • メニューの「プロフィール」からユーザーごとの/profile画面へ
    • メニューの「ユーザー設定」からユーザーごとの/edit画面へ
    • メニューの「投稿する」から/post画面へ
  • /profile画面で
    • ユーザーの名前、画像、コメントを表示
    • コメントの投稿と画像のアップロードが可能(いずれか一方でも可)
    • ヘッダーメニューのロゴあるいは「トップ画面」から/top画面へ
  • /edit画面で
    • プロフィール画像、プロフィールコメントを選択・編集
    • ボタンを押してプロフィールの内容を変更し、/profile画面へ遷移
  • /post画面で
    • コメントの投稿と画像のアップロードが可能(いずれか一方でも可)
    • 記事投稿後、ヘッダーメニューのロゴあるいは「トップ画面」から/top画面へ

データベース

テーブル

登録ユーザーテーブル:users

userテーブルは、プロフィールに表示する以下の項目について新たにカラムに追加。

  • プロフィール画像のファイル名
  • コメントテキスト
カラム名 型(SQL) 型(Rails) 内容
id BIGINT(AI) Rails生成
name VARCHAR(255) string ユーザー名
email VARCHAR(255) string メールアドレス
pasword VARCHAR(255) string パスワード
image_file_name VARCHAR(255) string プロフィール画像ファイル名
comment TEXT text プロフィールコメント
created_at DATETIME Rails生成
updated_at DATETIME Rails生成

投稿記事テーブル:posts

postsテーブルは、Railsが設定する項目以外に以下の項目を持つ。

  • 投稿ユーザーのid(users.idを参照)
  • 投稿記事のメッセージを保存する。なお画像のみの投稿も認めるため、commentNULLを許容する。
カラム名 型(SQL) 型(Rails) 内容
id BIGINT(AI) Rails生成
user_id BIGINT references usersテーブルのid
message TEXT text メッセージ本文
created_at DATETIME Rails生成
updated_at DATETIME Rails生成

投稿画像テーブル:post_images

post_imagesテーブルは、Railsが設定する項目以外に以下の項目を持つ。

  • 投稿記事のid(posts.idを参照)
  • 画像ファイルのファイル名
カラム名 型(SQL) 型(Rails) 内容
id BIGINT(AI) Rails生成
post_id BIGINT references postsテーブルのid
file_name VARCHAR(255) string 画像ファイル名
created_at DATETIME Rails生成
updated_at DATETIME Rails生成

リレーション

テーブル間の関係は以下のとおり。

  • userは0以上複数のpostを持つ
  • postは必ず1つのuserに属する
  • postは0以上複数のpost_imageを持つ
  • post_imagesは必ず1つのpostに属する

検討

コントローラーとアクション

/profile/:id -> users#show

  • ユーザーごとのプロフィールを表示する
  • プロフィールの内容はプロフィール画像とコメント

users#edit, users#update

  • ユーザーごとのプロフィールを編集する
  • 編集内容は、画像の登録/差し替え、コメントの記入/変更

posts_controller.rb

  • 投稿記事を取り扱うコントローラー
  • 投稿記事の新規登録、編集、削除
  • 投稿記事は1つのメッセージと0~複数の画像からなる
  • メッセージがあれば画像はなくてもよく、画像があればメッセージは空でもよい

ビュー

users

  • users/profile.html.erb:ユーザーのプロフィールページ
  • users/edit.html.erb:ユーザー設定ページ
  • posts/new.html.erb:新規投稿ページ
  • posts/edit.html.erb:投稿内容編集ページ

スタイルファイル

プロフィールページやユーザー設定ページのスタイル定義を追加。

  • users.css

投稿関係ページのスタイルは以下のファイルに新規に作成

  • posts.css

実装手順

プロフィール

記事投稿

 

Rails – 掲示板 – トップ画面の仮表示

概要

サインアップ(ユーザー登録)サインイン・サインアウト(ユーザー認証)アクセス制御まで実現できたので、トップページを表示したときに登録済みユーザーの情報を表示させるように仮組みする。

掲示板の第1段階へ

コントローラー

pagesコントローラーのtopアクションで、usersテーブルに登録された全ユーザーをオブジェクトとして取り出し、その名前とメールアドレスを一覧として表示させる。

処理概要は以下のとおり。

  • allメソッド:モデルのクラスメソッドで、テーブルの全データをモデルインスタンスの配列として取り出す
  • orderメソッド:SQLのORDER BY句を適用する
    • 引数は1つの文字列として与え、スペースで区切る
    • 1つ目は並べ替えの基準とするカラム
    • 2つ目は並べ替えの順序(asc:昇順・デフォルト、desc:降順)
  • 取り出した結果は、ビューで利用可能なようにインスタンス変数に格納する

ビュー

コントローラーで得られた@usersの要素を取り出し、テーブルとして出力。

表示結果

 

Rails- 掲示板 – アクセス制御

概要

セッション機能を使って、悪性制御を行う。

サインインしていない状態で登録ユーザー用のページ(今回はトップページ)にアクセスした場合、これを表示せずにサインインページにリダイレクトする。

また、サインイン状態でルートにアクセスした場合はトップページを表示する。

掲示板の第1段階へ

セッション中確認機能の追加

以下のメソッドをUserHelpaerモジュールに追加する。

user_in_session?
アクセスしたユーザーがサインイン中かどうか(そのユーザーのセッションが存在するか)判定するメソッド。
authorize
ユーザーがサインイン状態でなければサインインページにリダイレクトするメソッド。コントローラーのフィルターでコールバックとして登録するのに用いる。

アクション実行前の認証

UserHelperで定義したauthorizeメソッドを、トップページを含むpageコントローラーのフィルターで設定する。

ここではpageコントローラーのフィルターとして設定し、コントローラーにはtopアクションが含まれている。

アクセスしたユーザーがサインイン状態であればtopビューがレンダリングされ、サインインしていなければauthorizeの内容に従ってsign_inページに遷移する。

たとえばアクションによって振り分けたい場合はフィルターのonlyexceptで対象となるアクションを制御する。

 

Rails – 掲示板 – セッション機能

概要

ユーザー認証によってサインインしたユーザーの情報を保持し、サインアウト時にそれを破棄する。

  • サイトのルートはトップページ
    • サインインしていなければサインインページへリダイレクト
    • サインイン中のユーザーの名前表示
    • メニューからサインアウト可能
  • サインインページ
    • サインイン成功後にトップページに遷移
  • サインアップページ
    • サインアップ成功後にトップページに遷移

掲示板の第1段階へ

ヘルパーの追加

ヘルパーモジュールのインクルード

以降、ユーザーセッションの管理に関するメソッドをUserHelperモジュールに書いていく。

プロジェクト内でそれらを共有するため、applicationコントローラーでモジュールをインクルードする。

app/controllers/application_controller.rb

ヘルパーモジュールのメソッド定義

ユーザーのサインイン/サインアウト処理、サインイン中のユーザーを取得するメソッドを、UserHelperモジュールに書く。

セッション中のユーザーをインスタンス変数に保持する。

app/helpers/user_helper.rb

サインイン・サインアウト処理

サインイン処理

サインインに成功した場合、セッションを開始してユーザIDを登録する。

app/controllers/users_controller.rb

サインアップ後の処理

サインアップが成功した場合も、サインイン状態と同様にセッション登録してトップページに遷移する。

サインアウト処理の追加

サインイン中のユーザーがサインアウトを要求した時のアクションを、usersコントローラーに定義する。

サインアウトにあたってセッションを停止し、サインインページに遷移する。

ルーティングの設定

サインアウトのルーティングを追加する。

ビューの修正・サインアウト

ヘッダー部にサインイン中のユーザー名を表示する。

また、ヘッダーメニューのサインアウトのリンク先をsign_outアクションとする。

 

Rails – フィルター

概要

フィルターは、コントローラーのアクションが実行される前や後などに登録した関数が実行されるよう設定する。

特定のアクションのときだけ実行したり、指定したアクション以外のときに実行させるよう指定できる。

フィルターの種類

before_action

before_action :コールバック

アクションの実行前に、指定したコールバックを実行するよう設定する。

after_action

after_action :コールバック

アクションの実行後に、指定したコールバックを実行するよう設定する。

around_action

around_action :コールバック

アクションの実行中に、指定したコールバックを実行するよう設定する。

コールバック中にアクションの実行をyieldするような場合に使うらしい。

only/except

コールバックを実行するアクションを限定したり(only)、除外したり(except)する。

before_action :コールバック, only: [...], except: [...]

only/exceptの指定は、コールバックのシンボルをカンマで区切って並べる。

[:コールバック1, :コールバック2, ...]

 

Rails – 掲示板 – ユーザー認証機能

概要

サインアップしたユーザーが、メールアドレスとパスワードでサインインし、サインアウトする機能を実装する。

セッションによるアクセス制限などの管理は後に実装し、ここではユーザー認証とページ遷移だけにとどめる。

掲示板の第1段階へ

サインイン機能

サインイン処理アクション

サインインが要求されたとき、sign_inアクションからサインインのビューがレンダリングされる。ビューのフォームとの間でUserモデルを介してデータをやりとりするので、アクションでUserモデルのインスタンスを生成しておく。

フォームでのモデルの設定

サインインのビュー、sign_in.html.erbform_withヘルパーで、コントローラーで生成されたUserモデルのインスタンスを指定する。

app/views/users/sign_in.html.erb

サインイン処理

サインインフォームの入力情報は、users_controllersign_in_processアクションにPOSTされる(ルーティングは「フォームの入力を確認する」で設定した)。

users_controllerに、以下のsign_in_processメソッドを追加する。

処理の流れは以下のとおり。

  1. POSTされたデータをuser_paramsメソッドにより受け取る(user_paramsについてはform_withとモデルの使い方を参照)
  2. user_paramsを使って入力データがセットされたUserインスタンスを生成
  3. ユーザー登録時と同じ方法で暗号化されたパスワード(ダイジェスト)を生成
  4. メールアドレスとパスワードダイジェストで、データベースからユーザーを検索
  5. ユーザーが存在する場合、サインインとしてtopページにリダイレクト
  6. ユーザーが存在しない場合、エラーメッセージをセットして再度サインイン画面をレンダリング(@userに入力データがセットされているので、サインイン画面にメールアドレスが残る)

サインアウト機能

サインインに成功すると、トップ画面に遷移する。

トップ画面を始めサインイン後にアクセス可能な画面は全てヘッダーメニューを持つ。そのメニュー中、サインアウトの項目をサインイン画面へのリンクとする。

 

Rails – 掲示板 – サインアップ機能

概要

サインアップ機能では、フォームで入力されたユーザー情報をデータベースに登録する。

データベースの構築、テーブルのマイグレーション、モデルによるDBの操作を整理する。

掲示板の第1段階へ

データベースの準備

データベースの準備」の要領でプロジェクトで扱うデータベースを準備する。

database.yml

development用のデータベースのみ準備し、testとproductionではデータベースを作成しない設定とする。

config/database.yml

DB作成

rails db:createでデータベースを作成し、rails db:consoleでmysqlコンソールに入り、データベースが作成されていることを確認。

モデルの生成とマイグレーション

モデルを生成し、これに対応したテーブルをマイグレーションにより生成する(基本的な方法はこちら)。

第1段階の最初で設定したデータベースのカラムを指定してモデルを生成する。idcreated_atupdated_atの3つはRailsが自動的に付加するので、以下の3つのみ指定する。

  • name:string
  • email:string
  • password:string

モデル名、モデルファイル名などの対応は以下の通り。

モデル名 user
モデルクラス名 User
モデルファイル名 user.rb
テーブル名 users

モデルの生成には、rails generate modelコマンドを使う。

rails generate model model_name col1:type col2:type ...

app/modelsディレクトリーにモデルファイルuser.rbが生成される。

app/models/user.rb

同時に、dbディレクトリーにマイグレーションファイルが生成される。

ファイル名は20210320060906_create_users.rbで、日時がUSTとなっている。

db/20210320060906_create_users.rb

rails db:maigrateコマンドを実行すると、マイグレーションファイルの内容に従ってテーブルが作成される。

rails dbconsoleコマンドを実行すると既にプロジェクトのデータベースを使う状態になっていて、usersテーブルが作成されているのが確認できる。

フォームデータのデータベースへの登録

フォームデータの取得

モデルによるフォーム入力への変更

フォーム入力の確認ではURL(アクション)を指定してデータを送信した。

モデルを使う場合には、モデルのインスタンスを生成して、そのインスタンスを介してデータを扱う。そのために以下の2点を変更する。

まずフォームでモデルを指定するため、その実行前、sign_upで空のUserクラスのインスタンスを準備する。結果はコントローラーのインスタンス変数@userに格納し、フォームがあるビューでも利用可能なようにしておく。

app/controllers/users_controller.rb

次に、フォーム側でモデルに入力データを格納するように変更する。form_withの場合は:modelキーで@userを指定する(form_forを使う場合は単に第1引数に@userを指定する)。

app/views/users/sign_up.html.erb

モデルのパラメーターの一括取得

モデルによるフォームデータの取得を参考に、require.permitによるメソッドを準備しておく。

app/controllers/users_controller.rb

データ保存の枠組み

フォームの入力データを単に保存する、というだけの手続きであれば、以下の処理になる。

  1. フォームからデータがPOSTされる
  2. それがsign_up_processにルーティングされる
  3. sign_up_processアクション内で
    1. フォームに入力されたパラメーターで新たなUserモデルのインスタンスを生成
    2. そのインスタンスの内容でデータベースに書き込み

フォームへの入力後、サインアップのボタンを押した後のテーブルの内容を確認すると、データは登録されている。

入力データの検証と書き込み

入力データのバリデーション

入力データのバリデーション処理を記述する。検証内容は以下の通り。

:name :email :password
存在 空ではない 空ではない 空ではない
一意性 一意 一意 一意
長さ 20文字以下 80文字以下 6文字以上
書式制約 なし あり なし

モデルのデータのバリデーションは、モデルクラス内で以下を宣言する。実行はsavecreateupdateの時に実行される。

validates: 対象カラム, 検証条件, 検証条件, ...

userモデルへのバリデーションの実装は以下の通り。

メールアドレスについては正規表現によりパターンチェックしている。

  • ローカル部は単語形成文字(a-z, _)2文字以上で間に連続しないドットをはさんでよい
  • ドメインは単語形成文字で間に連続しないドットをはさんでよく、最後にTLD(top level domain)が必要

エラー表示

モデルにvalidatesを定義すると、user.saveでデータベースに登録する前にバリデーションが実行される。

検証結果が適正であればデータは登録され、user.saveの戻り値はtrue。不適正であればデータは登録されず、user.saveの戻り値はfalseになる。

適正に登録された場合はトップページに遷移し、登録成功した旨のメッセージを一定時間表示する。一方不適正な場合は再度サインアップページを表示し、入力が不適正な旨のメッセージを一定時間表示する。

ページ遷移の際に一定時間メッセージを表示して消す機能についてはflushの使い方を参照。

コントローラーでの処理

  1. フォームからPOSTされたデータをで受け取り、user_paramsメソッドを介してモデルを生成
  2. テストのため、valid?メソッドでバリデーション実行
  3. 検証の結果が適正か不適正かによってflash.nowに保存
  4. 共通レイアウトを指定し、適正ならsign_inを、不適正ならsign_upをレンダリング

app/controllers/users_controller.rb

flash.nowを使っている理由は以下の通り。

  • エラー後の再入力で、直前に入力した値をフォームに入れておきたい
  • そのためにはインスタンス変数@userを使う必要がある
  • ただしredirect_toでアクションから実行すると@usernilに初期化されてしまう
  • そこでrender@userの内容を保持したい
  • そうすると、flashではその次のアクションまで内容が保持されて余計な表示がされることがある
  • そこで適正・不適正ともflash.nowrenderを使った

また、17行目の処理は以下の通り。

  • @user.errors.messagesはメッセージタイプをキーとするハッシュ
  • ハッシュの値は検証結果のメッセージの配列
  • まずvaluesmessagesの全てのメッセージタイプの値を取り出す(メッセージ配列を要素とする配列)
  • flattenで1次元のメッセージの配列にする
  • joinで配列要素を文字列に結合(区切りは<br>タグ)

レイアウトでの表示

  1. JQueryを使うのでCDNから読み込み(9行目)
  2. flash.nowが設定されていればメッセージを表示
    • 表示するp要素のクラスをタイプごとに設定
    • メッセージ中の<br>タグを有効にするため、html_safeを適用
  3. 3秒後にメッセージをスライドアップするJavaScriptを記述

app/views/layouts/application_signed_out.html.erb

スタイルの設定

  • .flash_messageで共通のスタイルを定義
  • success/dangerに応じて設定されたクラスごとに色を変える

app/assets/stylesheets/common.scss

DB書き込み処理

@user.valid?でメッセージの内容や挙動を確認したら、ここを@user.saveに書き換える。

データを1つ登録した結果。

以降、重複する内容のデータで登録しようとするとuniquenessで弾かれる。

パスワードの暗号化

パスワードの暗号化はDigest::MD5を使うuserモデルに以下を追記する。

before_saveでコールバックを設定していて、モデルの内容がデータベースに書き込まれるときにパスワードが暗号化される

当初これをbefore_saveとしていたが、これだと後述のデータ更新のときにもコールバックが実行され、既にハッシュ化されているパスワードが更にハッシュ化されて登録されていまう。このため、これをbefore_createに変更。

before_createでコールバックを設定していて、モデルの内容でデータベースのレコードが新規作成されるときにパスワードが暗号化される。

以下はテストデータを登録した結果。

 

Rails – コールバック

概要

コールバックは、validationsaveなどの処理の前後に登録した関数を実行させるよう設定する。

コールバックの種類

オブジェクトの生成

  • before_validation
  • after_validation
  • before_save
  • around_save
  • before_create
  • around_create
  • after_create
  • after_save
  • after_commit/after_rollback

オブジェクトの更新

  • before_validation
  • after_validation
  • before_save
  • around_save
  • before_update
  • around_update
  • after_update
  • after_save
  • after_commit/after_rollback

オブジェクトの消去

  • before_destroy
  • around_destroy
  • after_destroy
  • after_commit/after_rollback