RailsTutrial11章

紹介

アカウント有効化

トークンとメールアドレスの情報の2つをURLに含める。 (URLパラメータとか) 色々含んだURLを盛り込む。

f:id:shiness:20210115123946p:plainf:id:shiness:20210115124024p:plainf:id:shiness:20210115124034p:plainf:id:shiness:20210115124059p:plain

activation

したのかしてないのかのカラムも必要

f:id:shiness:20210115125150p:plain

editアクションがちょうどよい。/:id が入れられるから showアクションはリソースを変更しないから普通。 Restfulの規則的にはeditの方がふさわしいので。

sessionのときと同じかんじ

    def create_activation_digest
      self.activation_token  = User.new_token
      self.activation_digest = User.digest(activation_token)
    end

attr_accessorに追加 before_create : userがsign_upされる直前に必要。

ActionMailerについて

View → Mail Controller → Mailer

f:id:shiness:20210115141255p:plain

 メールの中に何を含めるか

f:id:shiness:20210115141607p:plain

 actionと違って引数がとれるメソッドと呼ぶべき コントローラを継承しているけど

 => return: mail object (text/html) example: mail.deliver

アカウント有効化のプレビューメソッド

 def account_activation
    user = User.first
    user.activation_token = User.new_token
    UserMailer.account_activation(user)
  end

signup時のcreateアクションを更新

f:id:shiness:20210115153206p:plain

オマケトピックス

9章を参考に作ってもよいが @user.authenticated?を便利にしたい!

f:id:shiness:20210115154406p:plain

メソッドを使い分けたいときどうするか?

エラーになる。以下のことを実現したい。

@user.remember_diggest

上に対して
a = "remember"

@user.#{a}digest @user"#{a}digest"

rubyの機能に`send`メソッドがある。
"文字列でも書ける"
メソッド名が一致していないとこの前提条件を満たすことができない!!!
未来を予想して作らないと無理
`メタプログラミングの序章`

attribute = :activation user.send("#{attribute}_digest")

nilじゃないか確認&2回目のクリックしたとかがないか& <span style="color: #62c7c9">params[:id]となっているけどこれはトークンが入っている</span>

  if user && !user.activated? && user.authenticated?(:activation, params[:id]) user.update_attribute(:activated, true) user.update_attribute(:activated_at, Time.zone.now)


# 目立たないバグ

 activation前にログインできてしまう問題


# リファクタリング

   メソッド化する

UserMailer.accont...これがメールに飛ぶっていうのがわかりにくいのでメソッド化する

RailsTutorial9章

参考資料前提

f:id:shiness:20210113201732p:plain f:id:shiness:20210113201802p:plain [f:id:shiness:20210113201902p:plain f:id:shiness:20210113201912p:plain f:id:shiness:20210113201920p:plain

ユーザIDの暗号化 IDとTokenもcookieの中で暗号化したい。→@user.authenticate!した後にログイン不要になる。 attr_accessor データベースには保存しないがインスタンス変数には保存したい。

トークン生成用メソッドを追加

さしあたっての実装計画としては、user.rememberメソッドを作成することにします。このメソッドは記憶トークンをユーザーと関連付け、トークンに対応 する記憶ダイジェストをデータベースに保存します。リスト 9.1のマイグレーションは実行済みなので、Userモデルには既にremember_digest属性が追加 されていますが、remember_token属性はまだ追加されていません。このためuser.remember_tokenメソッドを使ってトークンにアクセスできるようにし、 かつ、トークンをデータベースに保存せずに実装する必要があります。そこで、6.3で行ったパスワードの実装と同様の手法でこれを解決します。あのとき は仮想のpassword属性と、データベース上にあるセキュアなpassword_digest属性の2つを使いました。仮想のpassword属性はhas_secure_passwordメソ ッドで自動的に作成されましたが、今回はremember_tokenのコードを自分で書く必要があります。これを実装するため、4.4.5で触れたattr_accessorを使 って「仮想の」属性を作成します。

  def remember
    self.remember_token = ...
    update_attribute(:remember_digest, ...)
  end

rememberメソッドの1行目の代入にご注目ください。selfというキーワードを使わないと、Rubyによってremember_tokenという名前のローカル変数が成 されてしまいます。この動作は、Rubyにおけるオブジェクト内部への要素代入の仕様によるものです。今欲しいのはローカル変数ではありません。

  def User.new_token
    SecureRandom.urlsafe_base64
  end

記憶トークcookieに対応するユーザを返す

  def current_user
    if (user_id = session[:user_id])
      @current_user ||= User.find_by(id: user_id)
    elsif (user_id = cookies.signed[:user_id])
      user = User.find_by(id: user_id)
      if user && user.authenticated?(cookies[:remember_token])
        log_in user
        @current_user = user
      end
    end
class User < ApplicationRecord
  attr_accessor :remember_token
  before_save { email.downcase! }
  validates :name,  presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: true
  has_secure_password
  validates :password, presence: true, length: { minimum: 6 }


  def User.digest(string)
    cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                BCrypt::Engine.cost
    BCrypt::Password.create(string, cost: cost)
  end
  
  def User.new_token
    SecureRandom.urlsafe_base64
  end

  def remember
    self.remember_token = User.new_token
    update_attribute(:remember_digest, User.digest(remember_token))
  end

  def authenticated?(remember_token)
    BCrypt::Password.new(remember_digest).is_password?(remember_token)
  end
end
class SessionsController < ApplicationController
  
  def new
  end

  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if @user && @user.authenticate(params[:session][:password])
      log_in user
      remember user
      redirect_to user
    else
      flash.now[:danger] = 'Invalid email/password combination' # 本当は正しくない
      render 'new'
    end
  end

  def destroy
    log_out
    redirect_to root_url
  end
end
module SessionsHelper
  
  def log_in(user)
    session[:user_id] = user.id
  end

  def remember(user)
    user.remember
    cookies.permanent.signed[:user_id] = user.id
    cookies.permanent[:remember_token] = user.remember_token
  end

  def current_user
    if session[:user_id]
      @current_user ||= User.find_by(id: session[:user_id])
    end
  end
  
  def logged_in?
    !current_user.nil?
  end

  def log_out
    session.delete(:user_id)
    @current_user = nil
  end
end

目立たないバグ

今のcurrent_userの使い方では、ユーザーが1つのタブでログアウトし、もう1つのタブで再度ログアウトしようとするとエラーになってしまいます。これは、もう1つのタブで "Log out" リンク>をクリックすると、current_userがnilとなってしまうため、log_outメソッド内のforget(current_user)が失敗してしまうからです(リスト 9.12)13 。この問題を回避するためには、ユーザー がログイン中の場合にのみログアウトさせる必要があります。

ユーザーが複数のブラウザ(FirefoxChromeなど)でログインしていたときに起こります。例えば、Firefoxでログアウトし、Chromeではログアウトせずにブラウザを終了させ、再度Chrome で同じページを開くと、この問題が発生します14 。FirefoxChromeを使った具体例で考えてみましょう。ユーザーがFirefoxからログアウトすると、user.forgetメソッドによって>remember_digestがnilになります(リスト 9.11)。この時点では、Firefoxでまだアプリケーションが正常に動作しているはずです。このとき、リスト 9.12ではlog_outメソッドによってユーザ ーIDが削除されるため、ハイライトされている2つの条件はfalseになります。

def current_user
  if (user_id = session[:user_id])
    @current_user ||= User.find_by(id: user_id)
  elsif (user_id = cookies.signed[:user_id])
    user = User.find_by(id: user_id)
    if user && user.authenticated?(cookies[:remember_token])
      log_in user
      @current_user = user
    end
  end
end

RailsTutorial8章

積読

全体像

f:id:shiness:20210113175702p:plain

メソッドについて

    user = User.find_by(email: params[:session][:email].downcase)
    if @user && @user.authenticate(params[:session][:password])

if@userがないと一行目でnilが返って来たときにnilに対してauthenticate を使うことになってerrorが出ることを防ぐ目的がある。

renderとredirect_toの違い

参考 【Rails】renderとredirect_toの違いと使い分け - Qiita

DB問い合わせ削減の書き方

  def current_user
    if session[:user_id]
      @current_user ||= User.find_by(id: session[:user_id])
    end
  end

if session[:user_id]と予め書いておくことでfind_byの試行回数を減らせる。 DB問い合わせ数削減のため。

@current_userがnilまたはfalseだったら右を実行する。 これもDBへの問い合わせ削減。インスタンス変数の寿命の長さを利用したコ−ド。

なぜsessions_controllerではインスタンス変数ではなくローカル変数を使うのか

Ruby - railsチュートリアル 8章 createアクション内の変数についての質問|teratail

def login(user)の理由について

  def log_in(user)
    session[:user_id] = user.id
  end

log_in(user)とuserを引数にとらないようにするため 以下のように書けるが

session[:user_id] = @user.id

一般的に Helper は参照透過性のある関数として書かれることが良いとされるため 別の Controller からも log_in ヘルパーを利用したくなった時に、「log_in ヘルパーを呼ぶ前に予め @user というインスタンス変数にログインしたいユーザーのインスタンスをセットしておくこと」 という複雑なルールを強いられることになります。それよりも、単純に log_in のシグネチャーとして「引数で渡されたユーザーでログインする」という挙動にしておいた方がシンプルなので設計として 落ち着きやすいわけです。

application_helperとapplication_controllerに書くときの違いについて

application_helper viewをDRYにするためのもの

application_controller 共通のメソッドをまとめるためのもの

session_helper

module SessionsHelper
  
  def log_in(user)
    session[:user_id] = user.id
  end

  def current_user
    if session[:user_id]
      @current_user ||= User.find_by(id: session[:user_id])
    end
  end
  
  def logged_in?
    !current_user.nil?
  end

  def log_out
    session.delete(:user_id)
    @current_user = nil
  end
end

sessions_controller.rb

class SessionsController < ApplicationController
  
  def new
  end

  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if @user && @user.authenticate(params[:session][:password])
      log_in user
      redirect_to user
    else
      flash.now[:danger] = 'Invalid email/password combination' # 本当は正しくない
      render 'new'
    end
  end

  def destroy
    log_out
    redirect_to root_url
  end
end

application_controller

class ApplicationController < ActionController::Base
  include SessionsHelper
end

users_controller

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

  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    if @user.save
      log_in @user
      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

RailsTutorial7章

再復習している本

たのしいRuby 第6版

たのしいRuby 第6版

改訂2版 パーフェクトRuby

改訂2版 パーフェクトRuby

Railsの3つの環境

Railsにはテスト環境(test)、開発環境(development)、そして本番環境(production)の3つの環境がデフォルトで装備されています。Rails consoleのデフォルトの環境はdevelopmentです。

ストロングパラメータ

paramsハッシュ全体を初期化するという行為はセキュリティ上、極めて危険だからです。これは、ユーザーが送信したデータをまるごとUser.newに渡していることになります。ここで、Userモデルにadmin属性というものがあるとしましょう。この属性>は、Webサイトの管理者であるかどうかを示します(この属性を実装するのは10.4.1になってからです)。admin=’1’という値をparams[:user]の一部に紛れ込ませて渡してしまえば、この属性をtrueにすることができます。これはcurlなどのコマンドを使 えば簡単に実現できます。paramsハッシュがまるごとUser.newに渡されてしまうと、どのユーザーでもadmin=’1’をWebリクエストに紛れ込ませるだけでWebサイトの管理者権限を奪い取ることができてしまいます

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

エラー数を英語で表記

<%= pluralize(@user.errors.count, "error") %>

>> helper.pluralize(5, "error")
=> "5 errors"

このコードは、例えば"0 errors"、"1 error"、"2 errors"などのように、エラーの数に応じて活用された単語を返します。これにより、"1 errors" のような英語の文法に合わない文字列を避けることができます(これはWebアプリでもデスクトップアプリで>も実によく見かけるエラーです)。

redirect_to @userについて

redirect_to @userはredirect_to user_url(@user)と等価

エラーメッセージ

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

ターミナル

irb(main):007:0> flash = { success: "It worked!", danger: "It failed." ,info: "aaa"}
=> {:success=>"It worked!", :danger=>"It failed.", :info=>"aaa"}
irb(main):008:0>  flash.each do |key, value|
irb(main):009:1* puts "#{key}"
irb(main):010:1>  puts "#{value}"
irb(main):011:1> end
success
It worked!
danger
It failed.
info
aaa
=> {:success=>"It worked!", :danger=>"It failed.", :info=>"aaa"}
irb(main):012:0> 

RailsTutorial6章

db:migrateエラー

sandboxを終了しないとdb:migrateが失敗する

rails aborted!
StandardError: An error has occurred, this and all later migrations canceled:

SQLite3::BusyException: database is locked

メールアドレスについて

メールアドレスの一意性を保証するためには、もう1つやらなければならないことがあります。 それは、いくつかのデータベースのアダプタが、常に大文字小文字を区別するインデックス を使っているとは限らない問題への対処です。 例えば、Foo@ExAMPle.Comとfoo@example.comが別々の文字列だと解釈してしまうデータベースがありますが、私達のアプリケーションではこれらの文字列は同一であると解釈されるべきです。 この問題を避けるために、今回は「データベースに保存される直前にすべての文字列を小文字に変換する」という対策を採ります。例えば"Foo@ExAMPle.CoM"という文字列が渡されたら、 保存する直前に"foo@example.com"に変換してしまいます。これを実装するためにActive Recordのコールバック(callback)メソッドを利用します。このメソッドは、ある特定の時点で呼び出されるメソッドです。

before_save { self.email = email.downcase } before_save { email.downcase! }

ハッシュ化されたパスワード

has_secure_password

セキュアにハッシュ化したパスワードを、データベース内のpassword_digestという属性に保存できるようになる。 2つのペアの仮想的な属性(passwordpassword_confirmation)が使えるようになる。また、存在性と値が一致するかどうかのバリデーションも追加される19 。 authenticateメソッドが使えるようになる(引数の文字列がパスワードと一致するとUserオブジェクトを、間違っているとfalseを返すメソッド)。

この魔術的なhas_secure_password機能を使えるようにするには、1つだけ条件があります。それは、モデル内にpassword_digestという属性が含まれていることです。

以下を手動で行う必要あり

$ rails generate migration add_password_digest_to_users password_digest:string $ rails db:migrate

  • has_secure_passwordを使ってパスワードをハッシュ化するためには、最先端のハッシュ関数であるbcryptが必要

RailsTutorial4章

application_helperの使い方

メソッドが呼ばれるとき→viewで

apprication_helper.rb

module ApplicationHelper
  # ページごとの完全なタイトルを返します。
  def full_title(yield_title)
    base_title = "Ruby on Rails Tutorial Sample App"
    if yield_title.empty?
      base_title
    else
      yield_title + " | " + base_title
    end
  end
end

application.html.erb

<!DOCTYPE html>
<html>

<head>
  <title><%= full_title(yield(:title)) %></title>
  <%= csrf_meta_tags %>
  <%= csp_meta_tag %>
  <%= stylesheet_link_tag    'application', media: 'all',
                               'data-turbolinks-track': 'reload' %>
  <%= javascript_pack_tag 'application',
                               'data-turbolinks-track': 'reload' %>
</head>

<body>
  <%= yield %>
</body>

</html>

home.html.erb

<% provide(:title, "Home") %>
<!DOCTYPE html>
<html>

<head>
  <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
</head>

<body>
  <h1>Sample App</h1>
  <p>
    This is the home page for the
    <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
    sample application.
  </p>
</body>

</html>

参考 https://qiita.com/yukiyoshimura/items/f0763e187008aca46fb4

  • full_title(yield(:title))ここで引数を渡している。

<% provide(:title, "Help") %>

<title><%= yield(:title) %>

provideで定義してyieldで呼び出す。

スッキリわかるJava入門3②

メソッド呼び出しについて

test()の ()は変数名区別するために書かれているので引数なしのメソッド呼び出しを 意味しているわけではないことに注意。

mainメソッド以外からのメソッドの呼び出し

public class Main {
  public static void methodA() {
    System.out.println("methodA");
    methodB();
  }
  public static void methodB() {
    System.out.println("methodB");
  }
  public static void main(String[] args) {
    methodA();
  }
}

ソースコードの中に複数のメソッドが定義されている場合、 その順序には意味や特約があるわけではない。 ソースコード内に定義された各メソッドは同列の存在であって 上に記載されたものから順番に動くというわけではない。