忘備録とマイルストーン

WEBエンジニアを目指す20代男性の奮闘記

プログラミング初心者がWEBサービスを作ってみる―アプリ開発編⑤

いよいよメインとなる採点機能の実装に入るが、開発がなかなか難航している。ようやく”オリジナル”らしいことをしていることもあり、予定していたより大きく後れをとってしまった。コードを書く時間<<<エラー文を見て調べている時間、になりつつもあり、仕様の見直しもすることになりそうだ。

 

 

採点機能を実装する

採点機能とはいうものの、今回制作しているWEBアプリでは実は2段階に分けて実装されている。①チームの総評を記録するmatch_ratingsと②選手個人に紐づけられるrating_detailsの両者で採点機能を担っているのだ。もう一度データベース図を確認しておこう。

f:id:sante51:20190218095239p:plain

データベース図 v0.1

match_ratingsはlineupsとusersを、rating_detailsはmatch_ratingとplayersをそれぞれ親に持つ構造になっていることが分かる。将来的に多数の試合から選手個々の採点平均値を出すようなことを見込んでのことだ。

①match_ratings

ここからユーザーが操作できるデータになってくるが、そこまで難しい実装ではないはず。モデル作成→コントローラー(show, createアクション)作成→ルーターに追加→ビュー(lineups/show)にフォーム画面を追加

M: MatchRating, C: match_ratings#show, create, V: match_ratings/show, lineups/show

showアクションについては②rating_detailsができてからの編集になる。

 

M: MatchRating

特筆することはない。丁寧にマイグレーションファイルを編集し、関連モデルにアソシエーションを追記。あとはモデル自身にバリデーションをかけておく。

M: MatchRating作成のマイグレーションファイル

  create_table :match_ratings do |t|
    t.references :user,        foreign_key: true
    t.references :lineup,      foreign_key: true
    t.integer    :team_rating, default: 0
    t.string     :team_review

    t.timestamps
  end

 

C: match_ratings#show, create

before_actionを追加しログイン制限をかける。2つのアクションを定義し、特にcreateにはcreatestrong parametersで不正入力を防止。データベース保存の成功で分岐させ、成功にはトップページのurlを、失敗にはlineups/showのビューを指定しておく*1

C: match_ratings

class MatchRatingsController < ApplicationController
  before_action :require_user_logged_in, only:[:create]
  
  def show
    @match_rating = MatchRating.find_by(params[:id])
  end
  
  def create
    @match_rating = current_user.match_ratings.build(match_rating_params)
    
    if @match_rating.save
      flash[:success] = "採点を作成しました"
      redirect_to root_url
    else
      flash.now[:danger] = "採点の作成に失敗しました"
      @lineup = Lineup.find_by(params[:lineup_id])
      render "lineups/show"
    end
  end
  
  private
  
  def match_rating_params
    params.require(:match_rating).permit(:user, :lineup, :team_rating, :team_review)
  end
end

redirect_toとrender_toがごっちゃになってきたのでここで一度整理。

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

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

Railsドキュメントだとちょっとredirect_toが分かりづらいので補足。

railsのrenderとredirect_toの違い - Qiita

・redirect_to:HTTPメソッドを再送信する=基本的にurl(か同義の_path)を指定

・render_to:ビューを返す=ビューを指定

ということだろうか。納得。

 

V: lineups/show

match_ratings#createのフォームは予定通りV: lineups/showに作成する。newアクションの代わりにインスタンス変数@match_ratingsを1行目できちんと用意してやる。*2

V: lineups/show

<%  @match_rating = current_user.match_ratings.build %>
<%= form_for @match_rating, url: lineup_match_ratings_path do |f| %>
<%= render "layouts/error_messages", model: f.object %>
  <%= hidden_field_tag :lineup_id, @lineup %> 
  <%= f.select :team_rating, {"----":0, "4.0":1, "4.5":2, ... , "8.5":10} %>
  <%= f.text_field :team_review %>
  <%= f.submit "確定", class: 'btn btn-primary btn-sm' %>
<%  end %>

これで実際にテスト画面からチームの採点ができるはずだが…

確定ボタンを押すと、そこには"採点の作成に失敗しました"の表示と、"Match ratings lineup must exist"のエラーメッセージが。

 

②の前に詰まってしまったぞ

ここで本格的に詰まってしまった。parametersにはしっかりlineup_idが入っている(5行目)ように見える。

layouts/applicationに用意しておいたデバッグ用の<%= debug(params) if Rails.env.development? %>

--- !ruby/object:ActionController::Parameters
parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
utf8: ""
authenticity_token: R/WxIrf3WnBTwJ/o+VvtAIBlPX44tHNLEF8hTXLKFzDOe7jZrSG664OLudUgamzBAyFKy1Lmj1Lrj4vbqMJCOg==
lineup_id: '1' 
  match_rating: !ruby/object:ActionController::Parameters
    parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
      team_rating: '0'
      team_review: ''
    permitted: false
  commit: 確定
  controller: match_ratings
  action: create
permitted: false

hidden_field_tagが間違っているのかと疑い思いつく限り試してみるのだが不発。ここで本格的に詰まってしまった。

 

parametersへの理解を深めよう

そもそもparamsについて無知(strong paramsの時少し学んだが)なので復習。

Action Controller の概要 - Rails ガイド

Railsに限らず、一般にWebアプリケーションでは2種類のパラメータを扱うことができます。1番目は、URLの一部として送信されるパラメータで、「クエリ文字列パラメータ」と呼ばれます。クエリ文字列は、常にURLの"?"の後に置かれます。2番目のパラメータは、「POSTデータ」と呼ばれるものです。POSTデータは通常、ユーザーが記入したHTMLフォームから受け取ります。これがPOSTデータと呼ばれているのは、HTTP POSTリクエストの一部として送信されるからです。Railsでは、クエリ文字列パラメータの受け取り方とPOSTデータの受け取り方に違いはありません。どちらもコントローラ内ではparamsという名前のハッシュでアクセスできます。

なるほど。Railsでは表面上違いはないけれど、#show, createで利用していたparamsは、厳密に言えば別物ということになる。

改めて先ほどのparametersを見てみると、ここで取得しているlineup_idはどうやらURLの一部として送信されるクエリ文字列パラメータに過ぎず、肝心のmatch_ratingsのフォームには何も入っていない。(このあたりhidden parametersについて理解不足だ)

ならば、ということで強引だがC: match_ratingsで直接lineup_idを入力してやる。

C: match_ratingsを再編集(抜粋)。3行目を追加。

  def create
    @match_rating = current_user.match_ratings.build(match_rating_params)
    @match_rating.lineup_id = params[:lineup_id]

再度項目を入力して送信してみると、無事成功!これで何とか①チーム採点機能match_ratingsを実装することができた。

 

次回予告

思わぬところで貴重な時間を喰ってしまったが、何とか選手採点機能へと進むことができた。しかしここで理想と現実のギャップを目の当たりにする。一括登録機能の実装を前に、またGoogleと格闘する日々が続くのだろうか。次回、苦難のアプリ開発編⑥に続く。

((検索履歴がエラー文ばかりになってきたなあ…))

*1:ミス1render "lineups/show"でインスタンス変数の渡し忘れ

*2:ミス2 インスタンス変数の用意忘れ。いつもはnewアクションでやることだからつい…