オライリーの「Head First Rails」で躓いたところ (3章)

2章の補足に引き続き連投。

3章はScaffoldがやってくれていた部分を、1つずつ作りながら追いかける、という構成になっています。楽しくなってきたぞ!

そして、エラーが出たときの苦労も増してきたように思います。

躓いたところ

P108 追加するルート

2章で書いたときと同様、Rails4向けの書き方で書きます。

Mebay::Application.routes.draw do
  get 'ads/new' => 'ads#new'
  get 'ads/create' => 'ads#create'
  get 'ads/' => 'ads#index'
  get 'ads/:id' => 'ads#show'
end

P112 new.html.erb

form_forの呼び方が変わっています。

<%= form_for(@ad,:url=>{:action=>'create'}) do |f| %>

Rails3以降は<%=で始めるように変わったとの事。ややこしい。

form_for - リファレンス - - Railsドキュメント

P123 試運転の結果

本ではテンプレートが見つからない旨のエラーが出ることになっていますが、それとはまた違ったエラー「ActiveModel::ForbiddenAttributesError」が出ました。

調べてみたところ、これはRails4からコア機能として導入されたStrong Parametersって奴らしいです。本の例のように@ad=Ad.new(params[:ad])と一括代入する場合、HTTPリクエストを少しいじると「id」や「updated_at」の値を改変できてしまうという脆弱性((Mass Assignment脆弱性と言うらしい。))が生まれてしまいます。そこで、コントローラ側でホワイトリスト形式でパラメータを検証するようになってるみたい。

ホワイトリストなので、初期状態では何も通してくれません。そのせいでエラーになっていたようです。

Rails4 の Strong Parameters でリクエストパラメータを検証する
Rails4のMass Assignment脆弱性対策のStrong Parametersについて

こちらの記事を参考に、変更。

  def create
    @ad = Ad.new(ad_params)
    @ad.save
  end
  
  private
    # Never trust parameters from the scary internet, only allow the white list through.
    def ad_params
      params.require(:ad).permit(:name, :description, :price, :seller_id, :email, :img_url)
    end

これで、本と同じ「Template is missing」に辿り着けました。

P138 route.rb

たびたび出てきましたが、書き方が変わっています。
今まで通りにmap.connectgetpostに置き換えたらOKかと思いきや、ここでエラー。

No route matches [PATCH] "/ads/37/update"

「PATCH」??
どうやらPATCHリクエストというものが存在するようです。

編集画面のフォームからPATCHリクエストが届いているのに、対応したルートが無いからエラーになったわけですね。

PATCHリクエストって?

ブラウザで開いている編集画面のソースコードを見てみました。以下抜粋。

<form accept-charset="UTF-8" action="/ads/37/update" class="edit_ad" id="edit_ad_37" method="post"><div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="&#x2713;" /><input name="_method" type="hidden" value="patch" />

formのmethod属性では「post」となっていますが、それとは別に「_method」に「patch」が渡っています。何でこんな回りくどいことを?と思って調べたら、納得。

Rails では PUT や DELETE メソッドなどに対応していない Web サーバに対応するために、hidden フィールド()で本来のメソッドを送信しているので、その部分で patch という値が送信されるようになります。アプリケーションコードでは、コントローラで request.patch? で判定することになります。
Rails 4.0 では HTTP の PATCH メソッドで更新する

routes.rbに追加

というわけで、patchリクエストにも対応できるようなルートにします。

  get 'ads/:id/edit' => 'ads#edit'
  patch 'ads/:id/update' => 'ads#update'  

この2つを一番上に追加。これでこのエラーは消えました。

・・・代わりに別のが出ましたが。

ActiveModel::ForbiddenAttributesError が出る

またお前か。

とりあえずcreateのときと同じく、@ad = Ad.find(params[:id])@ad = Ad.find(ad_params)と置き換えてみたところ、「Unknown key: name」というエラーに変わりました。えー。

Strong Parameters are only used when creating/updating database records. They effectively replace the Rails 3 attr_accessible calls in your models.
Rails 4 Strong Parameters Unknown Key

Strong ParametersはDBへの書き込みが発生する場合(=creating/updating)のみ効くってことですね。なるほど。

その辺りを踏まえて、ads_controller.rbのupdateメソッドはこんな感じに。

  def update
    @ad = Ad.find(params[:id])
    @ad.update_attributes(ad_params)
    redirect_to "/ads/#{@ad.id}"
  end

無事、エラーも消えてくれました。よかった。

P148 追加するルート

いつものやつです。
個人的には、ここの命名が気になります。パスが/deleteなのに、メソッド名はdestroy。なぜ?

Mebay::Application.routes.draw do
  get 'ads/:id/edit' => 'ads#edit'
  patch 'ads/:id/update' => 'ads#update'  
  get 'ads/:id/delete' => 'ads#destroy'
  get 'ads/new' => 'ads#new'
  post 'ads/create' => 'ads#create'
  get 'ads/' => 'ads#index'
  get 'ads/:id' => 'ads#show'
end

ルートもだいぶ長くなってきたところで、3章が終わりました。とうとうMeBayアプリケーションともお別れですね。