rails作業日誌

前書き

今日は『Ruby on Rails5 アプリケーションプログラミング』での学習ができなかったので作業日誌です。あと、ここに書いてあることは単なる学習記録なので事実とは関係がない場合があります。ご了承ください。

外部キー制約の追加について

そもそも外部キーとは?

外部キーについては以前も取り上げたが、正確に理解していないようだったので、整理しておく。外部キーは例えばcompaniesテーブルに従属するstaffsテーブルにおける、company_idのことである。ただ、staffsテーブルが単にcompany_idを持っているだけではそれは外部キーではない。それが外部キー制約を持っていて初めて外部キーになる。大事なのは制約の方。

外部キー制約とは?

外部キー制約とは、そのカラムに参照先のテーブルの主キーの値以外を入れてはいけないという制約のこと。その制約のかかっているカラムのことを外部キーと呼ぶ。参照先テーブル_idという名前にすると自動的に外部キーになるわけではない。なので必ずしも参照先テーブル_idという名前でなくても良い。ただ、railsのdefaultではそうなっているのでそうしておいたほうがいい。

外部キーの設定方法

外部キーを設定するのに必要なものは、参照先テーブルの主キーと参照先テーブル_idという名前のカラムと外部キー制約の3つということになる。scaffoldを組む際には

rails generate scaffold staff company:references

のようにするとstaffとcompanyの主従関係が自動的に生成され(companyモデルには別途has_many等の記述が必要)上の3つのうち主たるテーブル以外の2つが勝手に出来上がる。

外部キー制約の追加

既存のテーブルに後から外部キーを設定するには、マイグレーションファイルを作って

class AddCompanyIdToStaffs < ActiveRecord::Migration[5.2]
  def change
    add_reference :staffs, :company, foreign_key: true
      end
end

のように定義してマイグレーションする。そうすればcompany_idとその外部キー制約が出来上がる。
add_referenceは

add_reference(テーブル名, リファレンス名 [, オプション])

のように使用する。
その3つのうちの外部キー制約のみ設定したい場合はadd_foreign_keyを使う。同様にマイグレーションファイルを作って、

class AddForeignKeyToStaffs < ActiveRecord::Migration[5.2]
  def change
    add_foreign_key :staffs, :companies
  end
end

のようにする。add_foreign_keyは

add_foreign_key :対象のテーブル, :指定先のテーブル

のように使う。これはcompany_idという名前のカラムがあることを前提にしているので、ない場合はエラーになる。

scaffoldでのnewの流れとbcrypt

scaffoldは便利だけど自動でできてしまうだけに流れがよくわからなくなる時があるので整理した(『Ruby on Rails5 アプリケーションプログラミング』第3章の復習)。あとパスワードでbcryptをハッシュ化している。
GET /companies/newでurlを受けると

resources :companies

のルートで内部的に定義されているのでcompaniesコントローラーのnewメソッドが処理される。

def new
  @company = Company.new
end

ここでCompanyモデルが初期化されテンプレート変数@companyに格納される。
次にviews/companies/new.html.erbが表示される。メインテンプレートであるnew.html.erb内に

render 'form', company: @company

の記述があり、部分テンプレートviews/companies/_form.html.erbを呼び出していることがわかる。また、第二引数をcompany: @companyとすることで、部分テンプレートでテンプレート変数@companyをcompanyパラメータとして受け取れるようにしている。
呼び出した_form.html.erbを見てみると

<%= form_with(model: company, local: true) do |form| %>

とある。これで@companyがdbにあるときはコントローラーのupdateにとび、ないときにはcreateにとぶという定義をしている。今回はnewなのでcreateに飛ぶ。createではpostで受け取ったパラメータからprivateメソッドのcompany_paramsを実行する。パスワードをハッシュ化する場合はそこで:password, :password_confirmationを受け取っていて、これらが一致した場合のみモデル内のhas_secure_passwordメソッドを受けることによってハッシュ化されると思われる(モデルにはhas_secure_passwordを記述しておかなければならない)。そしてインスタンス変数@companyに格納されて、password_digestにsaveされる。passwordがハッシュ化されたものがpassword_digestである。パスワードのハッシュ化はbcryptのインストールとdbにpassword_digestカラムがあることが前提となっている。

enumとformについて

モデルにenum

enum status: { draft:0, published:1, deleted:2 }

のように定義して、form

  <div class="field">
    <%= form.label :status %>
    <%= form.number_field :status %>
  </div>

のようにformを作ると、viewから送信するときに

'1' is not a valid status

のようなエラーが出る(これは1を入力した場合)。エラーメッセージをよくみると1に''がついている。数値で入力しているのに受け取るときになぜか文字列になってしまうらしい。enum以外のinteger型は大丈夫なのになんでだろう?これを回避するためにはformをセレクトボックスかラジオボタンにしてvalueを設定しておけば良いとわかった。

  <div class="field">
    <%= form.label '下書き' %>
    <%= form.radio_button :status, :draft %>
    <%= form.label '公開' %>
    <%= form.radio_button :status, :published %>
  </div>

のようにして保存することができました。見てわかるようにform.radio_buttonの第二引数にはenumの数値に対応する値をとります。

今日の感想

わかったつもりでわかってないことがいっぱいある。
作業の計画はなるべく細かく具体的に立てたほうが良い。当たり前なんだけど。徹底していきたい。