Scaffoldでアプリの雛形を作る
rails g scaffold リソース名 カラム名1:データ型1,カラム名2:データ型2
ためしにやってみる。
rails g scaffold memo title:stirng body:text
すると
Running via Spring preloader in process 12251 invoke active_record create db/migrate/20200117013020_create_memos.rb create app/models/memo.rb invoke test_unit create test/models/memo_test.rb create test/fixtures/memos.yml invoke resource_route route resources :memos invoke scaffold_controller create app/controllers/memos_controller.rb invoke erb create app/views/memos create app/views/memos/index.html.erb create app/views/memos/edit.html.erb create app/views/memos/show.html.erb create app/views/memos/new.html.erb create app/views/memos/_form.html.erb invoke test_unit create test/controllers/memos_controller_test.rb create test/system/memos_test.rb invoke helper create app/helpers/memos_helper.rb invoke test_unit invoke jbuilder create app/views/memos/index.json.jbuilder create app/views/memos/show.json.jbuilder create app/views/memos/_memo.json.jbuilder invoke assets invoke scss create app/assets/stylesheets/memos.scss invoke scss create app/assets/stylesheets/scaffolds.scss
たくさんのファイルが生成される。
rails routes
でルーティングが確認できる。
モデルクラスを作成
rails db:migrate
でマイグレーションをする。
テストのためにフィクスチャをする。test/fixtures/memos.yml
で中身を確認し、好きなように変更する。
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html one: id: 1 title: '今日は上半身の筋トレをした' body: '新しいベンチプレスを試してみた' two: id: 2 title: '背中トレ' body: 'やっぱりデッドリフトはフルレンジがいいわ'
テストデータを読み込む。
rails db:fixtures:load
これでrails s
rails s
仮想環境なら
rails s -b 0.0.0.0
でサーバーを起動。
http://192.168.33.10:3000/memos
へ移動すると簡素なウェブアプリケーションがすでにできている。素晴らしい・・・。
詳しく
app/controllers/memos_controller.rb
class MemosController < ApplicationController before_action :set_memo, only: [:show, :edit, :update, :destroy] # GET /memos # GET /memos.json def index @memos = Memo.all end # GET /memos/1 # GET /memos/1.json def show end # GET /memos/new def new @memo = Memo.new end # GET /memos/1/edit def edit end # POST /memos # POST /memos.json def create @memo = Memo.new(memo_params) respond_to do |format| if @memo.save format.html { redirect_to @memo, notice: 'Memo was successfully created.' } format.json { render :show, status: :created, location: @memo } else format.html { render :new } format.json { render json: @memo.errors, status: :unprocessable_entity } end end end # PATCH/PUT /memos/1 # PATCH/PUT /memos/1.json def update respond_to do |format| if @memo.update(memo_params) format.html { redirect_to @memo, notice: 'Memo was successfully updated.' } format.json { render :show, status: :ok, location: @memo } else format.html { render :edit } format.json { render json: @memo.errors, status: :unprocessable_entity } end end end # DELETE /memos/1 # DELETE /memos/1.json def destroy @memo.destroy respond_to do |format| format.html { redirect_to memos_url, notice: 'Memo was successfully destroyed.' } format.json { head :no_content } end end private # Use callbacks to share common setup or constraints between actions. def set_memo @memo = Memo.find(params[:id]) end # Never trust parameters from the scary internet, only allow the white list through. def memo_params params.require(:memo).permit(:title, :body) end end
index
def index @memos = Memo.all end
app/views/memos/index.html.erb
<p id="notice"><%= notice %></p> <h1>Memos</h1> <table> <thead> <tr> <th>Title</th> <th>Body</th> <th colspan="3"></th> </tr> </thead> <tbody> <% @memos.each do |memo| %> <tr> <td><%= memo.title %></td> <td><%= memo.body %></td> <td><%= link_to 'Show', memo %></td> <td><%= link_to 'Edit', edit_memo_path(memo) %></td> <td><%= link_to 'Destroy', memo, method: :delete, data: { confirm: 'Are you sure?' } %></td> </tr> <% end %> </tbody> </table> <br> <%= link_to 'New Memo', new_memo_path %>
<p id="notice"><%= notice %></p>
: noticeは特殊な変数で、コントローラ側の処理の結果、ユーザーに何かしらの通知メッセージを一度だけ表示したい、といった用途で使われる。
<td><%= link_to 'Show', memo %></td>
link_to
: Railsが提供するもので、リンクを設置するaタグを出力する, memo
でリンク先を設定しているmemo_path(memo.id)
の省略形である点に注意<a href="/memo/1">Show</a>
というHTMLとして出力される
- このようにビューで使えるメソッドのことヘルパーメソッドと呼ぶ
<td><%= link_to 'Edit', edit_memo_path(memo) %></td>
- edit_memo_path(memo):
<a href="/memo/1/edit">Edit</a>
というHTMLで出力される- 引数
(memo)
は(memo.id)
の省略形
<td><%= link_to 'Destroy', memo, method: :delete, data: { confirm: 'Are you sure?' } %></td>
link_to 'Destroy'
: Destroyへのリンクmemo
:memo_path(memo.id)
の省略形でリンク先method: :delete
: リクエストメソッドにdeleteを指定- data-method属性を付与し、その値をdeleteにする
data: { confirm: 'Are you sure?' }
:- aタグにdata属性を付与するもので、{}のブロックにハッシュで記述する
- aタグにdata-confirm属性を付与し、その値を「Are you sure?」にしている
こんなHTMLになる。
<a data-confirm="Are you sure?" rel="nofollow" data-method="delete" href="/memos/1">Destroy</a>
showアクションを確認
class MemosController < ApplicationController before_action :set_memo, only: [:show, :edit, :update, :destroy] # GET /memos # GET /memos.json def index @memos = Memo.all end # GET /memos/1 # GET /memos/1.json def show end
- showメソッドには処理が書かれていないが、
before_action
メソッドに書かれている before_action
: アクションを呼ぶ前に特定の処理をしたい場合に用意されている
before_action メソッド名, 条件ハッシュ before_action :set_memo, only: [:show, :edit, :update, :destroy]
- メソッド名: コントローラー内のメソッドをシンボルで記述する
- 条件ハッシュ: 適用するアクションの条件をハッシュで記述する
- キーにonly/exceptのいずれかを指定し、値にはアクション名のシンボルを指定
- onlyでは、指定したアクションにのみ適用される
- expectでは、指定したアクション以外のすべてのアクションに適用される
- show,edit,update,destroyメソッドが呼ばれる前に、set_memoメソッドが呼ばれている
def set_memo @memo = Memo.find(params[:id]) end
- URLのidを取得し、Memo.findでデータをとってきて、@memoに代入している
- Memo.find(params[:id])では、memosテーブルのidがparams[:id]に一致するレコードの、Memoモデルクラスのインスタンスを取得する
- @memoにレコードを代入することにより、レコードごとの表示画面を動的に生成することができる
対応するファイル: app/views/memos/show.html.erb
<p id="notice"><%= notice %></p> <p> <strong>Title:</strong> <%= @memo.title %> </p> <p> <strong>Body:</strong> <%= @memo.body %> </p> <%= link_to 'Edit', edit_memo_path(@memo) %> | <%= link_to 'Back', memos_path %>
それぞれHTMLにすると。
<%= link_to 'Edit', edit_memo_path(@memo) %> => <a href="/memos/1/edit">Edit</a>
<%= link_to 'Back', memos_path %> => <a href="/memos">Back</a>
こんな感じになる。
メモの登録
app/controllers/memos_controller.rb
# GET /memos/new def new @memo = Memo.new end
新しいメモ@memoを登録し、ビューで利用できるようにしている。
app/views/memos/new.html.erb
<h1>New Memo</h1> <%= render 'form', memo: @memo %> <%= link_to 'Back', memos_path %>
render
: ここではformという部分テンプレートを呼び出している- 部分テンプレートとは、ビューから別に切り出されたビューファイルのこと
app/views/memos/_form.html.erb
が呼び出されている
- 登録や更新をするとき、入力フォームに違いがない場合は、このようにrenderメソッドで部分テンプレートを呼び出すことでビューを部品化して共通に使える
<%= render '部分テンプレート名', キー1: 値1, キー2: 値2,,, %> => <%= render 'form', memo: @memo %>
_form.html.erb
の中で、アクションメソッドで用意された@memoに、memo変数でアクセスできる- memo変数は、
_form.html.erb
でモデルに関連づく入力フォームを生成するために使う
app/views/memos/_form.html.erb
<%= form_with(model: memo, local: true) do |form| %> <% if memo.errors.any? %> <div id="error_explanation"> <h2><%= pluralize(memo.errors.count, "error") %> prohibited this memo from being saved:</h2> <ul> <% memo.errors.full_messages.each do |message| %> <li><%= message %></li> <% end %> </ul> </div> <% end %> <div class="field"> <%= form.label :title %> <%= form.text_field :title %> </div> <div class="field"> <%= form.label :body %> <%= form.text_area :body %> </div> <div class="actions"> <%= form.submit %> </div> <% end %>
form_with
: 引数の形式にしたがって、さまざまなフォームタグを出力するヘルパーメソッドで、do~endで囲むブロック形式で使う
<%= form_with(model: モデルクラスのインスタンス, local: 真偽値) do |form| %> <% end %> <%= form_with(model: memo, local: true) do |form| %> <% end %>
- modelの引数には、フォームに紐付けるモデルクラスのインスタンスを指定
- 配下のフォーム要素に、モデルクラスの値を反映できる
- local: trueは、Ajaxによるデータ更新を許可するかどうかの設定
app/views/memos/_form.html.erb
がHTMLとして出力された場合。
<div> <h1>New Memo</h1> <form action="/memos" accept-charset="UTF-8" method="post"><input type="hidden" name="authenticity_token" value="aPI4hvB5SFzTI68oIgE091pVt0bzfchWcirN+FoHJd+K2oj5urRrJBy9fktMURqFcd0iMOmmOg1dhlqdfT0ZEw=="> <div class="field"> <label for="memo_title">Title</label> <input type="text" name="memo[title]" id="memo_title"> </div> <div class="field"> <label for="memo_body">Body</label> <textarea name="memo[body]" id="memo_body"></textarea> </div> <div class="actions"> <input type="submit" name="commit" value="Create Memo" data-disable-with="Create Memo"> </div> </form> <a href="/memos">Back</a> </div>
見ていく。
<input type="hidden" name="authenticity_token" value="aPI4hvB5SFzTI68oIgE091pVt0bzfchWcirN+FoHJd+K2oj5urRrJBy9fktMURqFcd0iMOmmOg1dhlqdfT0ZEw==">
label ビューヘルパー
app/views/memos/_form.html.erb
<div class="field"> <%= form.label :title %> <%= form.text_field :title %> </div> # 出力 <div class="field"> <label for="memo_title">Title</label> <input type="text" name="memo[title]" id="memo_title"> </div>
for属性モデル名_labelの引数
という形式で生成される<label for="memo_title">Title</label>
- 文字列は制定されたフィールド名の先頭を大文字にしたもの
text_field ビューヘルパー
app/views/memos/_form.html.erb
<div class="field"> <%= form.label :title %> <%= form.text_field :title %> </div> # 出力 <div class="field"> <label for="memo_title">Title</label> <input type="text" name="memo[title]" id="memo_title"> </div>
<%= form.text_field :引数, 属性名1: 値1, 属性名2: 値2, ... %>
- テキストボックス用のinputタグは、type属性がtextとなる
- name属性:
オブジェクト名[引数]
- id属性: モデル名_カラム名
- 追加属性を指定したい場合は
属性名: 値
text_are ビューヘルパー
<div class="field"> <%= form.label :body %> <%= form.text_area :body %> </div>
<div class="field"> <label for="memo_body">Body</label> <textarea name="memo[body]" id="memo_body"></textarea> </div>
- name属性: オブジェクト名[引数]
submit ビューヘルパー
<div class="actions"> <%= form.submit %> </div>
<div class="actions"> <input type="submit" name="commit" value="Create Memo" data-disable-with="Create Memo"> </div>
- input typeのsubmit
- name属性: commitがデフォルト
- value: "Create モデル名"
- data-disable-with: "Create モデル名"
createメソッドを確認
app/controllers/memos_controller.rb
def create @memo = Memo.new(memo_params) respond_to do |format| if @memo.save format.html { redirect_to @memo, notice: 'Memo was successfully created.' } format.json { render :show, status: :created, location: @memo } else format.html { render :new } format.json { render json: @memo.errors, status: :unprocessable_entity } end end end
@memo = Memo.new(memo_params)
では、memo_paramsメソッドの戻り値を引数として、Memoモデルクラスのインスタンスを作成している- memo_paramsメソッドは同一ファイルのprivateメソッド内で定義されている
- フォームから送信されたデータをparamsメソッドで取得している
private # Use callbacks to share common setup or constraints between actions. def set_memo @memo = Memo.find(params[:id]) end # Never trust parameters from the scary internet, only allow the white list through. def memo_params params.require(:memo).permit(:title, :body) end
- params.require/permit: フォームから送信されたデータを指定したものだけに限定するための仕組み
- Strong Parametersと呼ばれる
- 例えば、不正な入力フォームから値が送信されても、更新データを日記タイトルと日記本文に限定するため、別IDの日記データが更新されてしまうのを防げる
- requireの引数にはモデル名を指定
- permitの引数にはカラム名を指定
params.require(:memo).permit(:title, :body)
でこんなハッシュデータが得られる。
{ 'title': 'あああああ', 'body': 'ららららら' } これをDBに保存している。 ### respond_toメソッド `app/controllers/memos_controller.rb`
respond_to do |format|
if @memo.save
format.html { redirect_to @memo, notice: 'Memo was successfully created.' }
format.json { render :show, status: :created, location: @memo }
else
format.html { render :new }
format.json { render json: @memo.errors, status: :unprocessable_entity }
end
end
- アクセスURLの末尾の拡張子によって処理を分ける場合に使用する - ブロック引数に拡張子をとり、ブロック内で拡張子ごとの処理をブロックで定義する - Railsでは拡張子に何も指定されてない場合、フォーマットはhtmlと判断される - 末尾に「.json」がついている場合はフォーマットはJSONと判断される
if @memo.save
format.html { redirect_to @memo, notice: 'Memo was successfully created.' }
format.json { render :show, status: :created, location: @memo }
else
- format.html { リクエストフォーマットがhtmlの場合に実行する処理} - まずsaveメソッドで保存 - saveメソッドはデータの保存に成功したかどうかをtrue/falseで返すので、その値に応じて実行する処理を分岐している - 正常にデータが保存された場合は(trueが返った場合)、`format.html { redirect_to @memo, notice: 'Memo was successfully created.' }`が実行される `format.html { redirect_to @memo, notice: 'Memo was successfully created.' }` - @memoにリダイレクトしている - RailsアプリのURLの省略形 - いま保存したデータの表示画面を意味する - noticeに移動先で出力するメッセージをハッシュ形式で指定する - このメッセージのことをフラッシュメッセージと呼ぶ - ビュー側でもnotice変数で参照できる #### JSON形式で呼ばれた場合
format.json { render :show, status: :created, location: @memo }
- JSON形式でアクセスされると呼び出される処理 - renderメインには:showが指定されている - `app/views/memos/show.json.jbuilder`が呼ばれる - `status: :created`: Webサーバからの応答結果を表す - この場合、内部的には201のHTTPステータスコードが返る - 201はリクエストが成功し、新しいデータが作成されたことを表す - `location: @memo`: 新しく登録されたメモデータのURLを表す `app/views/memos/show.json.jbuilder`
json.partial! "memos/memo", memo: @memo
json.partial! "部分テンプレート名", キー1: 値, ...
- `memos/memo`: `app/views`ディレクトリからのパスを表す - `app/views/memos/_memo.json.jbuilder`が呼ばれる - `memo: @memo`: 部分テンプレートに対して、memo変数で@memoを渡す `app/views/memos/_memo.json.jbuilder`
json.extract! memo, :id, :title, :body, :created_at, :updated_at json.url memo_url(memo, format: :json)
- `json.extract!`: モデルオブジェクトのカラム要素を指定して値を列挙する `json.url memo_url(memo, format: :json)` - urlというキーにたいして、`memo_url(memo, format: :json)`という値を指定する `~:3000/memos/1.json`にアクセスすると、jsonファイルが返る。
{"id":1,"title":"今日は上半身の筋トレをした","body":"新しいベンチプレスを試してみた","created_at":"2020-01-17T11:04:23.419+09:00","updated_at":"2020-01-17T11:04:23.419+09:00","url":"http://192.168.33.10:3000/memos/1.json"}
### else以降 `app/controllers/memos_controller.rb`
else
format.html { render :new }
format.json { render json: @memo.errors, status: :unprocessable_entity }
end
- JSON形式で日記データの保存に失敗した場合に呼ばれる - `render json: @memo.errors`: すべてのエラーメッセージをJSON形式で返す - `status: :unprocessable_entity`: 422ステータスコードを表し、処理できなかったことを意味する