LoginSignup
0
0

More than 3 years have passed since last update.

rails-tutorial第7章

Last updated at Posted at 2020-06-04

Restfulってなんだ?

RESTfulなアーキテクチャの場合、URLは同一のものを使うかわりに、HTTPリクエストメソッドにそれぞれ、GET、POST、PATCH、DELETEを使って異なるアクションに結びつけるようです。

こうすることでURLを名詞とし、HTTPリクエストメソッドを動詞とすることができ
シンプルにURLをいろいろなアクションに結びつけることができるようになります。

ところで、現在のブラウザにはPATCHやDELETEといったメソッドはないようです。

Railsは存在しないHTTPメソッドを存在するかのように見せかけ
RESTfulなアーキテクチャの実装を実現しているようです。

なんでRestfulなアーキテクチャがいいのか?

Progateのrailsアプリを作った時もそうだったけど、

現在使われているHTTPリクエストメソッドは、GETとPOSTのため
普通は、showアクションは/users_show/1、updateアクションは/users_update/1、
destroyアクションは/users_destroy/1とかのURLを考えますよね。

昔Django(pythonのwebフレームワーク)でアプリを作った時は実際にそうしてました。

でもそうするとurlを管理するファイルの中がアクションの数によっては大変なことになったのを
記憶しています。

これをresources :users
とすることで、ファイルを見やすくすることができる。

ユーザー登録機能を実装する

まずは開発環境でのみデバッグ情報を表示するようにしてみよう

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>

<%= debug(params) if Rails.env.development? %>
ここではデバッグメソッドが使われている。paramsでデバッグ情報を受け取り、開発環境でのみそれを表示させる。putsメソッドと似ている。ついでに、後置if文を使うときは1行で済む時に使われることが多い。2行以上の時は前置if文を使おう。

ルーティングを設定しよう

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

ここで、どのurlがどのアクションに対応するのか知りたい。
そんな時は、

$ rails routes

このコマンドを打つと、

ec2-user:~/environment/sample_app (sign-up) $ rails routes
   Prefix Verb   URI Pattern               Controller#Action
     root GET    /                         static_pages#home
     help GET    /help(.:format)           static_pages#help
    about GET    /about(.:format)          static_pages#about
  contact GET    /contact(.:format)        static_pages#contact
   signup GET    /signup(.:format)         users#new
    users GET    /users(.:format)          users#index
          POST   /users(.:format)          users#create
 new_user GET    /users/new(.:format)      users#new
edit_user GET    /users/:id/edit(.:format) users#edit
     user GET    /users/:id(.:format)      users#show
          PATCH  /users/:id(.:format)      users#update
          PUT    /users/:id(.:format)      users#update
          DELETE /users/:id(.:format)      users#destroy

というように、urlと対応するアクションを調べることができる。

PUTリクエストとPATCHリクエストの違いって?

どちらもupdateアクションを指定しているが、PUTリクエストは基本的に全ての情報を更新する時に使う。PATCHリクエストは全部、または一部の情報を更新する時に使う。そのため情報の更新にはPATCHリクエストの方が適切であると言える。

ローカル変数とインスタンス変数

user ローカル変数
ローカル変数のスコープはメソッド内。

@userはインスタンス変数
インスタンス変数は、method外、例えばviewで使うことができる。

Userリソースのshowアクションを実装

app/controllers/users_controller.rb
class UsersController < ApplicationController

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

  def new
  end
end

showアクションが実行される条件は、GET /users/:id
そのため、showアクションが実行される時は必ずidがurlに含まれている。
paramsにはハッシュで情報が保存されるので、params[:id]という書き方で情報を取得する。

params[:id]超わかりやすく解説

・User.new ~ @user.saveでインスタンスが保存される。
@userは {id: 1}というハッシュの情報を持っている。
・で、例えば/users/1というurlにアクセスしたとする。
・このurlは/users/:idという型に当てはまるので{id: 1}という前提でshowアクションが呼び出される。
・重要なのは、urlにアクセスすると、{id: 1}という情報が送られ、それをparamsで取得することができるということ。

debuggerメソッド

app/controllers/users_controller.rb

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

  def new
  end
end

debuggerメソッドはブレイクポイントみたいなもの。
そこで処理を止めて何が起きてるかrails sをしたターミナルに表示される。

ユーザー登録機能を作ろう

form_forを使ったUser登録フォーム

app/views/users/new.html.erb
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_for(@user) do |f| %>
      <%= f.label :name %>
      <%= f.text_field :name %>

      <%= f.label :email %>
      <%= f.email_field :email %>

      <%= f.label :password %>
      <%= f.password_field :password %>

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation %>

      <%= f.submit "Create my account", class: "btn btn-primary" %>
    <% end %>
  </div>
</div>

例えば、
<%= f.label :email %>
<%= f.email_field :email %>
は、ユーザーがemail欄に書いたvalueを:emailというキーに対応させますよーって意味。

<%= f.label :password_confirmation, "Confirmation" %>
はpassword_confirmationという文字列だと長いので、"Confirmation"に上書きするよーって意味。
表示される文字がConfirmationになる。

<%= f.submit "Create my account", class: "btn btn-primary" %>
このボタンを押すと、フォームの中身がparamsに代入される。

createアクションを見てみよう

app/controllers/users_controller.rb
def create
    @user = User.new(params[:user])    # 実装は終わっていないことに注意!
    if @user.save
      # 保存の成功をここで扱う。
    else
      render 'new'
    end 
  end 

本来User登録は、
User.new(name: ~~, email: ~~....)という感じ。

で、paramsには
{user: {name: , email:}}というハッシュが代入されている。
なので、params[:user]とすれば、:userをキーとするハッシュが代入されてインスタンスを作れるが、、、、、

このままだとクラッキングされてしまう。
{ admin: true }などのメッセージを入れられると、管理者権限を付与することになってしまう。

そこで、、

Strong Parametersを使おう

app/controllers/users_controller.rb
class UsersController < ApplicationController
  .
  .
  .
  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
end

こうすることで、permitで指定したキー以外は扱わないよー、変なキーと値が入ってたら弾くよーってしている。

エラーメッセージを出そう。

validationに引っかかって登録に失敗した時、errors.full_messagesオブジェクトは、エラーメッセージの配列を持っています。

なので、

>> user.errors.full_messages
=> ["Email is invalid", "Password is too short (minimum is 6 characters)"]

このように、失敗した理由を出すことができる。エラー要因が複数あれば複数渡してくれる。

登録フォームのviewにエラーメッセージを表示させよう。

app/views/users/new.html.erb
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_for(@user) do |f| %>
      <%= render 'shared/error_messages' %>

      <%= f.label :name %>
      <%= f.text_field :name, class: 'form-control' %>

      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>

      <%= f.label :password %>
      <%= f.password_field :password, class: 'form-control' %>

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation, class: 'form-control' %>

      <%= f.submit "Create my account", class: "btn btn-primary" %>
    <% end %>
  </div>
</div>

render 'shared/error_messages'これはエラーメッセージを表示するviewをパーシャル化しますよーってこと。

ディレクトリの作成

$ mkdir app/views/shared
$ touch app/views/shared/_error_messages.html.erb

app/views/shared/_error_messages.html.erb
<% if @user.errors.any? %>
  <div id="error_explanation">
    <div class="alert alert-danger">
      The form contains <%= pluralize(@user.errors.count, "error") %>.
    </div>
    <ul>
    <% @user.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
<% end %>

signup(失敗時)の統合テストを書いていこう。

$ rails generate integration_test users_signup

インテグレーションテストの名前の付け方は、この場合、signupという一連の動作を確認するものだから、users_signupとしている。

test/integration/users_signup_test.rb
require 'test_helper'

class UsersSignupTest < ActionDispatch::IntegrationTest

  test "invalid signup information" do
    get signup_path
    assert_no_difference 'User.count' do
      post users_path, params: { user: { name:  "",
                                         email: "user@invalid",
                                         password:              "foo",
                                         password_confirmation: "bar" } }
    end
    assert_template 'users/new'
  end
end

post users_pathは/usersにpostリクエストを送っていますよー。
その際に、params: { user: ~~~}を送ってますよーって意味。

上記のテストはユーザー登録が失敗することを期待している。

じゃあ、どうやってそれを判断するのか?

test/integration/users_signup_test.rb

assert_no_difference 'User.count' do
      post users_path, params: { user: { name:  "",
                                         email: "user@invalid",
                                         password:              "foo",
                                         password_confirmation: "bar" } }
    end

assert_no_difference は引数(この場合、User.count)がdo end を実行する前と後では変更ないよね?っていうアサーション。

この場合、validationを設定しているのでuserインスタンスは登録されず、テストは通る。

ユーザー登録成功

まずはcreateアクションの中身を埋めよう

app/controllers/users_controller.rb
class UsersController < ApplicationController
  .
  .
  .
  def create
    @user = User.new(user_params)
    if @user.save
      redirect_to @user
    else
      render 'new'
    end
  end

  private

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

redirect_to @userこれ気になる。

rails routesを見てみよう

user GET    /users/:id(.:format)      users#show
          PATCH  /users/:id(.:format)      users#update
          PUT    /users/:id(.:format)      users#update
          DELETE /users/:id(.:format)      users#destroy

/users/:id は名前付きルートでuser_pathで表すことができる。

本来は user_path(@user.id)で実現できる。
しかし、user_pathはデフォルトで:idに値を入れて渡す

そのため、user_path(@user)というように引数を渡すことでuserインスタンスの情報を渡すことができる。

これをさらに省略すると、
redirect_to @user というようにredirect_toメソッドの引数に直接Userオブジェクトを渡すことで、showアクションへリクエストできる。

補足すると、redirect_toは基本的に指定したurlにgetリクエストを送るという考えでいいと思う。

flash 成功時に一時的なメッセージを出そう!

flashは特殊な変数で、使いたい時はflashという特殊な変数が最初から用意されていると考えるとわかりやすい。実際はメソッド。

usersコントローラに実装してみよう

app/controllers/users_controller.rb
class UsersController < ApplicationController
  .
  .
  .
  def create
    @user = User.new(user_params)
    if @user.save
      flash[:success] = "Welcome to the Sample App!"
      redirect_to @user
    else
      render 'new'
    end
  end

  private

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

flashはキーと値を設定すると、それが次のリクエストまで残り、次の次のリクエストが来た時に消えてくれるという特徴をもつ。

flashメッセージを画面に表示するには?

flashはいろいろなところで使われるので、共通のapplicationテンプレートに表示するためのコードをかくと便利。

具体的には、

app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  .
  .
  .
  <body>
    <%= render 'layouts/header' %>
    <div class="container">
      <% flash.each do |message_type, message| %>
        <div class="alert alert-<%= message_type %>"><%= message %></div>
      <% end %>
      <%= yield %>
      <%= render 'layouts/footer' %>
      <%= debug(params) if Rails.env.development? %>
    </div>
    .
    .
    .
  </body>
</html>

<% flash.each do |message_type, message| %>には先ほどコントローラで設定したキーと値が入っている。

この場合、キーがmessage_type 値が、messageに代入されてeachメソッドが実行される。

先ほどは キーに :successを入れた。

実はbootstrapで alert-successというclassが元から用意されているため、キーをsuccessにした。これはcssによって、緑色の文字と縁を作る。
そのため、実際に表示されるのはmessageに代入された値だけとなる。

成功時のテスト

test/integration/users_signup_test.rb

class UsersSignupTest < ActionDispatch::IntegrationTest
  .
  .
  .
  test "valid signup information" do
    get signup_path
    assert_difference 'User.count', 1 do
      post users_path, params: { user: { name:  "Example User",
                                         email: "user@example.com",
                                         password:              "password",
                                         password_confirmation: "password" } }
    end
    follow_redirect!
    assert_template 'users/show'
  end
end

follow_redirect!は「POSTリクエストを送信した結果を見て、指定されたリダイレクト先に移動するメソッド」だそうです。ちなみに、このアプリケーションではユーザー登録がうまくいった場合そのユーザーのページ(users/show.html.erb)にリダイレクトするようにしています。assert_template 'users/show'はそれをチェックしているわけですね。
つまり、 redirect_toする前のテストの結果を精査してから、redirect_to後のテストに進みたい時に使うってこと?

ch7には以下のように書いてある。

ここで、users_pathにPOSTリクエストを送信した後に、follow_redirect!というメソッドを使っていることに注目してください。このメソッドは、POSTリクエストを送信した結果を見て、指定されたリダイレクト先に移動するメソッドです。したがって、この行の直後では'users/show'テンプレートが表示されているはずです。

これが わかりやすい!!!!!!!

つまり、follow_redirect!は、assert_difference内で、users_pathへのPOSTリクエスト(URL:/users、アクション:create)を送信した結果(レスポンス)を見て、controllerで指定しているリダイレクト先のuser_url @user(ユーザ登録完了後のユーザ画面)へ移動している。

これにより、assert_template 'users/show'がテストされるのはpostリクエストがうまくいった時のみとなる。重要なのはredirect_toの前と後どちらもテストが存在するということ。

SSLを使ったデプロイ

SSLを使うと、http から httpsになる。
httpsは流れる情報が暗号化されるらしい。

herokuのサブドメインの場合は問題ないが、自分で独自ドメインを設定する際はSSL証明書を発行する必要がある。

Railsではありがたいことに、本番環境用の設定ファイルであるproduction.rbのコードをたった1行変更するだけでSSLを強制し、httpsによる安全な通信を確立できます。具体的には次のリスト 7.36に示すように、config.force_sslをtrueに設定するだけで完了です。

config/environments/production.rb
Rails.application.configure do
  .
  .
  .
  # Force all access to the app over SSL, use Strict-Transport-Security,
  # and use secure cookies.
  config.force_ssl = true
  .
  .
  .
end

コメントアウトされてるので外してあげればOK

pumaの設定は7章見ながら設定すればOK

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0