Rails – form_withとモデルの使い方

概要

form系ヘルパーではフォームの入力がparams変数にセットされるが、一般には以下の様な流れで入力パラメーターを利用する。

  • form_with/form_forで読み込んだパラメーターをモデルにセットする
  • モデルへのパラメーターのセットは、慣習的にprivateのメソッドを準備して、それを介する
  • 各パラメーターは、モデルインスタンスのプロパティーとして参照・利用

モデルによるフォームデータの取得も参照。

例示

基本構造

流れ

  • トップページに入力フォームを表示
    • /top
    • app/controllers/pages_controller#top
    • app/views/pages/top.html.erb
  • フォーム入力をpagesコントローラーのinputアクションへ送信
    • app/controllers/pages_controller#input
  • inputアクションでフォーム入力をUserモデルインスタンスに格納して表示

入力フォーム

form_withによる以下のようなフォームを例にする。フォームビルダーのtext_fieldpassword_fieldに入力された値が送信される。

注意点

form_withはデフォルトでajaxによるフォーム送信となり、ページ全体がレンダリングされない。HTMLとして送信してレンダリングするには、パラメーターにlocal: trueを指定する。

<%= form_with(url:/model: ***, local: true) %>

モデルとデータベース

データを扱うモデルは以下で生成し、マイグレートしてテーブルを作成しておく

テーブル

コントローラー

以下の枠組みでコントローラーを生成。ビューのform_withでモデルを使うため、Userインスタンスを生成している。

ルーティング

  • アプリケーション起動時にtopページを表示
  • 入力POST時にinputアクションへルーティング

入力データのモデルへのセット

コントローラーに以下に様に追記。

要点は次のとおり。

パラメーターを引数にしてモデルインスタンスを与える。

変数 = モデルクラス.new(モデル名_params)

引数はモデルに与えるparamsを返すメソッドで、慣習としてメソッド名の付け方、praivate宣言、requirepremitによるパラメーター取得が定まっている。

params.require(:モデル名).permit(:要素名, ...)

モデルにパラメーターがセットされた後は、要素名と同じモデルのプロパティーで値を取得。

コンソールの出力部分は以下のとおり

require/permitメソッド

フォームの入力をモデルにセットする際、requirepermitでパラメーターを指定している。

たとえば悪意のあるユーザーによってPOSTされる要素が追加された場合でも、必要なパラメーターのみを参照・変更することができる。

そして上述のように、一般的にはrequirepermitのメソッドチェーンをprivateメソッドでラップして使う。

 

Rails – form関係ヘルパーの基本

概要

form系のヘルパーの基本的な機能・挙動について整理してみた。

  • form系の新旧4つのヘルパーの動作についてまとめた
  • モデルを介して複数のパラメーターを扱うform_forform_withのオーソドックスな使い方については、最後の「パラメーターを一括で扱う」にまとめた。

各formヘルパーの関係は

  • form_tagform_forform_withはHTMLのformタグを生成するRailsのヘルパー
  • Rails 5.1より前はform_tagform_for
  • Rails 5.1からform_withが導入され、form_tagform_forは非推奨に

form_tagform_forについては

  • form_tagはブロック内にtext_field_tagなどtag系のヘルパーを持つことを想定
  • データベースと紐づいたモデルを扱う場合はform_for、モデルを介さずにフォームのデータのみやりとりする場合はform_tagを使用

form_withについては

  • form_withform_tagの機能とform_forの機能を1つで使い分け

form_tag

動作

form_tagはフォームブロックの各要素の値を直接ハッシュで取り出すため、モデルは介在しない。

  • form_tagではアクションへのパスを指定し、アクションで各要素の値を参照する
  • 各要素の値は1次元のハッシュparamsに格納されている
  • 要素の値は要素のname属性をキーとして参照

コード間の流れ

app/views/top.html.erb

  • form_tagヘルパーの引数に、POSTを送るアクションへのパスを指定
    • パス指定にはroutes.rbで設定するエイリアスを使っている
  • 各要素ヘルパーの第1引数でid/name属性の値をシンボルで定義

config/routes.rb

  • form_tagからのPOSTを受け取るアクションへのルートを設定
  • URLにエイリアスを使っていて、それをアクションに紐づけている

app/controllers/pages_controller.rb

  • ルーティングの結果呼ばれたアクション内で、フォームの要素から送られた値を処理
  • 要素の値はparams[キーのシンボル]で取り出す

コンソール出力

  • 4行目でPOSTで送られるparams全体が表示されている
  • 6行目でparamsを表示させており、4行目と同じ内容
  • 7~8行目は個別の要素の値をキーを使って参照

form_for

動作

  • form_forでは第1引数にモデルのインスタンスを指定
    • モデルのインスタンスはコントローラーでインスタンス変数として準備
  • form_forでPOSTされた各要素の値はハッシュparams[モデル名のシンボル]に格納される
    • 要素の値を取り出すには以下のハッシュを参照
    • params[モデル名のシンボル][要素名のシンボル]
  • ここでは第2引数のURLでPOST先のアクションを指定している

コード間の流れ

より具体的な実装については、モデルによるフォームデータの取得を参照。

app/views/top.html.erb

  • form_forの第1引数にモデルのインスタンスを指定
    • インスタンスはコントローラーで生成
    • 第2に引数のurl:で指定したパスにPOST
  • form_forの構文は以下のとおり
    • form_for(モデル, オプション群) do |フォームビルダー|
  • 各要素の値はtext_fieldなどフォームビルダーのメソッドで要素名を指定して取得する

config/routes.rb

  • form_tagからのPOSTを受け取るアクションへのルートを設定
  • ここではurl:で指定したエイリアスからアクションに紐づけている

app/controllers/pages_controller.rb

  • ルーティングの結果呼ばれたアクション内で、フォームの要素から送られたデータを確認
  • 各要素の値はparams[モデル名のシンボル]にハッシュとしてまとめられている
  • 個別の要素の値はその中で要素名をキーとしてとりだせる
    • params[モデル名のシンボル][要素名のシンボル]

なお上の例ではform_forの引数に与えるモデルインスタンス(@user)を最初に生成しているが、コントローラーの中ではこれを使わず、ローカルな変数(user)を使っている。

コンソール出力

  • 4行目中、"record" => {"param_text" => "TEXT", ...}が各要素のデータ
  • 6行目に全要素のハッシュ、7~8行目に個別の要素の値が表示されている

form_with (データベースなし)

動作

  • 動作の内容はform_tagと同じ
  • そのためビューにおいてform_withの引数としてURLを指定(url: パス)

注意点

form_withはデフォルトでajaxによるフォーム送信となり、ページ全体がレンダリングされない。HTMLとして送信してレンダリングするには、パラメーターにlocal: trueを指定する。

<%= form_with(url: 'URL', local: true) do |f| %>

コード間の流れ

  • ビューでの書き方以外はform_tagと同じ流れ

app/views/top.html.erb

  • form_withの引数にURLを指定(url: パス)
  • モデルが介在しないform_tagと同じ動作をさせる書き方
  • 各要素はフォームビルダーのメソッドで書いている

config/routes.rb

app/controllers/pages_controller.rb

コンソール出力

form_with (データベースあり)

より具体的な実装については、モデルによるフォームデータの取得を参照。

動作

  • 動作の内容はform_forと同じ
  • そのためにビューにおいて引数にデータベースと紐づいたモデルを指定(model: モデルインスタンス)
  • ここでは第2引数のURLでPOST先のアクションを指定している

<%= form_with(model: 'model', url: 'URL', local: true) do |f| %>

コード間の流れ

app/views/top.html.erb

  • form_withの引数にモデルのインスタンスを指定している
    • model: モデルインスタンス名で指定
    • モデルのインスタンスはコントローラーで生成
  • モデルを介在したform_withと同じ動作をさせる書き方
  • 各要素はフォームビルダーのメソッドで書いている

config/routes.rb

app/controllers/pages_controller.rb

コンソール出力

 

Vagrant/AmazonLinux2 – Railsインストール

概要

VagrantのAmazonLinux2仮想環境rbenv/Rubyをインストール後、Railsをインストールした記録。

  • ホスト:Windows10
  • ゲスト:Vagrant + VirtualBox + bento/amazonlinux-2

Railsの最新版ではなく、オリジナルの環境に併せて特定のバージョンを指定してインストールした。

Railsに影響する機能の停止

SELinux

もともとSELinuxは無効状態だった。

firewalld

そもそもfirewalldはセットされていなかった。

Railsのインストール

一応、gemで使用できるRailsの確認。使用可能なRailsはない。

gemでRailsのバージョン5.1.7を指定してインストール。

再度使用可能なRailsを探して、インストールされていることを確認する。

Railsサーバーの稼働

エラー~JavaScriptランタイムがない

テスト用のプロジェクトを生成してRailsサーバーを起動しようとしたところ、エラーになった。

Node.jsでもよさそうなので、CentOS7と同じ手順Node.jsをインストール。Rails6では最新安定版のNode.jsだと警告が出たのでダウングレード下が、Rails5では最新版では警告が出なかった。

ここまででrails serverの実行が可能になった。

ホストブラウザーからの接続

vagrantfileに以下の1行を記述

config.vm.network "forwarded_port", guest: 3000, host: 3000

テスト用のプロジェクトを生成してrails sを実行すると、「このサイトにアクセスできません」と弾かれる。

netstatで確認すると、ポート3000は開いている。

サーバー立ち上げのオプションを以下の様にして接続成功。

rails s -b 0.0.0.0

以前参考にしたサイトを再度確認すると、Rails4.2以降ではこの接続方法になるとのこと。オリジナルの環境ではrails sで十ていたが(エイリアス?)。

既存プロジェクトの実行

オリジナルの環境でつくったプロジェクトをダウンロードし、共有フォルダー経由で仮想環境に移して実行したところ、db:createでエラーが出た。

指示通りにbundle install

この後db:migrationは問題なく、これでアプリケーションがオリジナルの環境通りに実行できるようになった。

一方、仮想環境で作ったプロジェクトをオリジナルの環境にアップロードした場合、オリジナル側でbundle installが必要になったが、これで問題なく実行できた。

 

Vagrant/AmazonLinux2 – rbenv/Rubyインストール

概要

VagrantのAmazonLinux2仮想環境にrbenv&Rubyをインストールした記録。

  • ホスト:Windows10
  • ゲスト:Vagrant + VirtualBox + bento/amazonlinux-2

Gitのインストール

CentOS7仮想環境と同じ方法でGitをインストール。

バージョン確認

rbenv/Rubyのインストール

CentOS7仮想環境と同じ方法でインストール・設定(ライブラリーのインストールも含む)。

  • Gitからrbenvをクローン
  • .bash_profileの設定
  • Gitからruby-buildをクローン
  • Rubyに必要なライブラリーをインストール

Rubyインストール時に最新バージョンではなく過去のバージョンを指定してインストール。

  • rbenv install --list-allで全バージョンを確認
  • Rubyのインストール時に、バージョンを指定してインストール
  • rbenv globalで通常使うバージョンを指定

 

Vagrant – MySQLのインストール – AmazonLinux2

概要

Vagrant + VirtualBoxのamazonlinux-2にMySQLをインストールした記録。

  • MySQLをローカルインストールする際、展開されたすべてのファイルをインストールする必要がある
  • Amazon Linux 2では標準でMariaDBがインストールされているのでアンインストール

インストール

概要

CentOS7へのインストールと同じ手順で完了した。ただしAmazon Linux 2にデフォルトでインストールされているMariaDBをアンインストール。

MariaDBのアンインストール

wgetの確認

Boxにwgetがインストールされていることを確認。

ダウンロード

ブラウザーで以下のページをたどる。

アーカイブページで以下を選択。

  • Product Version
    • 5.5.62
  • Operating Sysrem
    • Red Hat Enterprise Linux / Oracle Linux
  • OS Version
    • Red Hat Enterprise Linux 7 / Oracle Linux 7 (x86, 64-bit)

一覧の中のRPM Bundle、Downloadボタンを右クリックしてリンクのアドレスをコピー。

コンソールからwgetに上でコピーしたURLを適用してダウンロード。tarファイルがダウンロードされる。

ダウンロードされたtarファイルを確認し、展開。

展開されたファイルのインストール

1つずつsudo yum…でインストールしても、1行にまとめてインストールしても。

全ての展開ファイルのインストールが必要

当初、server、client、embeddedのみをインストールしたところ、不具合が出た。

  • Railsインストール後にプロジェクト生成でエラー
  • MySQLの文字コードセットがdatabaseとserverでlatin1
  • /etc/my.cnfが存在しない

後から残りのファイルをインストールしたところ、上の不具合は全て解消。ただしtestまで必要かどうかは確認していない。

サーバー稼働

サーバーの起動

オリジナルの環境ではservice mysqld startでサーバーを起動していたが、仮想環境ではsystemctlを使う。

ログイン

このバージョンのMySQLは、rootの初期パスワードは設定されていなかった。

文字コードセットの確認

MySQLコンソールからshow variablesで文字コードセットを確認。

 

Vagrant – 仮想環境構築 – Amazon Linux 2

概要

クラウドで利用していたAWS(Amazon Web Service)のEC2/Cloud9の環境をVagrantでローカルに構築した記録。利用時の環境に併せて、OS、MySQL、Ruby、Railsのバージョンをできるだけ整合させた。

オリジナルの環境

  • OS : Amazon Linux AMI release 2018.03
  • MySQL : mysql Ver 14.14 Distrib 5.5.62, for Linux (x86_64) using readline 5.1
  • Ruby : ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux]
  • Rails : Rails 5.1.7

構築した環境

  • OS(Box) : bento/amazonlinux-2 (virtualbox, 1.2.1)
  • MySQL : Ver 14.14 Distrib 5.5.62, for Linux (x86_64) using readline 5.1
  • Ruby : ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux]
  • Rails : Rails 5.1.7

Vagrant/VirtualBoxの導入

Vagrant – 仮想環境構築 – CentOS7を参照。

Boxの導入

ダウンロード

vagruntupFind Boxesでキーワードamazonを含むBoxを検索し、bento/amazonlinux-2(1.2.1)を選択。

このBoxをvagrant addコマンドでローカルに追加。

仮想環境の初期化

ディレクトリー\vagrant\amazonlinux2を作成・移動し、vagrant initでダウンロードしたBoxで環境を構築。

仮想環境の利用

仮想環境の起動

vagrant upコマンドで仮想環境を起動。初回起動時は多数のパッケージインストールや依存関係の解決で時間がかかる。

仮想環境へのログイン

vagrant sshコマンドで仮想環境にログイン。EC2のロゴが表示される。

システム情報

リリースの表現が違っている。

構築したシステム情報

オリジナルのシステム情報

ロケールとタイムゾーンの設定

確認

タイムゾーンがUTC、ロケールはLANG=enになっている。

ロケールの設定

ロケールをJPに設定して再起動。表現は日本語になるが、まだタイムゾーンはUTC。

timedatectlで現在のロケールを確認。UTC。

タイムゾーンの設定

timedatectl list-timezonesで設定可能なタイムゾーンを確認。

タイムゾーンをAsia/Tokyoに設定。

その他の設定

共有設定、Atomでの接続はCentOS7と同じ手順で完了。

 

Rails – タイムゾーン

概要

Railsでモデルのデータをデータベースに保存した時、OSやMySQLのタイムゾーンはJSTなのに、created_atupdated_atの時刻がUSTで保存されているのに気付いた。

以下の2つの方法があるようだ。

  • データベースへの登録時刻をJSTにする方法
  • データベースへの時刻登録はUSTとし、表示をJSTにする方法

日本以外でも利用するデータベースの時刻を統一するにはUSTの方がよさそうなので、ここでは2つ目の方法を用いた。

  1. プロジェクトのconf/application.rbに1行追加
    • config.time_zone = ‘Tokyo’
  2. サーバーが実行中の場合は再起動

これでデータベース上はUST、Railsでの扱いはJSTになる。

データベースへの記録はUST

文字列フィールドを1つ持つテスト用のモデルを生成・マイグレートする。

この操作は21時過ぎに実行したが、マイグレーションファイルの時刻が12時台で、9時間遅れているのでUSTとわかる。

なお。CentOSとMySQLのタイムゾーンはTokyoに設定されている。

mysqlにテーブルが作られている。

コントローラーを編集して、トップページ表示時にレコードを保存し、改めて読み込んで表示させる。

データベースへの書き込み時と読み込み時の時刻がUSTになっている。

mysqlで登録されたレコードを確認すると、USTで保存されている。

application.rbでタイムゾーンを設定

プロジェクトのconfig/aplication.rbに以下の1行を追加。

Railsサーバーを立ち上げなおして実行すると、表示がJSTになっている。

 

Rails – formヘルパーとCSRF対策

formヘルパーから生成されるタグ

たとえばfomr_tagヘルパーがレンダリングされると、formタグとhidden属性のinputタグが生成される。

CSRF対策

form系のヘルパーで生成されるhiddenタイプのinputタグは、authenticity_tokenという名前でランダム文字列を値に持つ。

これはフォームに対する送信を確認するもので、Railsから送ったフォーム以外からの送信を排除するための仕掛け。

CSRF(Cross Site Request Forgeries)は、攻撃者のサイトへのリンクをクリックした場合に、そのサイトから意図しない送信が送られてしまうもの。

Railsではformヘルパーで生成されたトークンとサーバー内に持っているトークンが符合すれば、意図したフォームからの投稿と判断される。このためのメソッドがprotect_from_forgery

Rails 5.2より前は、ApplicationControllerprotect_from_forgeryが自動的に記述される。

Rails 5.2以降はActionController::Baseで有効になっていて、追加記述の必要はない。

 

Rails – モデルによるDB操作の仕組み

概要

モデルによるデータベース操作を、できるだけ基本的な方法で行うことを試した。

またテーブル構造の変更に対して、マイグレーションを行わずにmysqlコンソールとコントロールファイル編集だけで対応できることを確認した。

モデルとテーブルの生成

モデルの生成

以下の様にモデルを生成する。このモデルは文字列型のフィールドを1つ持っている。

rails generate model record col1:string

app/models/record.rbが作成される。

マイグレーションファイルも作成されていて、string型のフィールドが定義されている。

マイグレーション~テーブル生成

このモデルからマイグレーションによりテーブルを生成する。

rails db:migrate

mysqlでテーブルが生成されていることと、そのテーブルの構造が確認できる。

モデルの構造の確認

コントローラーからモデルの内容を出力させる。

トップページを出力させると、サーバー実行中のコンソールに以下が出力される。

Railsデフォルトのid、created_at、updated_atに加えてcol1が要素に加わっている。

データの登録確認

トップページアクセス時にレコードを1つ登録する。レコードのフィールドはハッシュで指定する。

Railsサーバーのコンソールに以下が出力され、データが生成・登録されていることがわかる。

mysqlでもテーブルへの登録状況が確認できる。

データの読み込み確認

トップページ読み込み時にデータベースを読み込んで表示するように変更する。

出力は以下のとおりで、データベースの内容が読みだされている。

MySQLでのテーブル変更

以下の様にmysqlで直接テーブルにフィールドを加える。

Railsで変更後テーブルへの書き込み

トップページアクセス時に、フィールド追加後のテーブルに新たなデータを書き込むように変更。

トップページ表示時にデータが登録され、テーブルは以下の様に更新されている。

 

Rails – プロジェクトでのデータベース準備

概要

Railsでモデルによりフォームやデータベースとのやり取りをする流れ。

  1. プロジェクト生成時にDBMSを指定
  2. database.ymlを編集してデータベースを生成
  3. モデルを生成
  4. マイグレーションでテーブルを生成
  5. モデルでフォームデータを取得
  6. モデルでテーブルを読み書き

プロジェクト生成時

プロジェクト生成時にDBMSを指定する。

  • Rails2.0.2以降ではデフォルトデータベースがMySQLからSQLiteに変更
  • MySQLを使う場合、プロジェクト生成時に以下の様に指定

rails generate project_name -d mysql
rails generrate project_name --database=mysql

データベースの生成・操作

database.ymlの編集

config/database.ymlファイルの内容を設定・変更

データベースの生成

以下のコマンドでdatabase.ymlの設定に従ってデータベースを生成。

rails db:create

データベースの確認

以下のいずれかの方法で、アプリケーションのデータベースを確認できる。

  • mysql -u user -pを実行してMySQLのコンソールに入る
  • プロジェクトディレクトリー内でrails dbconsoleを実行して、アプリケーションが使うデータベースをuseした状態でMySQLのコンソールに入る

データベースの削除

以下のコマンドでアプリケーションで使っているデータベースが削除される。有無を言わさずすべてのテーブルごと消えてしまうので注意。

rails db:drop

実行例

database.yml

データベース名を設定する。以下はdemo_appアプリケーションでdemo_app_dbを使う設定。

データベースの生成

rails db:createコマンドでdatabase.ymlの設定を使ってデータベースを作成する。

rails dbconsoleコマンドでデータベースが作成されたことを確認。