セキュリティ系の勉強・その他開発メモとか雑談. Twitter, ブログカテゴリ一覧
本ブログはあくまでセキュリティに関する情報共有の一環として作成したものであり,公開されているシステム等に許可なく実行するなど、違法な行為を助長するものではありません.

ruby チュートリアル7章 メモ

//

サイトにデバッグ情報表示

下のコードをレイアウトのコードに挿入することで、描画されるページの状態を把握するのに役立つ情報をサイトに表示することができる。if~文は開発環境でのみデバッグ情報を表示するよう指定している。

#app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  .
  .
  .
  <body>
    <%= render 'layouts/header' %>
    <div class="container">
      <%= yield %>
      <%= render 'layouts/footer' %>
      <%= debug(params) if Rails.env.development? %>
    </div>
  </body>
</html>


Sassのミックスイン

ミックスインを使用することでcssルールのグループをパッケージ化して簡単に複数要素に対して使用することができる。例えば、要素のサイズや見た目をいろんな場所で統一したい場合、あらかじめレイアウトをパッケージ化しておけば、そのパッケージをインポートするだけで、レイアウトを適用することできます。

#app/assets/stylesheets/custom.scss
@import "bootstrap-sprockets";
@import "bootstrap";

/* mixins, variables, etc. */

$gray-medium-light: #eaeaea;

@mixin box_sizing {
  -moz-box-sizing:    border-box;
  -webkit-box-sizing: border-box;
  box-sizing:         border-box;
}
.
.
.
/* miscellaneous */

.debug_dump {
  clear: both;
  float: left;
  width: 100%;
  margin-top: 45px;
  @include box_sizing;
}


RESTアーキテクチャの適用

データの作成、表示、更新、削除をリソースとして扱うこと。これらに対応する4つの操作(GET,POST,PATCH,DELETE)を各アクションに割り当てる必要がある。RailsのREST機能が有効だと、GETリクエストは自動的にshowアクションとして扱われる。この場合、リソースへの参照はリソース名とユニークIDをURIとして使用することになり、id=1のユーザの参照はusers/1となる。これをルーティングに設定するには下のようにする。

#config/routes.rb
Rails.application.routes.draw do
  root 'static_pages#home'
 . . .
  get  '/contact', to: 'static_pages#contact'
  get  '/signup',  to: 'users#new'
  resources :users
end


UsersリソースをRESTfulなアクションにした場合以下のようになる。

f:id:thinline196:20181106163627p:plain

debuggerメソッド

byebug gemのdebuggerメソッド。下のようにコード中に挟むとサーバを立ち上げたコンソール画面から、その場所のデバッグを行うことができる。

#app/controllers/users_controller.rb
class UsersController < ApplicationController

  def show
    @user = User.find(params[:id])
    debugger
  end

  def new
  end
end


Gravatar

プロフィール写真をアップロードして、指定したメールアドレスと関連付けることができ、画像パスを生成するだけでその画像を引っ張ってこれる。チュートリアルでは以下のようなヘルパーを作成して画像を引っ張ってきていたが、このチュートリアルに最適かは疑問。後々使うのであればまた別ですが。

  # 引数で与えられたユーザーのGravatar画像を返す
  def gravatar_for(user)
    gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
    gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}"
    image_tag(gravatar_url, alt: user.name, class: "gravatar")
  end


オプション引数

メソッドを定義した際に予めデフォルト値を与えてやれば、引数に値を指定しなかった場合自動でデフォルトの値が使用される。もちろん値を渡した場合はその値が優先される。これをオプション引数といい下のように定義する。

  def gravatar_for(user, options = { size: 80 })
    gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
    size = options[:size]
    gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}"
    image_tag(gravatar_url, alt: user.name, class: "gravatar")
  end


またRuby2.0から導入されたキーワード引数を使用することで同じものをより短く実装が可能。

  def gravatar_for(user, size: 80)
    gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
    gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}"
    image_tag(gravatar_url, alt: user.name, class: "gravatar")
  end


これのメソッドの呼び出し方は下のようにする。

<%gravatar_for @user%>  #デフォルトの80が適用される
<%gravatar_for @user, size:200%> #引数で渡した200がsizeに適用される。


form_forメソッド

form_forメソッドはActiveRecordのオブジェクトを取り込んで、そのオブジェクトの属性を使用してフォームを構築してくれる。

<%= form_for(@user) do |f| %>
  .
  .
<% end %>


fオブジェクトはHTMLフォームに対応するメソッドが呼び出されると、@userの属性を設定できる特別なHTMLを返す。labelの:nameシンボルは@userの属性になくてもエラーは出ない。シンボル名がそのままラベルに変換される。text_fieldの方はuserの属性に含まれるものをシンボルとして渡さないとエラーになる。

<%= f.label :name %>
<%= f.text_field :name %>

上を実行すると下のように展開される。

  <label for="user_name">Name</label>
  <input id="user_name" name="user[name]" type="text" />


text_fieldを使用すると一般的な入力フォームが、password_fieldを使用すると文字隠しが適用される入力フォームが生成されるなど、柔軟に対応できていることもわかります。
form自身のhtmlは以下のように生成される。

<form action="/users" class="new_user" id="new_user" method="post">


form_forは生成時に読み込んだ@userがUserクラスであり新しいUserであることを自動で認識し、actionとmethodを生成する。RESTfulな設計なのでpostはcreateに対応しており、Userクラスのcreateアクションが実行されるといった流れ。classとidはformヘルパーに対して特に直接的な結びつきは持たない。
-----------追記(2019/3/4)---------
チュートリアルでは、エラーの表示部分をパーシャルとして抽出しているのですが、チュートリアルのままだと@user変数にしか使えません。下のサイトを参考にどのフォームでもこのパーシャルを使い回せる様にしましょう。 qiita.com

= form_with(model: resource, as: resource_name, url: password_path(resource_name),local:true, html: { method: :post }) do |f|
  = render "shared/error_messages", model: f.object # 追加:modelで参照できる様にする
  .field
    = f.label :email
    br/
    = f.email_field :email, autofocus: true, id: "email" , autocomplete: "email", class: "form-control"
  .actions
    = f.submit I18n.t("devise.passwords.new.send_me_reset_password_instructions"), class: "btn btn-primary"
= render "devise/shared/links"

ごめんなさい、Slimでの記法になってます。

# _error_messages.html.slim
- if model.errors.any? # modelでオブジェクトの参照が可能
  .error-explanation.alert.alert-danger
    strong
      = I18n.t("errors.messages.not_saved", count: model.errors.count, resource: model.class.model_name.human.downcase)
    ul
      - model.errors.full_messages.each do |message|
        = message

-----------追記ここまで-----------

Strong Parameters

user情報入力後、その値を全てそのままUser生成に使用してしまったら、意図しないパラメータも更新されてしまう可能性がある。そのため、予め設定したものだけのみ、入力情報から取り出せるよにするべきである。

@user = User.new(params[:user]) #:userに格納された全ての情報を使用しようとする
@user = User.new(params.require(:user).permit(:name, :email, :password, :password_confirmation)) #許可した値だけ取り出される。


例えばcreateアクション時に簡単に呼び出せるようなメソッドをprivateに実装しておくことが考えられる。

  def create
    @user = User.new(user_params)
    if @user.save
      # 保存の成功をここで扱う。
    else
      render 'new'
    end
  end

  private

    def user_params
      params.require(:user).permit(:name, :email, :password,
                                   :password_confirmation)
    end


app/views/sharedディレクト

複数のビューで使用されるパーシャルはこの下に保存される。(習慣なので自動でやるわけではない)

pluralize

英語テキスト専用のヘルパー。一つ目の引数の値(数字)によって二つ目の文字を複数形に変更してくれる。

redirect_to

redirect_to @userredirect_to user_url(@user)と等価。これは以下の挙動と同じだが、あくまで挙動が同じだけ。route.rbでresourcesを指定していないものに対しては使用できない(user_urlがresourcesを使用した場合のみ利用可能になるから)

redirect_to "/users/#{@user.id}" # 条件付き(後述)
or
redirect_to user_url(id: @user.to_param)
or
redirect_to user_url(id: @user.id)


flash

ページが切り替わった時にユーザに向けて表示するメッセージを生成管理してくれるメソッド。コントローラのアクション定義の際にflashにシンボルと紐づけてメッセージを格納すると、次のアクションするまでその値を保持してくれる。(createアクション内で格納したメッセージは次のアクションで飛んだページでは残っているが、もう一度アクションをすると消える)

【Rails入門】flashの使い方まとめ | 侍エンジニア塾ブログ(Samurai Blog) - プログラミング入門者向けサイト
なお、次のアクションにも残さなかったり、逆に永続的に保持させることも可能。

content_tag

railsのヘルパー。content_tag(タグの名前)といった形で使用し、タグを動的に生成してくれる。チュートリアルではflash用のhtmlを見やすいように書き換えるために使用した。

#before
      <% flash.each do |message_type, message| %>
        <div class="alert alert-<%= message_type %>"><%= message %></div>
      <% end %>

#after
      <% flash.each do |message_type, message| %>
        <%= content_tag(:div, message, class: "alert alert-#{message_type}") %>
      <% end %>


今回のintegrationテストに関して

今回のサインアップのテストはチュートリアル上ではIntegrationテスト(featureテスト)の位置付けで行われていた。しかし、その内容はコントローラのテストとした方がよい可能性がある。
と言うのも、featureテストはcontrollerを挟んだviewをテストするイメージであり、今回のテストは完全にcontrollerのメソッド内で完結している処理だからである。
f:id:thinline196:20181109125000p:plain


では、viewのテストとは?一般にはviewのテストは書かないという風潮があるかもしれない。。何かを表示するにはほぼ必然的にcontrollerを経由し、それはつまりfeatureテストになる。(getしたページのタイトルが予期したものか等) またそもそもページに何のコンテンツが表示されているかのテストを始めると、保守コストが高すぎてやってられるか〜!となる話もあったりなかったり。
(念のためですが、書いている方もしっかりいらっしゃるので決して必要ないと言うことではありませんよ=> RSpec使ってERBに対するspecをどう書くか? - Qiita)


ちなみに、viewのヘルパーのテストは行う可能性が十分にあるので注意。