作業日誌2

前書き

今回やりたかったことはstaffの新規登録をしようとするとログイン認証がかかる→companyのパスワードでログイン→ログインしたcompanyを外部キーとして新規staffをcreateという流れ。その過程で学習したこととハマったところをまとめる。話題が飛んだりエラーの解決法はわかったけど、原因がわからなかったりする。わかったら追記する。

学習ポイント

フィルタとログイン認証

ルートでstaff/newをリクエストするとstaffコントローラのnewメソッドに飛ぶが

before_action :check_logined, only: [:new, :edit, :update, :destroy]

のようにフィルタをかけているので、check_loginedの処理に入る。check_loginedの部分(フィルタのメソッド)は通常privateメソッドとして定義する。before_actionメソッドでオプションonly: で指定したメソッドに先立ってcheck_loginedを実行させることができる。

    # app/controllers/staff_controller.rb
    
    def check_logined
    
          # sessionに:companyがあるかどうかで条件分岐
    
          if session[:company] then
    
            # begin...endで囲むと...の部分が最低一回は繰り返される。
    
            begin
    
              # 新たにインスタンス変数(テンプレート変数)を作る
    
              @company = Company.find(session[:company])
    
            # rescueで例外処理
    
            rescue ActiveRecord::RecordNotFound
    
              reset_session
    
            end
              
          end
          # companyがなければ、、
          unless @company
            # flashメソッドを使って現在地へのパスをredirect_toの先に渡している。
            flash[:referer] = request.fullpath
            # redirect_toメソッドで引数urlの位置に処理を飛ばすことができる。
            # ここではloginコントローラのindexを指定している。
            # もしコントローラクラスにindexがあればその処理を行うが
            # ない場合はviews/login/index.html.erbを表示。
            redirect_to controller: :login, action: :index
          end
        end

予めcompanyログインをしてない場合、unless以下の処理に入る。redirect_toは 処理を引数のurlに移動させるメソッド。引数はurlぽくなくてハッシュにしている。これはurl_forと同じ指定方法。

url_forメソッド

ルート定義から動的にurlを生成する。これ自体を使用する機会は多くはないが、引数やオプションの指定の仕方はredirect_toやform_tagでのurlのして方法とも共通するので理解を深めておくと便利。いずれもurlを動的に生成するための指定の仕方。

オプション 概要
controller コントローラー
action アクション名
host ホスト名(現在のホストを上書き)
protocol プロトコル名(現在のプロトコルを上書き)
anchor アンカー名
only_path 相対パスを返すかどうか。hostの指定がない場合、defaultはtrue
trailing_slash 末尾にスラッシュをつけるかどうか。
user http認証に使用するユーザ名
password http認証に使用するパスワード

上のredirect_toではこのようにした。

    redirect_to controller: :login, action: :index

この場合はloginコントローラのindexアクションを指定している。生成されるurlは/login/index。なのでroutes.rbにもget 'login/index'が必要。この場合は現在地がstaffコントローラの中にいるのでcontroller: :loginを明示的に指定する必要がある。redirect_toの先をみてみる。

    # app/views/login/index.html.erb
    <p style="color: Red"><%= @error %></p>
    # 
    <%= form_tag action: :auth do %>
      <div class="field">
        <%= label_tag :name, '企業名' %><br />
        <%= text_field_tag :name, '', size: 20 %>
      </div>
      <div class="field">
        <%= label_tag :password, '企業パスワード' %><br />
        <%= password_field_tag :password, '', size: 20 %>
      </div>
      <%= hidden_field_tag :referer, flash[:referer] %>
      <%= submit_tag 'ログイン' %>
    <% end %>

対して↑のform_tagのアクションのpost先は

    form_tag action: :auth

のようにアクションパラメータのみ指定している。こうするとまず各メソッドが現在地を基点にurlを生成するためコントローラはデフォルトでloginが指定される(loginディレクトリのviewだから)。具合的に生成されるurlは/login/authとなる。form_tagはデフォルトでpostメソッドでパラメータをurl先に渡すためroutes.rbにはpost 'login/auth'の定義がないといけない。次にloginコントローラのauth。

     def auth
      # paramsで受け取った:nameでCompanyインスタンスを作る。
       company = Company.find_by(name: params[:name])
      # companyインスタンスが作られていて、かつauthenticateできれば
       if company && company.authenticate(params[:password]) then
         #reset_session
         #セッションにcompanyの主キーを入れる。
         session[:company] = company.id
        # :refererにはflash[:referer]が入っているためnewの画面に戻れる。
         redirect_to params[:referer]
       else
         flash.now[:referer] = params[:referer]
         @error = 'ユーザ名/パスワードが間違っています。'
         render 'index'
       end
     end

    redirect_to params[:referer]

の:refererはapp/controllers/staff_controller.rbで flash[:referer] = request.fullpathとしてflashメソッドで定義しているものが次のapp/views/login/index.html.erbで

    <%= hidden_field_tag :referer, flash[:referer] %>

で:refererキーでpostされているものを受け取っている。なので中身はrequest.fullpathということになるがこれはその地点のルートからのパスが戻り値になるので、実際には/staff/newが入っていることになる。つまり、ログイン認証で入れなかった場所にとぶということ。その前の無事パスワード認証をクリアした時点でsession[:company]にさっき作ったcompanyインスタンスのidが格納されている。

セッション

セッションとはユーザ単位でデータを管理するための仕組み。

    session[:name] = value

のようにデータを保存する。sessionにキーを持たせてそこに値を入れる。取り出すときも

    @name = session[:name]

のようにする。セッションの寿命はデフォルトではブラウザを閉じるまでである。

    session[:name] = nil  #特定のキーのセッションを消去
    reset_session         #全てのsession情報を削除

のようにして記録を消すことができる。

テンプレート変数と部分テンプレート

/staff/newでコントローラに行くと改めてフィルターであるcheck_loginedがかけられる。今度はsession[:compaby]を持っているのでそこに格納されている主キーを元にテンプレート変数@companyが作られる。 viewメソッドでは@staff = Staff.newでstaffインスタンスが初期化され、staffs/new.htmlに行き、そこで部分テンプレートの_formが呼び出される。ここが超ハマったところ。

    <%= render 'form', staff: @staff, company: @company %>

ここでとても 重要なのは@company(や@staff)を変数経由で部分テンプレートに渡すということ。

例えば部分テンプレートが

    # app/views/staffs/_form.html.erb
    <%= form_with(model: staff, local: true) do |form| %>
    <div class="field">
    <%= form.hidden_field :company_id, :value => company.id %>
    </div>
    <div class="actions">
        <%= form.submit %>
    </div>
    <% end %>

となっている場合、company.idがいつでも参照できる。もし仮にcompany: @company の定義をせずに部分テンプレートで

    :value => @company.id

のように直接 @companyを参照しようとすると、staffs_controllerのcheck_loginedで@companyを作ってからそのままメインのテンプレートを経由して部分テンプレートまできた場合にはテンプレート変数@companyが生きている。しかしそれをpostした時に@companyの値がnilになってしまう。どのルートを経てもその値を保持するためにはメインのテンプレートで引数に入れておくことが重要になる。なぜnilになるのかはわからない。インスタンス変数を経由してインスタンスの値にアクセスできるのが最初にてviewを描画したときだけ、ということ何だと思うが逆にcompany: @companyならおっけーなの何で?renderが何かしているのかもしれない。

submitするときは

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

のform_withメソッドによりURLとスコープが自動推測される。この場合はurlとして

    action="/staffs"

が自動生成され、formの元として渡しているモデルがメインテンプレートのstaff: @staffによって@staffになっており、@staffはstaff/newを通った時に@staff = Staff.newで初期化されているのでform_withによってcreateアクションが要求される。すでにこの時にはパーシャルに@companyを入れた場合それがnilになっているので考えられる仮説としてはsubmitした後に再度テンプレートが描画されるタイミングがあって、その際に@companyを格納している変数を参照することはできるが、コントローラーで定義したテンプレート変数は参照することができない、ということが起こっているのかもしれない。よくわからないが、コントローラで生成したテンプレート変数はメインのテンプレート上で別の変数に格納した上で部分テンプレートに渡すのが重要だということがわかった。

感想

やっぱりコントローラーのことが全然わかってない。やっぱり『Ruby on Rails5 アプリケーションプログラミング』の第6章早くやるべき!

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の数値に対応する値をとります。

今日の感想

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

『Ruby on Rails5 アプリケーションプログラミング』学習日誌 6

前書き

今日も第5章モデルについて学習していきたいと思います。あと、ここに書いてあることは単なる学習記録なので事実とは関係がない場合があります。ご了承ください。

ページ範囲

p.291-311

学習ポイント

コールバックについて

あるモデル操作に対してそれに付随する処理はコールバックとして定義することで同じようなコードがモデルやコントローラに分散するのを防ぐことができる。

マイグレーションについて

テーブルレイアウトを作成・変更するためにマイグレーションという機能がある。この機能は開発の途中での構成の変化にも対応できる。マイグレーションファイルには必ず頭にタイムスタンプが振られる。rails側ではこの値を元にそれがすでに実行済みかどうかを判断している。railsではこのマイグレーションファイルをタイムスタンプによって一元的に管理しているため、テーブルの状態を特定の時点まで戻したりすることが可能。バージョンの管理ができるためとても便利。

マイグレーションファイルの作成について

ターミナルから下記のようにコマンドすることでマイグレーションファイルを自動的に作成することができる。

rails generate migration ChangePhoneOfStaffs

20180813110400_change_phone_of_staffsのようなマイグレーションファイルができる。20180813110400の部分がタイムスタンプ。ここではinteger型で定義してしまった電話番号フィールドをstring型に変更したいので下記のようにファイルの内容を変更する。

class ChangePhoneOfStaffs < ActiveRecord::Migration[5.2]
  def change
    change_column :staffs, :phone, :string
  end
end

そして

rails db:migrate

マイグレーションを実行すると実際にstaffsテーブルのphoneカラムのデータ型がstringになる。これはdb/schema.rb内の記述が書き換わっていることから確認できる。
また、コマンドで指定するファイル名について若干の命名規則があり、

Add〇〇Toテーブル名 〇〇:データ型


のようにすることで生成されるファイルの内容が下記のようになる。

class Add〇〇ToStaff < ActiveRecord::Migration[5.2]
  def change
    add_column :テーブル名, :〇〇, :カラム名
  end
end

感想

マイグレーションは単にテーブルを簡単に用意するためのものだと思っていたので仕組みを知ることができ良かった。自分で考えるテーブルの設計は基本間違っているのでどんどん便利に使えるようになりたい。

『Ruby on Rails5 アプリケーションプログラミング』学習日誌 5

前書き

今日も第5章モデルについて学習していきたいと思います。 ソースコードにもコメントで気づいたことについて注釈入れてます。あと、ここに書いてあることは単なる学習記録なので事実とは関係がない場合があります。ご了承ください。

ページ範囲

p.268-290

学習ポイント

アソシエーションについて

アソシエーションとはテーブル間のリレーションシップをモデル上の関係として操作できるようにする仕組みのこと。主キーはそのテーブル上の各データに一意についている番号のことで通常id。そして別のテーブルでその各データをそのテーブルのデータに関連づけたい時には外部キーと呼ばれるものを利用する。外部キーはrailsでは「参照先のモデル名(頭は小文字)_id」の形式で命名するという規則がある。外部キーは言ってみれば別のテーブルのデータが入っている変数のようなものだと理解している。アソシエーションを利用することで

@book = Book.find(1)
@reviews = @book.reviews

のような書き方で特定のデータに関連する別のテーブルのデータを取得することができる。
これの二行目は

@reviews = Review.where(book_id: @book.id)

に相当する。2番目の方から考える。Reviewモデルから.where メソッド(ActiveRecord::Relationのメソッド?)で第一引数にそのモデルの属性(book_id:)をとり第二引数にインスタンスのid属性を指定している。それらが==になるのを条件に(where)個別のレコードを@reviewsというテンプレート変数に(配列で?)格納している。
対して一個目の書き方では、@bookインスタンスからreviewモデルのメソッドが使えるようになっており、そのインスタンスに従属する値をにアクセスできるようになっている。これも外部キーの命名規則を定めていて、なおかつアソシエーションを定義しているのでこのようなことが可能になる。テーブルのリレーションで重要な中間テーブルの命名は参照先のテーブル名をアルファベット順に_で繋げたものとするという規則がある。

belongs_to アソシエーション

belongs_toメソッドの引数にはモデル名(頭は小文字)を渡す。これが参照先のテーブルを取得するためのアクセサメソッドにもなる。アクセサメソッドとは内部的に持っている値(変数)を外部から取得するためのメソッドのこと。belongs_toメソッドで参照先のモデルを定義することで参照元のモデルから参照先のモデルのメソッドを使うことができるようになる。

has_many アソシエーション

belongs_toメソッドのみでは参照元→参照先という一方通行の関係しか定義できなかったがhas_manyメソッドを使うことで1対多の双方向の関係を定義することができる。has_manyメソッドでは参照先である引数のモデルを複数形で記述する。has_manyを定義したモデルから参照先のモデルをメソッドとして呼び出すときも複数形にする。戻り値も複数あることが前提となるため、配列で返ってくる。

has_one アソシエーション

has_oneメソッドでは1対1の関係を表現できる。主たるモデルにhas_oneを定義し、従たるモデルにbelongs_toを定義する。引数は単数形とする。1対1の関係では主従関係がわかりにくいがどちらかは0or1になる場合があるのでそちらを従にする。

has_and_belongs_to_many アソシエーション

多対多の関係を表現することができる。リレーショナルデータベースでは多対多の関係を表現する方法が無いのでそれぞれのモデル名を複数形で_で繋げた名前の中間テーブルを用意する。中間テーブルにはそれぞれの外部キーだけ用意して置くことで、DB上の全部の関係を1対多までにとどめておくことができる(主キーもなし)。なお、参照元のテーブルには外部キーは必要ない。中間テーブルの名前を参照先のテーブル名を_で繋いだものにすることによってそれらの中間テーブルであることを表現することができるため。また中間テーブルはDBでの表現方法として用意したものに過ぎないので、モデルを作る必要がない。それぞれの元のモデルにおいてはhas_and_belongs_to_manyを定義することで関係を表現する。多対多の関係には主従関係がないので、それぞれのモデルに記述する。引数にとるモデルは複数形になる。これだけでモデル上では多対多の関係を作ることができる。

has_many through アソシエーション

has_and_belongs_to_manyメソッドでは中間テーブル以上の意味をもつ中間テーブルを扱うことができない。(中間テーブルに命名規則が必要になるため。)例えばbooks, users, reviewsのテーブルがあったとして、books↔︎reviewsは1対多、users↔︎reviewsも1対多、books↔︎usersは多対多の関係になる。このような関係に置いてreviewsは自然とbooks↔︎users間の中間テーブルの役割を果たしているがそれ以上にreviewを管理するテーブルとしての意味が強い。reviewsテーブルもモデルとしてアクセスできる必要があるため名前を変えることができない。そうした場合に利用できるのがhas_manyメソッドの第二引数にthroughをとる方法。

class User < ApplicationRecord
  has_many :reviews
  has_many :books, throuth: :reviews
end

のようにhas_many :reviewsでreviewとの関係を定義した上でhas_many :books, throuth: :reviewsのようにreviewsを中間テーブルに見立てたbooksとの多対多の関係を定義する。このようにすることによってuserモデル↔︎bookモデル間のメソッドの呼び出しを直接行うことができる。

アソシエーションで追加されるメソッドについて

上記のメソッドでアソシエーションということはモデル新しいメソッドを追加することを意味する。例えばbelongs_toで宣言したモデルからアクセサメソッドとして参照元のモデルを呼び出すことができるのもその一つ。他にもempty?やbuildというメソッドもアソシエーションから生まれる。@book.reviews.buildのように使う。このbuildは新しいモデルを生成するメソッドでこの場合は新しいreviewを一つ作ることになる(保存はされない)。@bookに対して、reviewが一つ増えることになるので1対多か多対多の関係を前提としている。どの関係からどんなメソッドが生まれるのかはそれぞれ違う。これらのメソッドは@book.buildのようには使えない(bookモデルが新しく作られたりはしない)。アソシエーションを定義した上で@book.reviewsを前提として初めて使用することができる。たぶん@book.reviewsとすることでreviewモデルのメソッド + buildなどの新しいメソッドを取得する感じになるのだと思う。(ただ、pryでempty?メソッドを探そうとしたけど見つけられなかった。探し方が悪いんだと思うけど。)いずれにせよ、buildやempty?などのメソッドはアソシエーションという関係性のなかで生まれるメソッドということができるようだ。ちょっと注意が必要そう。

ポリモーフィック関連

一つの子モデルが複数の親モデルをもつ関係性のこと。このような場合は子モデルのテーブルに〇〇_typeと〇〇_idという二つのカラムを用意する。これによって親モデルと外部キーを分けて管理することができる。モデル側の定義としては親モデルでasオプション付きのhas_manyメソッドを宣言し、子モデルでpolymorphicオプション付きのbelongs_toメソッドを宣言することでポリモーフィック関連を定義することができる。

アソシエーションで利用できるオプション

上記のポリモーフィック関連以外にもアソシエーションで利用できるオプションはたくさんある。これらをうまく使うことによってモデルによるデータの扱いがすごく簡単になるようだ。本では表でまとめられているので実際の開発で使えそうなのがあったら積極的に使っていきたい。

よく使うのによく忘れるコマンド(メモ)

PRAGMA TABLE_INFO(TABLE_NAME);

sqliteカラム名を調べるのに使う

rails db:fixtures:load

テスト用データをデータベースに入れるのに使う。
scaffoldで生成されたテストデータには主キーの指定がない。そのままloadするとなんか変な数字が入るので注意。

今日の感想

ついにアソシエーションで追加されるメソッドについて学習することができて嬉しい。前によくわかってないのに使おうとしてハマったことがあったので。モデルの関係性のなかで新しいメソッドが使えるようになるという考え方が面白いと思う。それ以外でもモデルで不思議だったところが前に比べてかなり知ることができて嬉しい一日だった。

『Ruby on Rails5 アプリケーションプログラミング』学習日誌 4

前書き

今日も第5章モデルについて学習していきたいと思います。 ソースコードにもコメントで気づいたことについて注釈入れてます。

ページ範囲

p.229-267

before_action メソッド, only: アクション

コントローラで上記のように書くとアクションに先立ってメソッドを実行するという意味になる。この場合のメソッドをフィルターと呼ぶ。そしてこのメソッドも同じコントローラ内で定義するが、この場合は意図せず呼び出されるのを防ぐためにprivateメソッドにするのが普通である。

ルート定義(基礎編復習)

ルートはurlパターンとhttpメソッドとそれを呼び出すコントローラーのアクションを指定する必要があるが、(アクションメソッドがない場合は直接viewを返す)フィルターの場合はそれの親のメソッドがルートに影響する。

トランザクション処理とは?

いくつかの処理をひとまとめにして処理が全部成功しなかった場合はをロールバックしてはじめの状態に戻し全ての処理をなかったことにするメソッドのこと。transactionメソッド。トランザクションが確定することをcommitと呼ぶ。

例外処理について

raiseによって意図的に例外を発生させることができる。raiseメソッドには引数としてエラーメッセージを渡すことができる。また例外が発生したときはrescueメソッドを用意しておくとrescue節までジャンプし、その時点から処理を続行することができる。rescue => e で変数eに例外自体をオブジェクトとして格納することができる。この場合の => は少し特殊な書き方に見える。rescueのメソッドなのかな?eはインスタンスメソッドとしてmessageやbacktraceなどのメソッドを持っている。この場合のmessageでは対象の例外であるraiseで引数として渡したメッセージが返ってくる。

form_withについて

qiita.com


ここにあるようにform_tagとform_forはrails5.1で非推奨となっているようです。scaffoldで部分テンプレートを(_form.html.erb)生成したときform_withで書かれていて、バージョン5.0.1を前提としている本書のサンプルコードでうまく動かなくて軽くはまりました。ここで ”利用バージョンは本書検証バージョンに揃えることを強くおすすめします。” を無視したことを思い出した。自分が悪いが、form_withは前の二つに比べて賢くて便利そうでよかった。 ** 検証機能
Active Model にはValidation機能がある(ActiveModel::Validations::〇〇Validatorクラス)。モデルクラスに検証のルールを設けて、不正なデータをDBに入る前にはじくことができる。validatesメソッドでデータ型やlength、正規表現などを指定してデータの形を限定することができる。

非DB系のモデル

ActiveModel::Modelモジュールを利用することでDBと関係を持たないモデルを作ることができる。例えばdbに関係ない検証などと関連づけて使用できる。モデルクラスを定義するときはActiveModel::Modelをincludeして必要な項目をアクセサメソッドで定義する必要がある。

感想

例外処理を学習している時にrubyは例外自体をオブジェクトとして扱っているということを知った。なおかつそいつが例外についての情報を持っていて、聞くと教えてくれるなんてなんか良い。rubyでは全てがオブジェクトらしいので当然なのかもしれないけどやっぱり面白いと思った。あと、saveやsave!が戻り値を返してくれて条件分岐できるってのも気持ち良い。「全ての式が値を返す」という基本思想があるようで、最後に評価した値が戻り値になる。わざわざreturnを書かないという習慣もこれに基づいているということを知りました。

『Ruby on Rails5 アプリケーションプログラミング』学習日誌 3

前書き

今日は第5章モデルについて学習していきたいと思います。
ソースコードにもコメントで気づいたことについて注釈入れてます。

ページ範囲

p.200-229

ポイントまとめ

ActiveRecord::Relation

この章について考えるにあたって前提となるActiveRecord::Relation
について知りたい。ActiveRecordrailsが標準のO/Rマッパー。O/Rマッパーとはobjectとrelational dbの橋渡しをするためのライブラリ。ActiveRecordがリレーショナルモデルであるdbをオブジェクト化してくれることによってそれぞれのレコードをカラム(属性)によるプロパティを持つオブジェクトとして扱うことができる。ActiveRecordがどこから来たのかというとまず、各モデルはApplicationRecordを継承している。そしてこのApplicationRecord
はmodels/application_record.rbで ActiveRecord::Baseを継承している。なので各モデルとそのインスタンスActiveRecordのメソッドを使うことができる。findメソッドやクエリメソッドなどもActiveRecordのメソッドである。下記のようにクエリメソッドはその場でdbにアクセスせず、ActiveRecord::Relationのオブジェクトを返す。そのオブジェクトにさらにクエリメソッドを実行させて、メソッドチェーンを作ることができる。メソッドチェーンを作った場合、合体した条件式を戻り値として返すが、オブジェクトそれ自体は変化しない。想像だと、それぞれのオブジェクトがクエリを生成するメソッドを内部的に持っている。そしてそのメソッドはデータのもつ属性に依っているのでorメソッドなどでActiveRecord::Relationのオブジェクト同士を結合する際にはモデルによる互換性がないといけない。

データ取得の方法ークエリメソッド

railsにはwhere, selectなどのクエリメソッドと呼ばれるメソッドが用意されている。
これらのfindやfind_by, allなどのメソッドとの違いはそれが読まれた時点でデータベースにアクセスしないという点。クエリメソッドの場合はそれが読まれた時点ではActiveRecord::Relationのオブジェクトが生成されてそれがviewなどで実行される時点で初めてまとめてクエリとなってdbにアクセスするイメージ。このようにコードが読まれる時点(オブジェクト化)とdb接続のタイミングがずれていることを遅延ロードと呼ぶ。これによってクエリメソッドを繋げて、簡単に複雑なクエリを作ることができる。このような記法のことをメソッドチェーンと呼ぶ。

RESTfulインターフェイスとは?

RESTとはhttpメソッドを使ってurlで表現されているリソースを操作する方法のことで、RESTfulインターフェイスとはRESTを前提としたルーティングのことである。railsではこのRESTfulインターフェイスを前提とした機能設計がなされているためこれを活用するのが効率が良いとされる。

httpメソッド
  • GET 取得
  • POST 作成
  • PATCH 更新
  • DELETE 削除

RESTfulインターフェイスの定義方法

resourcesメソッドは引数を各リソースとする。リソースとはdbにあるデータなどのことでモデルに対応させて表現する。resources :users など。この一行だけで汎用性の高いルートがたくさん生成される。rails routesコマンドでどんなルートが定義されているのか確認できる。RESTfulインターフェイスを実現するにはルートによってhttpメソッドやurlパターンを定義しなければならない。それがあて初めてルートを通ることができ、ルートの先でparamsメソッドによって値を受け取ることができる。またparamsの引数はリクエスト情報の属性とするが、これはルートで定義するか、もしくはフォームでpostされた際のidかname?どちらかよくわからないけどどっちかを参照してvalueを受け取るようになっている。

感想

特になし

『Ruby on Rails5 アプリケーションプログラミング』学習日誌 2

前書き

ruby on rails 5 アプリケーションプログラミング」(山田 祥寛 著)で勉強しています。今日は第4章の途中から学習するのでその記録です。ソースコードにもコメントの形で学んだことについて注釈入れてます。

学習の範囲

p.177~198

ポイントまとめ

レイアウトテンプレート

レイアウトテンプレートとはアプリ全体を通して共通のデザインを定義するテンプレートのこと。railsにより/app/views/layouts/application.erbが自動で生成されており、これをカスタマイズすることで自由にデザインを決めることができる。全てのページを同じレイアウトにする必要がない場合はコントローラー単位やアクション単位でのレイアウトの変更も可能。例えばコントローラー単位での設定は/app/views/layouts/以下にコントローラー名.html.erbというファイルを用意する。また、コントローラークラスの中で明示的にlayoutメソッドを利用すれば引数のレイアウトを指定することもできる。さらにはコントローラーのrenderメソッドでアクション単位でのレイアウト呼び出しも可能。レイアウトテンプレートにおいてページごとに要素を変更する場合はテンプレート変数が使える。

<title><%= @title || 'Rails' %></title>

のようになるこれはもしテンプレート変数@titleがnilの場合は'Rails'が入るいうこと。これは真偽値が確定した時点で計算をやめてその時点での値を返すというrubyの性質を利用している。また、application.erbの個別のviewが埋め込まれる部分はyieldが当てられているが、これはviewをブロックに見立てているという意味になるのかな?いずれにしろ、ここで各viewの情報が受け渡されていると理解している。またprovideやcontent_forなどのヘルパーを使うことでyeildに値を持たせることもできる。親レイアウトをメソッド定義のようなものとして、子レイアウトをブロックのように捉えると、yeildの働きがわかるような気がする。親レイアウトのyeildの置かれた場所で子レイアウトが実行される。このような仕組みを利用してレイアウトを入れ子にし使用することができる。また親レイアウトには子レイアウトから継承することを明示的に記述が必要になる(条件分岐も必要)。これが無いと子レイアウトがそれを通過してきているのに、無視される。部分テンプレートはある意味レイアウトテンプレートの逆で、そのままだけど、パーツをテンプレート化するもの。ファイル名の頭に_をつけるという命名規則があり、これが無いとrailsが部分テンプレートとして認めない。

部分テンプレート

部分テンプレートはviews配下にコントローラーごととかのディレクトリを用意してまとめて管理すると良いらしい。また、viewファイルから呼び出す時はrenderで呼び出せる。この時viewsからみたパスを'books/book'のように書く。最初どこからの視点なのかわかっていなかったのでエラーを出した。ちなみにディレクトリの移動は mv dir ../ のようなコマンドで配下のファイルごと動かせる。

今日の感想

やるたびに感じることだけれどrailsすごく簡略化がなされているなと思う。少しでも記述を減らせるところはそうしようという考え方徹底しているような気がする。厳密な命名規則を前提としているからできることですね。でも省略しすぎて意味わかんなくならないかな?慣れれば最高なんだろう。命名規則もあったほうが人間にもわかりやすいし。viewは一旦ここまでで明日からはモデルについて学習する。