- MVCアーキテクチャ
- モデル
- コントローラの役割
- ビューの役割
- MVCについてのまとめ
MVCアーキテクチャ
モデル
ActiveRecord::Relation
- Query Interface による捜査結果をオブジェクトとして表現したもの
- どんなSQLを発行するか、という情報だけを保持している
- そのSQLの実行結果が必要になるタイミングまではDBアクセスを行うことはない
どういうことか?
動作
- ActiveRecord に対してQuery Interface が呼ばれると ActiveRecord::Relation のインスタンスが生成される
- 繰り返し呼び出した Query Interface は ActiveRecord::Relation のインスタンスに蓄積され、どんなSQLを発行するかの情報が更新されていく
- 実際にデータが必要になった時点で、蓄積された情報を元にSQLを発行しデータを取得する
データが必要になったタイミングで初めて実行される理由
=> メソッドチェーンによるクエリ構築を行うため。
最初のメソッド呼び出し時に検索条件が自明でない場合もあります。
このような場合、組み立てた時点で実行されてしまうと複雑な条件や外部からの引数でSQLを切り替えたい場合に記述が難しくなってしまいます。
このような理由により、データが必要になったタイミングで実行されるようになっています。
(明示的に任意の箇所でクエリを発行したい場合には to_a
を呼び出すなどを行うとできます。返却されるものはモデルのインスタンスの配列です。)
Scope
検索結果に名前をつけてひとまとめにしたもの。
- クエリに名前をつけることで可動性が向上する
- 重複コードが減る
scope :written_about, ->(theme) { where("name like ?", "%#{theme}%")}
バリデーション
エラーになったら、例えば book.errors とすることで取得できる
コールバック
- before_validation
- after_validation
コントローラ
ルーティングとリソース
bundle exec rake routes
で設定されているルーティング一覧を見ることができます。
resources について
resources は REST にしたがって endpointを作成してくれる。
resources :publishers
と書くと、RESTの統一インターフェースにしたがって
- リソースの取得(GET)
- リソースの作成(POST)
- リソースの更新(PATCH(PUT))
- リソースの削除(DELETE)
というendpointを作成する。
ちなみに、
resources :publisher do
resources :books
member do
get 'detail'
end
end
end
こんなかんじで親子関係をルーティングすることができます。
resources 以外
resource :profile
一人のユーザーからみてアプリケーション上1つしか存在しないようなリソースの時にはこのように書くと便利です。
例えば、ログインユーザーが参照する自身のプロフィールなど。
表示と更新だけにしたいときとかそういう時には only を使って
resource :profile, only: %i{show edit update}
とかくと show
, edit
, update
を呼び出すendpointのみ作成します。
例外処理
rescue_from
ApplicationController などに定義しておく。
rescue_from LoginFailed, with: :login_failed
def login_failed
render template: 'shared/login_failed', status: 401
end
すると、ApplicationController を継承したクラス内で、raise したときに、rescue_from してある例外はキャッチしてくれる。
class LoginController < ApplicationController
def create
@user = User.where(name: params[:name], password: params[:password]).first
raise LoginFailed unless @user
end
end
StrongParameters とは
- Mass Assignment 機能を利用する際に起こりうる脆弱性に対抗する手段の一つ。
- Mass Assignment とはモデルの生成や更新の際に、以下の様なRubyのHashクラスを使って一括で属性を設定できる非常に便利な仕組み。
下記のようにすると、意図しない属性の変更を一般ユーザーに許してしまう。
user = User.find(1)
user.update(name: "Bob", email: "bob@example.com")
params[:user] には外部からどんな値も送られてきうる。(params はユーザーが送ってきたHTTPリクエストから組み立てられるものなので)
class ProfileController < ApplicationController
def update
user = current_user
user.update(params[:user])
end
end
これを防ぐための機構が StrongParameters です。
StrongParameters は Mass Assignment で、利用を許可する Hash の key を事前に検査する。
class ProfileController < ApplicationController
def update
user = current_user
user.update(user_params)
end
private
def user_params
params.require(:user).permit(:name, :email)
end
end
リクエストに :user という key が必要であることに注意。
上記のコードでは、user の中で受け付けても良いのは [:name, :email] の2つのkeyのみ。
require に指定したパラメータが params に含まれていなかった場合は、
ActionController::ParmeterMissing 例外が発生する。
StrongParameter が生まれた背景 = "mass assignment"
- http://blog.sorah.jp/2012/03/05/mass-assignment-vulnerability-in-github
- http://blog.sorah.jp/2012/03/14/rails-strong-parameter
- http://selfkleptomaniac.org/archives/2109
- https://github.com/blog/1068-public-key-security-vulnerability-and-mitigation
ビュー
テンプレートの検索
render :show
- 描画するためのテンプレートを探す
- 探されたテンプレートを基に、データを展開し、最終的なHTMLを生成する
どうやってcontrollerからの指定でviewファイルを探索しているのか?
=> RAILS_ROOT/app/views/コントローラ名/アクション名.html.erb
ex) app/controllers/books_controller.rb -> app/views/books/show.html.erb
render を省略した場合
def show
@book = Book.find(params[:id])
end
コンテンツのタイプによって表示を出し分ける
HTML, JSON, XML などその他のフォーマットで表示させる。
class BookController
def show
@book = Book.find(params[:id])
respond_to do |format|
format.html
format.csv
end
end
end
app/views/books/show.csv.erb
=> コントローラ名/アクション名.フォーマット.エンジン
format.xml { render xml: @book }
partialテンプレートとlayout
同じ内容を複数箇所で使いまわす。
-> :partial
によって使いまわしたい部分を切り出せる。
partial:
をつけると prefix _
で始まるファイル名のテンプレートから検索する。
テンプレートの構造化
app/views/layouts/ というディレクトリにレイアウト用のテンプレートを配置する。
デフォルトでは application.html.erb というファイルが用いられる。
- pp/views/layouts/application.html.erb
<%= yield %>
こうするとそれぞれのページのコンテンツが展開されるので、
- app/views/books/show.html.erb
^^ このファイルには body タグの中身だけ書けば良い。
variants によるテンプレートの切り替え
variants ... Rails 4.1 でテンプレートを切り替える機構として導入されたもの。
テンプレートエンジンの紹介
ERB
Ruby に標準添付されているテンプレートエンジン。
Railsでも標準のテンプレートエンジンとして採用されている。
<ul>
<%= 3.times do |n| %>
<li>Number = <%= n %></li>
<% end %>
</ul>
Haml
railsで使いたいときには haml-rails
indentベース
%html
%head
%title Hi
%body
%h1 #header Header
- 3.times do |i|
%p Item
%p= i
Slim
railsで使うには slim-rails
indentベース
Hamlよりもさらに簡素な表記が可能
doctype html
html
head
title Hi
body
h1 id="header" Header
- 3.times do
p Item
ヘルパー
url_for
url_for(controller: :users, action: :index)
# => /users
url_for(controller: :users, action: :index, id: 1234, detailed: 'true')
# => /users/1234?detailed=true
form_tag/form_for
- form_tag ... 単純なフォームを作る
- form_for ... モデルの情報を基に対応するフィールドの内容を埋めたり、エラーを表示したり
<% form_for(@publisher) do |f| %>
<% end %>
エスケープ処理
Railsのエスケープの仕組み。
XSSに対する対策がフレームワークとして組み込まれている。
そのまま出したいときには 'raw' を使う
<%= raw "<script>alert('sample');</script>" %>
raw ヘルパは内部的には String#html_safe というメソッドを読んで String オブジェクトを ActiveSupport::SafeBuffer のオブジェクトに変換している。
<%= "<script>alert('sample');</script>".html_safe %>
つまり、^^ともかける。
APIサーバにとってのビュー
最近ではJSONが主流。
- format.json
json.extract! @book, :id, :name, :price, :created_at
- "!" => 終わるメソッドはJSON化する
- "!" => 終わらないメソッドはそのままJSONのキーになる。
json.name_with_id "#{@book.id} - #{@book.name}"
json.publisher do
json.name @book.publisher.name
json.addredd @book.publisher.address
end
unless @book.high_price?
json.low_price true
end