映画レビューサイトのコード実装
トップページ
トップページでは、スクレイピングで追加した作品を、追加された順に20件表示する。
コントローラで最新順の作品データを取得
最新順にデータを取得するにはどうしたらいいのか?
ActiveRecordには順番を指定するorderメソッド
、取得する件数を指定するlimitメソッド
がある。
orderメソッド
orderメソッド
は、データベースからデータ取得時にデータをどう並び替えるかというのが指定できる。
例えば、「id順に並べたいとき」や「作成日順に並べたい」ときに使う。
引数には並び替える基準のカラムと、DESC(降順)かASC(昇順)のどちらかの2つを指定することにより順番を指定して取得することができる。
1 |
モデル名.order('カラム名 順序')
|
実行例
実行例 | 詳細 |
---|---|
User.order('id DESC') | idを降順でユーザデータ取得 |
User.order('id ASC') | idを昇順でユーザデータ取得 |
limitメソッド
limitメソッド
はデータをいくつ取得するかというのが指定できるメソッドで、
引数には取得したい件数を指定する。
1 |
モデル名.limit(取得件数)
|
実行例
ユーザを10件取得する
1 |
User.limit(10)
|
問題1:app/controllers/products_controller.rbのindexアクションに、productsテーブルから最新順に作品データを20件取り出す処理を書く
作業ファイル:app/controllers/products_controller.rb
トップページに作品が表示されるようになったら成功。
トップページの作品一覧でタイトルを表示する
画像は作品それぞれの画像になっているが、作品タイトルがすべて「タイトル」になっている。このままでは作品タイトルがわからないので現在、「タイトル」となっているところをそれぞれの作品名にしたい。
編集するファイル
この作品のビューはRailsアプリケーションのディレクトリのどこにあるのか。
コントローラはproducts_controller
なので「mooovi」>「app」>「views」>「products」のフォルダの中にビューのファイルが入っている。
- mooovi
- apps
- views
- products
- index.html.erb
- search.html.erb
- show.html.erb
- reviews
- new.html.erb
- views
- apps
productsフォルダの中にはビューのファイルが2つ入っている。この中でトップの作品一覧を表示しているビューはindex.html.erb
。
このファイルを編集して「タイトル」の部分にそれぞれの作品の作品名を表示させるようにする。
問題2:app/views/products/index.html.erbファイルを編集して作品タイトルを表示させる
作業ファイル:app/views/products/index.html.erb
作品ごとに作品タイトルが表示されるようになったら成功。
作品ページ
トップページの作品をクリックしたら、作品ページへリンクする。
作品の情報を取得
まずは、コントローラから編集する。
作品ページを扱うアクションで作品のidを受け取り、そのidにあたる作品情報をデータベースから1件取得する。
idに当たる作品情報を1件取得するメソッドが、findメソッド
。
findメソッド
findメソッドは引数に指定したidにあたる作品情報を1件だけ取得する。
もし、そのidにあたる作品が存在しない場合、エラーが発生する。
idが5のユーザを取得する
作品ページで呼び出されるアクションを探す
トップページの作品をクリックしたあと、作品ページに遷移する。その際、どんなHTTPリクエストが呼ばれているのか。完成版のmoooviでトップページの作品をクリックしてそのURLを見てみる。
上の画像では作品ページのURLはmooovi.tech-camp.in/products/20となっている。mooovi.tech-camp.inの部分はサイトのホスト名なので大事なのはパスの「/products/20」。このページを表示するアクションを調べるためにルーティングを見て行く。
rake routesコマンド
rake routesコマンドはそのアプリケーションでアクセスできるHTTPリクエストの一覧を見ることのできるコマンド。このコマンドを使うとHTTPリクエストのメソッド(GET, POSTなど)とパス、それに対応するコントローラとアクション名が確認できる。
moooviのディレクトリでrake routesコマンドを使ってみる
1 2 3 4 |
$ cd ~/projects/mooovi
# 「mooovi」ディレクトリに移動
$ bundle exec rake routes
|
以下のようにターミナルに表示される。
1 2 3 4 |
Prefix Verb URI Pattern Controller#Action
search_products GET /products/search(.:format) products#search
product GET /products/:id(.:format) products#show
root GET / products#index
|
いろいろと情報が表示されているが今のところは5行目の以下の文だけ見てみる。
5 |
product GET /products/:id(.:format) products#show
|
GETとはHTTPリクエストの種類のこと。
そのあとに続いて/products/:id(.:format)と表示されている。これは先ほどの作品ページのURLにあった「/products/20」と対応している。
/products/:id(.:format)にある:idとは任意の作品idということ。上の例ではこの作品idは20。
1 2 |
/products/:id(.:format)
# :idは作品ページで表示される作品のidが該当する(例: 20)
|
ターミナルの一番右にはproducts#showと表示されている。これはproductsコントローラのshowというアクションが呼ばれるという意味。/products/:id(.:format)のURLにGETでHTTPリクエストが送られると呼ばれる。
1 2 |
products#show
# productsコントローラのshowアクションが呼ばれる
|
つまり、rake routesコマンドで表示された文は以下のような意味を持っている。
ではこのHTTPリクエストによって送られたidの取得方法を見ていく。
params[:id]
HTTPリクエストが送られると、パス(/products/20)から作品idを取得することができる。
作品idはパラメーターとして自動的にparamsというハッシュ型の変数に格納されコントローラに送られる。またこの時キーは/products/:idのidになり、バリューはアクセスしたパスの:idの箇所に一致する数字になる。
/products/20にアクセスした場合paramsという変数は以下のようになる。
1 2 |
puts params
# { id: 20 }
|
ハッシュから指定したキーのバリューを取得したい際は、以下のようにする。
1 2 |
puts params[:id]
# 20
|
作品ページで呼び出されるアクションを編集する
showアクションに作品ページで表示する作品情報を取得する処理を追加する。
問題3:products_controller.rbのshowアクションに、productsテーブルから該当するidの作品情報を取得し`@product`の変数へ代入する処理を書く
作業ファイル:app/controllers/products_controller.rb
ヒント:パス/products/:id内の:idにあたる数字(作品のid)はコントローラ内でparams[:id]とすると取得できる
検索機能
いよいよレビューを投稿する機能を作る。この映画レビューサイトではまず、投稿したい作品を検索して選び、作品名で検索できる検索機能を実装する。
コントローラで検索結果の作品データを取得
入力したキーワードを含んでいる作品を検索する。
作品タイトルにキーワードが含まれている作品を取得するにはLIKE句
というものを使う。
LIKE句
LIKE句は、曖昧(あいまい)な文字列の検索をすることができるもので、whereメソッドと一緒に使う。
曖昧な文字列の検索とはどういうことか。
例えば、1文字目に'a'という文字列が入ったデータや最後の文字に'b'が入っているデータ、文字列の途中に'c'が入ったデータなどを検索したい時に、曖昧文字列というものを使って検索すること。
曖昧文字列について
文字列 | 意味 |
---|---|
% | 任意の文字列(空白文字列含む) |
_ | 任意の1文字 |
実行サンプル
実行例 | 詳細 |
---|---|
where('title LIKE(?)', "a%") | aから始まるタイトル |
where('title LIKE(?)', "%b") | bで終わるタイトル |
where('title LIKE(?)', "%c%") | cが含まれるタイトル |
where('title LIKE(?)', "d_") | dで始まる2文字のタイトル |
where('title LIKE(?)', "_e") | eで終わる2文字のタイトル |
検索したときに呼ばれるメソッド
moooviの右上の投稿するボタンから投稿画面に移動して、実際に作品を検索してみる。
すると、検索キーワードが消えますが作品は表示されない。まだ検索機能を実装していないので当然である。検索したあとのURLを見てみる。
URLが/products/search?utf8=✓&keyword=美女
になっている。rake routesコマンドでこのURLがどのコントローラのアクションに対応しているか確認してみる。
1 |
$ bundle exec rake routes
|
1 2 3 4 |
Prefix Verb URI Pattern Controller#Action
search_products GET /products/search(.:format) products#search
product GET /products/:id(.:format) products#show
root GET / products#index
|
2行目の以下の部分の表示が/products/search?utf8=✓&keyword=
に対応している文。
2 |
search_products GET /products/search(.:format) products#search
|
productsコントローラのsearchアクションが呼ばれていることがわかる。
productsコントローラのsearchアクションを編集する
productsコントローラのsearchアクションに入力された検索キーワードで曖昧検索をして、その結果を返す処理を追加する。
問題4:products_controller.rbのsearchアクションに、productsテーブルのタイトルからキーワードで検索した作品データを`20件`取り出し、`@products`変数に入れる処理を書く
作業ファイル:app/controllers/products_controller.rb
ヒント:フォームに入力した文字はコントローラ内でparams[:keyword]で取得する。これは、検索バーを表示するinput要素のname属性の値
ヒント:取得するデータ数を制限するにはlimitメソッドを使う
これでレビューを投稿する準備が整った。
投稿機能
続いて、レビュー投稿画面をつくるためにReviewモデルとapp/views/reviews/new.html.erbに入力フォームを作成していく。
現段階では投稿画面に遷移してもReviewモデルがないためにエラー画面が表示されてしまう。
レビューのモデルReviewを生成
レビューのモデルを生成する。モデル名はReview
にする。必要なカラムは何か?レビューの投稿画面から必要なカラムを考える。入力する箇所はニックネームと評価(10段階評価)とレビューの内容である。この他に必要な情報はどの映画のレビューかという情報。これらをカラムとして保存する。
カラム名 | データ型 | 用途 |
---|---|---|
nickname | string | ニックネーム |
rate | integer | 評価 |
review | text | レビュー |
product_id | integer | 作品id |
どの映画のレビューか、という情報はProductモデルのidがあれば十分である。よって作品idとしてproduct_id
をカラムにする。
このカラムを持ったモデルReviewを作成する。
問題5:Reviewモデルを作成し、必要なカラムを持ったテーブルを作成する。
ヒント:bundle exec rake db:migrateコマンドでマイグレーションファイルの実行
問題ができたら、実際にreviewsテーブルが作成されているか確認してみる。
投稿フォームを作る
レビューを投稿するためのフォームを作っていく。フォームに入力したデータは検索フォームと異なりReviewモデルのインスタンスとしてreviewsテーブルに保存する。その時はform_for
ヘルパーメソッドを使う。
また、ルーティングを簡単に作成するためにresources
メソッドを使って定義する。さらに今回は、どのproductsに対するreviewなのかをパスから簡単に判別できるようにresourcesメソッドをネスト
させる。
今、投稿画面にアクセスすると「Routing Error」が表示される。これはまだ投稿画面のルーティングを設定していないためである。これからルーティングを設定していく。
1 2 3 4 5 6 7 8 9 10 11 12 |
TechReviewSite::Application.routes.draw do
get 'products/:product_id/reviews/new' => 'reviews#new'
post 'products/:product_id/reviews' => 'reviews#create'
resources :products, only: :show do
collection do
get 'search'
end
end
root 'products#index'
end
|
これで投稿画面のルーティングを指定することができた。
ただこのように パス と アクション を自分で考えて定義していくのは冗長であるので、resources
というメソッドを使ってルーティングを生成していく。
resourcesメソッド
resourcesメソッドに対象となるリソース名(コントローラ名)を指定するだけで、Railsのリソースの7つのアクションで記載した7つのアクション名に対してのルーティングが自動的に設定される。
1 2 |
Rails.application.routes.draw do
resources :books
|
ターミナルにて bundle exec rake routes
コマンドを実行すると以下のように7つのルーティングが設定されているのが確認できる。
1 2 3 4 5 6 7 8 9 |
Prefix Verb URI Pattern Controller#Action
books GET /books(.:format) books#index
POST /books(.:format) books#create
new_book GET /books/new(.:format) books#new
edit_book GET /books/:id/edit(.:format) books#edit
book GET /books/:id(.:format) books#show
PATCH /books/:id(.:format) books#update
PUT /books/:id(.:format) books#update
DELETE /books/:id(.:format) books#destroy
|
またcontrollerでbefore_action
の編集をした時と同様に、resourcesメソッドでもexceptやonlyオプションを組み合わせて特定のアクションを指定することができる。
1 2 3 4 5 6 7 8 9 10 11 |
TechReviewSite::Application.routes.draw do
resources :reviews, only: [:new, :create]
resources :products, only: :show do
collection do
get 'search'
end
end
root 'products#index'
end
|
1 |
$ rake routes
|
1 2 3 4 5 6 |
Prefix Verb URI Pattern Controller#Action
reviews POST /reviews(.:format) reviews#create
new_review GET /reviews/new(.:format) reviews#new
search_products GET /products/search(.:format) products#search
product GET /products/:id(.:format) products#show
root GET / products#index
|
このようにresourcesメソッドを使えばRailsの規則に適ったルーティングを定義することができる。
3行目の /reviews/new はreviewの新規作成のページへのパスになる。
しかしこのままではどのproductに対するreviewなのかということを表す情報がない。これはresourcesメソッドをネストさせる(入れ子構造にする)ことによって取得することができる。
resourcesメソッドのネスト
resourcesメソッドのネストは以下のように書く。
1 2 3 |
resources :books do
resources :reviews
end
|
bundle exec rake routesコマンドを実行すると以下のように表示される。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
Prefix Verb URI Pattern Controller#Action
book_reviews GET /books/:book_id/reviews(.:format) reviews#index
POST /books/:book_id/reviews(.:format) reviews#create
new_book_review GET /books/:book_id/reviews/new(.:format) reviews#new
edit_book_review GET /books/:book_id/reviews/:id/edit(.:format) reviews#edit
book_review GET /books/:book_id/reviews/:id(.:format) reviews#show
PATCH /books/:book_id/reviews/:id(.:format) reviews#update
PUT /books/:book_id/reviews/:id(.:format) reviews#update
DELETE /books/:book_id/reviews/:id(.:format) reviews#destroy
books GET /books(.:format) books#index
POST /books(.:format) books#create
new_book GET /books/new(.:format) books#new
edit_book GET /books/:id/edit(.:format) books#edit
book GET /books/:id(.:format) books#show
PATCH /books/:id(.:format) books#update
PUT /books/:id(.:format) books#update
DELETE /books/:id(.:format) books#destroy
|
では、ルーティングを定義する。
1 2 3 4 5 6 7 8 9 10 |
TechReviewSite::Application.routes.draw do
resources :products, only: :show do
resources :reviews, only: [:new, :create]
collection do
get 'search'
end
end
root 'products#index'
end
|
4行目にあるcollection というのは、Rails7つのアクション以外のアクション名を定義する時に使う。
bundle exec rake routesコマンドでルーティングを確認する
1 2 3 4 5 6 |
Prefix Verb URI Pattern Controller#Action
product_reviews POST /products/:product_id/reviews(.:format) reviews#create
new_product_review GET /products/:product_id/reviews/new(.:format) reviews#new
search_products GET /products/search(.:format) products#search
product GET /products/:id(.:format) products#show
root GET / products#index
|
これでどのproductに対するreviewなのかということがパスから判断できるようになった。
最後にビューのフォームを編集していく。
今回入力フォームから送られたデータはReviewモデルのインスタンスとしてreviewsテーブルに保存する。その時はform_forというヘルパーメソッドを使う。
form_for
form_forは、特定のモデルを編集・追加するためのフォームを生成するヘルパーメソッド。
特定のテーブルにレコードだけを新規作成、更新するときに利用する。
1 2 3 |
<%= form_for(モデルクラスのインスタンス) do |f| %>
…
<% end %>
|
このようにあるモデルのインスタンスをform_forの引数にする。form_tagでは送信先のurlを直接引数に定義した。
1 |
<%= form_tag('/products/search', method: :get) do %>
|
form_forは引数のインスタンスが何も情報を持っていなければ自動的にcreateアクションへ、すでに情報を持っている場合はupdateアクションへ自動的に振り分けてくれる。
次にform_for内でフォームを作るメソッドについて。
form_for内で使うメソッドは、f.htmlタグ名 :カラム名
の形で指定する。
1 |
<%= f.text_field :name %>
|
こちらは、次のようなhtmlに変換される。
1 |
<input id="モデル名_name" name="モデル名[name]" type="text" size="モデルで設定したsize">
|
form_for内におけるメソッドはformに使用するhtmlタグの数だけある。
メソッド | 用途 |
---|---|
f.label | labelのlabelタグを表示 |
f.text_field | textのinputタグを表示 |
f.date_select | モデルで設定したフィールドをselectタグで選べるようにして表示 |
f.check_box | checkboxのinputタグを表示 |
f.number_field | numberのinputタグを表示 |
f.submit | submitのinputタグを表示 |
基本的にhtmlのformを知っていたら推測できるようなメソッドが用意されている。必要なときに都度調べる。
form_forとform_tag
form_for
と form_tag
どちらを使うべきかは、基本的にモデルの有無で判断する。入力フォームで入力するデータのモデルがあれば form_for
を使い、入力するデータが特にモデルを持っていなければ form_tag
を使う。
- form_for: モデルがあるデータを扱うときに使う。(投稿フォームなど)
- form_tag: 単にデータを特定のアクションに送りたい時に使う。(検索フォームなど)
form_for
今回は、レビュー投稿画面views/reviews/new.html.erbに常に新規作成されたReviewクラスのインスタンスを渡す。
フォームに入力された値は、投稿ボタンを押した瞬間にReviewクラスの新規インスタンスにそれぞれの属性の値としてセットされ、インスタンスはそのままreviewsテーブルに保存される。
このような関係を簡単に実現してくれるのがform_for
である。
【例】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<div class="row form">
<div class="col-lg-12">
<%= form_for current_user do |f| %>
<h2>登録情報</h2>
<div class="form-group">
<div class="col-lg-6">
<%= f.text_field :full_name, placeholder: "フルネーム", class: "form-control" %>
</div>
</div>
<div class="form-group">
<%= f.submit "送信", class: "btn btn-primary withripple" %>
</div>
<% end %>
</div>
</div>
|
eachメソッドのように3行目で|変数|
で変数を定義しており、さらに7行目と11行目でf. ~
としている部分で、属性値をセットしている様子が直感的にわかる。このように、「モデルの新規インスタンスに値を追加して保存」したい場合はform_for
を利用する。
form_tag
一方で「となりのトトロ」と入力すると、モデルを介さずにコントローラにてproductsテーブルから該当する情報を引用し、結果をビューに表示した検索機能ではfom_tagを利用した。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
<div id="main_cnt_wrapper">
<div id="yjContentsBody">
<div class="yjContainer">
<span class="yjGuid"><a id="yjContentsStart" name="yjContentsStart"></a><img alt="ここから本文です" height="1" src="http://i.yimg.jp/yui/jp/tmpl/1.1.0/audionav.gif" width="1"></span>
<div id="yjMain">
<article class="section">
<div class="container">
<header class="header header--section">
<h2 class="text-middle">
<i class="icon-movie color-gray-light"></i>投稿
</h2>
</header>
<div>
<ul class="listview js-lazy-load-images">
<li style="margin-bottom: 15px">
<a class="listview__element--right-icon" href="#">
<div class="box">
<div class="box__cell w80">
<div class="thumbnail thumbnail--photo">
<div class="thumbnail__figure" style="background-image: url(<%= @product.image_url %>);"></div>
</div>
</div>
<div class="box__cell pl1em">
<h3 class="text-middle text-break color-sub">
<%= @product.title %>
</h3>
<p class="text-xsmall">
</p>
<p class="text-xsmall opacity-60"></p>
</div>
</div>
</a>
</li>
</ul>
</div>
</div>
<%= form_for [@product, @review] do |f| %>
<div style="margin: 8px 0">
<%= f.label :nickname, style: { 'margin-right' => 8 } %>
<%= f.text_field :nickname, placeholder: 'nickname', value: '' %>
</div>
<div style="margin: 8px 0">
<%= f.label :評価, style: { 'margin-right' => 8 } %>
<%= f.number_field :rate, placeholder: '評価', value: 1, max: 10, min: 1, html: { class: "search__query", style: 'text-align: right;' } %>
</div>
<div style="margin: 8px 0">
<%= f.text_area :review, placeholder: 'レビューを書いてね!', style: 'width: 100%;height: 300px;' %>
</div>
<div class="row">
<div class="col10 push1">
<%= button_tag type: "submit", class: "btn btn--block" do %>
投稿する<i class="icon-arrow-right"></i>
<% end %>
</div>
</div>
<% end %>
</article>
</div>
<div id="yjSub">
|
37行目のform_forの引数が配列になっている。配列の1つ目の要素のインスタンスのidがパスの:product_id
に入る。今回はパスのネストが2段階なので、form_forの引数のインスタンスは2つ。つまり、resourcesメソッドを2回ネストさせた場合、form_forの引数のインスタンスは3つになる。
resourcesメソッドを2回ネストした場合
1 2 3 4 5 |
resources :users do
resources :products do
resources :reviews
end
end
|
1 2 3 4 5 |
def new
@user = User.find(4)
@product = Product.find(2)
@review = Review.new
end
|
1 2 3 |
<%= form_for ( [@user, @product, @review ] ) do |f| %>
(中略)
<% end %>
|
1 2 3 4 |
# HTTPメソッド
POST
# パス
/users/4/product/2/reviews
|
これで投稿画面が完成。
試しにレビューを投稿してみる
試しに、いまの段階でフォームに値を入力して投稿ボタンを押しても何も変化がない。
RailsではHTTPリクエストを送ったあと、レスポンスとして ビュー を返す。
レビューの投稿では reviewsコントローラのcreateアクション が呼び出される。本来その後に、「views」->「reviews」->「create.html.erb」が表示される。
しかし、ディレクトリを見るとわかるようにreviewsの中にcreate.html.erbはない。つまり、createアクションに対応したビューが存在しないということ。表示させるコンテンツがないため画面の遷移が起きなかった。
投稿が完了したらトップページに移動する
そこで、投稿されたらトップページへ遷移するように実装する。
このようにアクセスしたページとは違うページへ移動することをリダイレクト
と言う。リダイレクトをする場合はアクションのビューが呼ばれない。そのため、ビューが無くてもエラーがでない。
リダイレクト
本来アクセスしたページとは別のページへ移動することを言う。
railsではredirect_to
というメソッドでリダイレクトさせることができ、actionオプションを使用することで特定のアクションを指定することができる。
1 |
redirect_to action: アクション名
|
という書き方ができる。
問題6:createアクションですべての処理が終わった後、トップページにリダイレクトする処理を書く
ヒント:redirect_toメソッドでコントローラとアクションを指定する
コントローラで投稿データをデータベースに保存
reviewsコントローラのcreateアクションでフォームから送られてきた値を使ってReviewクラスのインスタンスを作成する処理を書く。これでレビューが投稿できるようになる。form_forから送られてきたパラメーターはrequire
メソッドを使ってデータを取得する。
requireメソッド
form_forに入力されたデータはparamsの中に以下のような形でコントローラに送られる。
【例】
1 2 3 4 |
<%= form_for(@book) do |f| %>
<%= f.text_field :name %>
<%= f.text_area :detail %>
<% end %>
|
1 2 |
params
# { book: { name: "入力された名前", detail: "入力された詳細" } }
|
キーはform_forの引数にあるインスタンスの モデル名(book) になり、バリューはカラム名と入力された値のハッシュ。
paramsからキーがbookのバリューを取得し、createアクションの引数に渡せるようにストロングパラメーターを定義するには以下のようにする。
【例】
1 2 3 4 |
params
# { book: { name: "入力された名前", detail: "入力された詳細" } }
params.require(:book)
# => { name: "入力された名前", detail: "入力された詳細" }
|
form_tagとform_forでの保存方法の違いについて
Pictweetではform_tag
を使用して入力情報を保存したが、Moooviではform_for
を使用して入力情報を保存した。以下のように、Pictweetではコントローラで入力情報を保存する際、ストロングパラメーターとしてparams.permit(:キー名)
という記述をしたが、Moooviではparams.require(:モデル名).permit(:カラム名)
と記述した。
1 2 3 4 5 6 7 8 |
class TweetsController < ApplicationController
# 省略
def tweet_params
params.permit(:image, :text)
end
# 省略
|
1 2 3 4 5 6 7 8 |
class ReviewsController < RankingController
# 省略
def create_params
params.require(:review).permit(:rate, :review) # 省略
end
# 省略
|
この記述方法の違いは、params
の構造の違いによるものである。form_tag
を使用したPictweetの場合は以下の様な構造をしている(確認したい場合は、binding.pry
を使ってみる)。
1 2 3 4 5 6 7 8 |
=> {
"utf8"=>"✓",
"authenticity_token"=>"/3QchQigdEcpc2VaOn+6IGIV14x1iJ5bDsM7GI5NA6k=",
"image"=>"http://sample/hoge.jpg",
"text"=>"hello!",
"controller"=>"tweets",
"action"=>"create"
}
|
これに対して、form_for
を使用したMoooviの場合は以下の様な構造をしている。
1 2 3 4 5 6 7 8 9 10 11 |
=> {
"utf8"=>"✓",
"authenticity_token"=>"/3QchQigdEcpc2VaOn+6IGIV14x1iJ5bDsM7GI5NA6k=",
"review"=> {
"rate"=>"5",
"review"=>"It's reasonable"
},
"action"=>"create",
"controller"=>"reviews",
"product_id"=>"4"
}
|
ポイントは、params
の中で、自身が入力した情報を含む箇所のハッシュ構造。
form_tag
を使用したPictweetのparams
は、ビューファイルのフォームで与えたname属性
がキー
となっている。そして、それぞれのキー
に対して、入力した情報が値
として紐付いている。このハッシュ構造を生成するform_tag
では、params.permit(:キー名)
で入力した情報が取得できる。
これに対してform_for
を使用したMoooviのparams
は、ハッシュの中にハッシュが存在する二重構造になっている。review
というキー
に対応する値
そのものがハッシュだからで、そのハッシュの中には、カラム名
がキー
となり、入力した情報が値
として紐付いている。このハッシュ構造を生成するform_for
では、params.require(:モデル名).permit(:カラム名)
で入力した情報が取得できる。
このように、params
のハッシュ構造の違いによって、入力した情報の保存方法も変わってくる。これは非常に重要な点。
2つのハッシュを結合してみる
ここではそれ以外にパスで送られてきたproduct_idも取得しなければならない。
product_idもparamsとして送られてきている。paramsはハッシュになっているので、2つのハッシュを結合できればうまく取得できそうである。
mergeメソッド
mergeメソッドは2つのハッシュを統合する時に使うメソッド。
1 2 3 4 |
hash1 = {question: "Who is your favorite artist?"}
hash2 = {group: "tech_camp", name: "abe"}
hash1.merge(hash2)
#=> {:question=>"Who is your favorite artist?", :group=>"tech_camp", :name=>"abe"}
|
投稿フォームで送られてきたparamsに、パスで送られてきたidなどのparamsを追加するときにはこの方法を使う。
問題7:reviews_controller.rbのcreateアクションでレビューデータを受け取り、データベースのReviewsテーブルに保存する処理を書く
ヒント:Reviewクラスにcreateメソッドを実行する
ここまでできたら、実際に投稿してデータが保存されているか確認。
まずはレビューを投稿。
次に、reviewsテーブルに投稿した情報が保存されているか確認。
作品ページから投稿するボタンを作る
ヘッダーの右の「投稿する」ボタンからはレビューを投稿できるようになったが、いちいち作品を検索して選ぶのは大変。せっかくトップ画面に作品の一覧が並んでいるのでここで作品を選んでレビューを投稿したいので作品ページからレビューを投稿できるようにする。
作品ページに移動すると、
作品ページの右下に「この作品を投稿する」というリンクがある。いまはクリックしても何も起きない。ビューを編集してこのリンクを押すとその作品のレビュー投稿画面に遷移する実装する。
作品ページのビューはproductsコントローラのビューのディレクトリに入っている。
- mooovi
- apps
- views
- products
- show.html.erb
- views
- apps
作品ページではproductsコントローラのshowアクションが呼ばれるのでビューはそれに対応するshow.html.erb。このファイルを編集してレビュー投稿画面に遷移するようにする。
問題8:作品ページの「この作品を投稿する」というリンクをクリックするとその作品のレビュー投稿画面に遷移するように実装する
作業ファイル:app/views/products/show.html.erb
ヒント:productsコントローラのshowアクションで定義した@productを使う
ヒント:newアクションに対応するパスはrake routesコマンドで確認する
書いたレビューを表示
レビューは書けるようになったが、肝心のレビューがどこにも表示されていない。作品ページに移動してみると、
「みんなのレビュー」というところにレビューが表示されている。いまはサンプルのレビューが書かれている。ここを本当のレビューを表示させるようにする。
アソシエーションを設定
レビューのモデルReviewと作品のモデルProductの間にアソシエーションを設定する。ReviewモデルとProductモデルの間には以下のような関係がある。
① 1つの作品(Productモデル)は複数のレビューを持っている
様々なユーザーが作品についてのレビューを書くため、1つの作品は複数のレビューを持っている。
② 1つのレビュー(Reviewモデル)は1つの作品に属している
レビューはある特定の1つの作品についてのものなので、1つのレビューはただ1つの作品と紐づいている。
1つのProductモデルは複数の Reviewモデルをもつ
1つのReviewモデルは1つのProductモデルを持つ
以上より、ProductモデルとReviewモデルの間には1対多の関係がある。
この関係をProductモデルとReviewモデルに記述する。アソシエーションの記述はbelongs_toやhas_manyを宣言。
問題9:1対多のアソシエーションをProductモデルとReviewモデルで宣言する
作業ファイル:app/models/product.rb, app/models/review.rb
ヒント:has_many,belongs_toをそれぞれ指定する
アソシエーションを宣言できたので、rails cで確認。
1 |
$ rails c
|
以下の流れをrails cで試してProductモデルとReviewモデルのアソシエーションを確認。
- ReviewモデルからProductモデルを取得できるか確認
1 2 3 |
review = Review.first # 一番最初のレビューを取得
review.product
=> その作品情報が表示されるか
|
- ProductモデルからReviewモデルを取得できるか確認
1 2 3 4 |
review = Review.first # 一番最初のレビューを取得
product = review.product # productを変数に入れる
product.reviews
=> その作品のレビューの一覧が表示されるか
|
作品ページにレビューの一覧を表示
作品ページでレビューの一覧を表示する。今のままではサンプルのレビューが表示された状態。作品ページのHTMLが書いてあるのはapp/views/products/show.html.erbなのでshow.html.erbを編集する。
erbファイルはHTML内でerb記法を用いることによりRubyを書くことができるのでアソシエーションを使うことができる。show.html.erb内でアソシエーションを使って作品のレビューの一覧を取得し、それらを表示する。
問題10:作品ページでレビューの一覧が表示されるように show.html.erbを編集する
作業ファイル:app/views/products/show.html.erb
ヒント:1つのレビューを表す<li></li>をeachメソッドを使用して@product.reviewsの数だけ繰り返し表示する
編集できたら、作品のレビューが表示されるか確認する。
これで基本的な映画レビューアプリケーションの機能は完成。