Railsチュートリアル学習 備忘

備忘 1章～


メソッドの()は省略で書かれることが基本
Refactor（コードの可読性とか問題）

ページネーション（htmlを分けるやつ）
<%= render 'layouts/header' %> に対して
_header.html.erb

rails _5.1.6_（バージョン） new apurimei（アプリ名）

Gemfileで設定したら保存してから
bundle install --without production
bundle update

Git
git config --global user.name "Your Name"
git config --global user.email your.email@example.com
（情報を合わせて書かないとGitHubは反応しない）

一連の流れ
git init 準備
git add -A 選択
git commit -m "メッセージ" 反映
git push

git log 確認

git checkout -f 前コミットに戻る
もっと戻すときはブランチ

Heroku
bundle install --without production テストや本番環境があるということ

source <(curl -sL https://cdn.learnenough.com/heroku_install) インストール
よく使う

heroku create 最初に作る
git push heroku master これで送る
heroku run rails db:migrate dbを動かす

REST
ルート
  root 'static_pages#home'
  get  '/help',    to: 'static_pages#help'
  get  '/signup',  to: 'users#new'    （testで get signup_path
  resources :users
HTTPリクエスト  URL	         アクション	用途
GET	            /users	      index	  すべてのユーザーを一覧するページ
GET	            /users/1	    show	  id=1のユーザーを表示するページ
GET	            /users/new	  new	    新規ユーザーを作成するページ
POST	          /users	      create  ユーザーを作成するアクション
GET	            /users/1/edit	edit	  id=1のユーザーを編集するページ
PATCH	          /users/1	    update	id=1のユーザーを更新するアクション
DELETE	        /users/1	    destroy	id=1のユーザーを削除するアクション

ページ名	URL	  名前付きルート
Home	  /	        root_path
About	  /about	  about_path
Help	  /help	    help_path
Contact	/contact	contact_path
Sign up	/signup	  signup_path
Log in	/login	  login_path

users
id      name    email   c_at      up_at
integer string  string  datetime  datetime


関連付け userが１つでmicropostが複数の場合
models/user.rb
  has_many :microposts

models/micropost.rb
  belongs_to :user
  
modelsにはvalidates

継承は
class 子　<　親


ルート
コントローラ
ビュー

rails generate controller 名前 アクション
rails destroy controller 名前 アクション

rails generate model テーブル名 名前:型 名前:型
rails destroy model テーブル名

rails db:igrate
rails db:rollback

テスト
test "テスト名" do
  get static_pages_about_url（get  'static_pages/about'ならば）
  assert_response :success httpのステが200台　普通に表示で成功
                redirect missing error がある
  assert_select  セレクタ, 内容 セレクタ部分が一致で成功
end

レイアウトのリンクテスト
    get root_path
    assert_template 'static_pages/home' （指定が出ていればOK）
    assert_select "a[href=?]", root_path, count: 2（リンク個数も調べられる）
    assert_select "a[href=?]", help_path
    （Railsは?をhelp_pathに自動変換する）
    
Code	                                  マッチするHTML
assert_select "div"	                          <div>foobar</div>
assert_select "div", "foobar"	                <div>foobar</div>
assert_select "div.nav"	                      <div class="nav">foobar</div>
assert_select "div#profile"	                  <div id="profile">foobar</div>
assert_select "div[name=yo]"	                <div name="yo">hey</div>
assert_select "a[href=?]", '/', count: 1	    <a href="/">foo</a>
assert_select "a[href=?]", '/', text: "foo"	  <a href="/">foo</a>
表 5.2: assert_selectのいくつかの使用例

assert @user.valid? バリデが通るか調べる。問題なければtrue
assert_not @user.valid? バリデが通らなければtrue
assert_equal a, b aとbが等しければtrue
assert_no_difference 'User.count' do　変わらなければtrue
（difference 差)


includeメソッド 含ませる
include ApplicationHelper（ヘルパーを使えるようにする）
include? インクルードしているか調べる

yieldメソッド 処理の塊。利回りじゃない
<% provide(:title, "中身") %>
<title><%= yield(:title) %></title>

あ

ハッシュ
abc = {"fname" => "b", "lname" => "d"}
シンボル
:a

ERB 埋め込みrubyの意

bootstrap 機能が仕上がったらテンプレートの変更くらいはする。
メインがデザイン違うから
Bootstrapテンプレートで適当に好きなのを選ぶくらいでいいと思う。


DB関係

find
User.find(1)
User.find_by(:id)

user = User.new(email:"a@gmail.com")
user.save
一発  User.create(email:"a@gmail.com")

user.email = "a@gmail.com"
user.save
一発  user .update_attributes(email: "a@gmail.com")

セキュアなパスワード
has_secure_passwordメソッドをモデルファイルに設置
password_digest属性（列、カラム）追加
bcryptジェムが必要


debug(params) if Rails.env.development?
                          testとproductionもある
                          
countメソッド dbの登録数を数えるのに使う


7章 ユーザー登録
これ以降は慎重に進まないと危険


HTTPリクエスト	URL	        アクション	名前付きルート	             用途
GET         	  /users    	index	      users_path	                 すべてのユーザーを一覧するページ
GET           	/users/1  	show	      user_path(user)	             特定のユーザーを表示するページ
GET           	/users/new	new	        new_user_path	               ユーザーを新規作成するページ (ユーザー登録)
POST          	/users	    create	    users_path	                 ユーザーを作成するアクション
GET	            /users/1/   edit	      edit	edit_user_path(user)	  id=1のユーザーを編集するページ
PATCH         	/users/1	  update	    user_path(user)	             ユーザーを更新するアクション
DELETE        	/users/1	  destroy	    user_path(user)	             ユーザーを削除するアクション
表 7.1: リスト 7.3のUsersリソースが提供するRESTfulなルート

@user = User.find(params[:id]) /user/:idを調べてDBデータを入れている

debugger 止めて調べるやつ

YAML わりと読みやすい形式のファイル

Gravatar メアドと連動させる画像表示サービス

form_forヘルパーメソッド
<%= f.label :name %>
<%= f.text_field :name %>
↓html
<label for="user_name">Name</label>
<input id="user_name" name="user[name]" type="text" />


redirect_to ルーターから呼び出し
render      view直呼び出し

7.3.2
@user = User.new(params[:user])はセキュリティ上問題があるため

params.require(:user).permit(:name, :email, :password, :password_confirmation)
requireメソッド オブジェクト指定
permitメソッド 変更キー指定
他の物がリクエストされても許可のない項目は変更されない。

user_paramsメソッドを作成（慣習）
privateをかけて外部から使えないようにする

@user = User.new(user_params)

    def user_params
      params.require(:user).permit(:name, :email, :password,:password_confirmation)
    end
    
要約    
params[:user]
↓
params.require(:user).permit(:name, :email, :password, :password_confirmation)

セーブで失敗したときのエラメ出し
user.errors.full_messages


エラメを表示する部分

    <%= form_for(@user) do |f| %>
      <%= render 'shared/error_messages' %>

      <%= f.label :name %>
      <%= f.text_field :name, class: 'form-control' %>
shared/error_messagesにパーシャルがある。複数ビューで使うのがsharedフォルダに入る。慣習
app/views/shared/_error_messages.html.erb

pluralize 複数形で表示するテキストヘルパー
.any?　有でtrue empty?の対

7.4.2 flash
flash[:success] = "Welcome to the Sample App!"
↓
div class="alert alert-success">Welcome to the Sample App!</div>


follow_redirect!
POSTリクエストを送信した結果を見て、指定されたリダイレクト先に移動するメソッド
assert_difference 'User.count', 1 ひとつふえていることの確認
assert_not flash.empty? フラッシュが存在しないとOKかと思いきや、assert_notなので存在でOK

7.5
本番ｋ南橋でのデプロイにはいろいろと手順がある。


8章 ログイン機構
認証システム (Authentification System
認可モデル (Authorization Model

session代わりに
cookies使用

RESTfulではなく
名前付きルーティングだけ使用。
  get    '/login',   to: 'sessions#new'
  post   '/login',   to: 'sessions#create'
  delete '/logout',  to: 'sessions#destroy'
HTTPリクエスト  URL	    名前付きルート	アクション名	  用途
GET	            /login	 login_path	    new	          新しいセッションのページ (ログイン)
POST	          /login	 login_path	    create    	  新しいセッションの作成 (ログイン)
DELETE	        /logout  logout_path	  destroy	      セッションの削除 (ログアウト)
表 8.1: リスト 8.2のセッションルールによって提供されるルーティング

8.1.2
Active Record自動生成メッセを使えない。
flashを使う。

    <%= form_for(:session, url: login_path) do |f| %>
      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>
↓
  <label for="session_email">Email</label>
  <input class="form-control" id="session_email"
         name="session[email]" type="text" />

form_for
新規はcreate 編集はupdateが呼び出される。
 , url指定
 第2要素でurl指定


8.1.3 ユーザーの検索と認証

最初に設定
　無効な場合の処理

 def create
    user = User.find_by(email: params[:session][:email].downcase) メアドを見つけて小さく整形
    if user && user.authenticate(params[:session][:password]) DBのuserと比較
      # ユーザーログイン後にユーザー情報のページにリダイレクトする
    else
      flash[:danger] = 'Invalid email/password combination' # 本当は正しくない
      render 'new'

flash[:danger]
↓renderでもメッセージが残らない
flash.now[:danger]

8.2ログイン
  application_controllerにセッションヘルパーを読み込ませる。
  include SessionsHelper
  
セッションヘルパーにどんどんメソッドを定義する
  # log_inメソッド 渡されたユーザーでログインする
  def log_in(user)
    session[:user_id] = user.id idをsessionに渡してブラウザ閉じるまで使う
  end

8.2.2 現在のユーザ���
ユーザーIDをsessionに置いたので

  # 現在ログイン中のユーザーを返す (いる場合)
  def current_user
    if session[:user_id] データがあったら
      @current_user ||= User.find_by(id: session[:user_id]) 
      # @が既にあればそのまま、なければ入れる
    end

( 
・currnt_user.name で名前が取り出したい
・redirect_to current_user でユーザープロフページに行きたい

User.find(session[:user_id]) ユーザーが存在しない場合「例外」になる。
↓
User.find_by(id: session[:user_id]) ユーザーが存在しない場合「nil」を返す。

8.2.3レイアウトリンク変更

<% if logged_in? %>
  # ログインユーザー用のリンク
<% else %>
  # ログインしていないユーザー用のリンク
を作る

  # ユーザーがログインしていればtrue、その他ならfalseを返す
  def logged_in?
    !current_user.nil?
  end

jqueryとbootstrapをライブラリ追加 app/assets/javascripts/application.js
見た目をいい感じにする。 class dropdown-menuのところとdivider(仕切り)

8.2.4 レイアウト変更テスト
ログイン画面行く、ログインする、
ログイン画面 非表示確認、ログアウトリンク 表示確認、プロフリンク 表示確認

BCrypt::Password.create(string, cost: cost) stringがハッシュ化する文字列、costは高いとパス難度が上がる

models/user.rb
  # 渡された文字列のハッシュ値を返す
  def User.digest(string)
    cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                  BCrypt::Engine.cost
    BCrypt::Password.create(string, cost: cost)
  end
テストで使うユーザー用のpassword_digestに対して使うのみ。

test/integration/users_login_test.rb
  def setup
    @user = users(:michael)
  end
  最初にセットするやつ
  
8.2.5 ユーザー登録時にログイン
  def create のsaveの流れに
  log_in @userを加えるのみ

test/test_helper.rb
  # テストユーザーがログイン中の場合にtrueを返す
  def is_logged_in?
    !session[:user_id].nil?
  end
  で有効な登録をしたときのテストで
    assert is_logged_in? がtrueならテスト成功
    
8.3ログアウト
セッションヘルパーにて
  # 現在のユーザーをログアウトする
  def log_out
    session.delete(:user_id) セッションからユーザーidを削除
    @current_user = nil
  end
  メソッドを作った後、
  セッションコントローラーにて
  def destroy
    log_out
    redirect_to root_url
  end
  でルートurlへ飛ばして完了
  
  テストの流れは
  ログアウト後、ログインしてない確認、root_urlへのリダイレクト確認、各リンク確認

有効なログインとログアウト完全版
  test "login with valid information followed by logout" do 
    get login_path
    post login_path, params: { session: { email:    @user.email,
                                          password: 'password' } }
    assert is_logged_in?
    assert_redirected_to @user
    follow_redirect!
    assert_template 'users/show'
    assert_select "a[href=?]", login_path, count: 0
    assert_select "a[href=?]", logout_path
    assert_select "a[href=?]", user_path(@user)
    delete logout_path
    assert_not is_logged_in?
    assert_redirected_to root_url
    follow_redirect!
    assert_select "a[href=?]", login_path
    assert_select "a[href=?]", logout_path,      count: 0
    assert_select "a[href=?]", user_path(@user), count: 0
  end
  
※補足 （たぶんまたわからなくなる）
count: は数字分のリンクが存在するかどうか。
GET           	/users/1  	show	      user_path(user)	             特定のユーザーを表示するページ


まだ大丈夫だと思っていたが、怪しい
9章 発展的なログイン
9.1 remember me 機能
permanent cookies（永続）使う
チェックボックスでユーザーの意を汲む

9.2 記憶トークンと暗号化
remenber token作成 cookiesメソッドで永続的cookies作成、remember digestでトークン認証
パスはユーザーが作成管理。トークンはコンピュータが作成管理する情報。という違い

cookiesメソッドの安全性を確保する。SSLでパケットスニッファ対策、DBに直保存ではなくハッシュ値保存
クロスサイトスクリプティングはRails対策済み、ログアウト時にトークンを変更する。重要情報はデジタル署名

・記憶トークン　ランダム文字列生成使用
・ブラウザにcookiesトークン保存有効期限設定
・トークンはハッシュ値変換でDB保存
・ブラウザのcookies保存はユーザーID暗号化
・永久ユーザーID含むcookies受け、IDをDB検索→記憶トークンのcookiesがDBハッシュ値と一致確認

まず
usersにremember_digest属性追加

何を記憶トークンにするか？
SecureRandom.urlsafe_base64を使う。ランダムの22文字出力
（モジュール.メソッド）

models/user.rb
  # ランダムなトークンを返す
  def User.new_token
    SecureRandom.urlsafe_base64
  end

attr_accessor :name, :email　で　@nameと@emailにアクセスするためのメソッド
（属性に対応するアクセサー）
attr_accessor :remember_token　で　@remember_tokenにアクセスするためのメソッド

仮想の属性

self.remember_token
selfがないとremember_token変数ができてしまう。（不要物

models/user.rb　
attr_accessor :remember_token 追加
  # 永続セッションのためにユーザーをデータベースに記憶する
  def remember
    self.remember_token = User.new_token
    update_attribute(:remember_digest, User.digest(remember_token))
  end

9.1.2 ログイン状態の保持
user.rememberメソッド 暗号化済みID、記憶トークンを永続cookies保存 永久セッション作成準備完了
cookiesメソッド ハッシュ扱い value（値 expires（有効期限 
cookies[:remember_token] = { value:   remember_token,
                             expires: 20.years.from_now.utc } 有効期限20年  
  
cookies[:user_id] = user.id
↓署名つきcookies
cookies.signed[:user_id] = user.id
↓cookieも永続化、signed（署名 とpermanent(恒久的 をメソッドチェーンで繋ぐ
cookies.permanent.signed[:user_id] = user.id

User.find_by(id: cookies.signed[:user_id]) 以後ビューでcookiesを取り出せる


渡したトークンとユーザー記憶ダイジェスト一致確認
BCrypt::Password.new(password_digest) == unencrypted_password
を参考に
BCrypt::Password.new(remember_digest) == remember_token
↓==演算子再定義
BCrypt::Password.new(remember_digest).is_password?(remember_token) 実際は==ではなくis_password?

models/user.rb
  # 渡されたトークンがダイジェストと一致したらtrueを返す
  def authenticated?(remember_token)
    BCrypt::Password.new(remember_digest).is_password?(remember_token)
  end
  
  セッションコントローラーのクリエイト
  ifがtrueに追加
  remember user
  
sessions_helper.rb
  # ユーザーのセッションを永続的にする
  def remember(user)
    user.remember
    cookies.permanent.signed[:user_id] = user.id
    cookies.permanent[:remember_token] = user.remember_token
  end

9.1.3ユーザーを忘れる
ログアウトのための
user.forgetメソッドでuser.rememberを消す

model/user.rb
nil更新する
  # ユーザーのログイン情報を破棄する
  def forget
    update_attribute(:remember_digest, nil)
  end
  
helpers/sessions_helper.rb
  # 永続的セッションを破棄する
  def forget(user)
    user.forget
    cookies.delete(:user_id)
    cookies.delete(:remember_token)
  end

  # 現在のユーザーをログアウトする
  def log_out
    forget(current_user) 追加
    session.delete(:user_id)
    @current_user = nil
  end

9.1.4　2バグ
・同タブ１ログアウト→もう片方ログアウト→エラー　解決はログイン中のみログアウト
・別種ブラウザログイン　remember_digestが存在しないときにfalseを返すauthentiatd?追加
F   G     G
out in　→ ?
１．Fはログアウトでremember_digestがnil エラー
２．Gはsession[:user_id]nilだがブラウザ内にcookiesが残る エラー

セッションコントローラー
  def destroy
    log_out if logged_in? 追加
    redirect_to root_url
  end
  
2種ブラウザシミュレーション？
Userモデルで直接テストで対応

models/user.rb
  # 渡されたトークンがダイジェストと一致したらtrueを返す
  def authenticated?(remember_token)
    return false if remember_digest.nil? 追加
    BCrypt::Password.new(remember_digest).is_password?(remember_token)
  end

9.2 □ Rmember me on this computer 追加
check_box

※※※要注意。前に詰んだ部分※※※
セッションコントローラー

  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      log_in user
      params[:session][:remember_me] == '1' ? remember(user) : forget(user)　追加

特に難しいことをしているわけではないので
前段階でセッティングがおかしかったと思われる。

※※※結※※※

三項演算子
これどう? ? あってる : 違う


9.3.1 Rmember me テスト

params[:session][:remember_me] == '1' ? remember(user) : forget(user)
↓
params[:session][:remember_me] ? remember(user) : forget(user)

0も1も理論値はtrue→常にon状態の動作になるというミス

テスト内でユーザーがログインのヘルパーメソッド作る
log_in_asメソッド


test/test_helper.rb
  # テストユーザーとしてログインする
  def log_in_as(user)
    session[:user_id] = user.id
  end
end

class ActionDispatch::IntegrationTest

  # テストユーザーとしてログインする
  def log_in_as(user, password: 'password', remember_me: '1')
    post login_path, params: { session: { email: user.email,
                                          password: password,
                                          remember_me: remember_me } }
  end
  
integration/users_login_test.rb
  test "login with remembering" do
    log_in_as(@user, remember_me: '1')
    assert_not_empty cookies['remember_token']
  end

  test "login without remembering" do
    # クッキーを保存してログイン
    log_in_as(@user, remember_me: '1')
    delete logout_path
    # クッキーを削除してログイン
    log_in_as(@user, remember_me: '0')
    assert_empty cookies['remember_token']

9.3.2 Rmemberme テスト

helpers/sessions_helper.rb
    elsif (user_id = cookies.signed[:user_id])
      raise       # テストがパスすれば、この部分がテストされていないことがわかる

後で動画見ておいて


問題ないはず
10章 更新表示削除
REST完成する。

ユーザーコントローラ
  def edit
    @user = User.find(params[:id])
  end
  
ビュー書いて
レイアウトにセッティングを追加

//メモ　発火不備・強制遷移のため　完了

10.1.2 失敗

  def update
    @user = User.find(params[:id])
    if @user.update_attributes(user_params)
      flash[:success] = "Profile updated"
      redirect_to @user
    else
      render 'edit'
    end
  end
  
10.1.3 編集失敗時のテスト
統合テスト生成
rails generate integration_test users_edit
編集失敗 editビューの描画で確認

10.1.4 TDD（テスト駆動開発）成功時のテスト
allow_nil: true
nilでバリデをスキップ

10.2 認可
認証(authentication サイトのユーザーを識別
認可(authorization ユーザーが実行可能操作の管理

ログイン中かつ自分のみユーザー情報を編集できるようにする。

10.2.1 ユーザーにログインを要求
beforeフィルターでコントロールの頭にメソッド実行
before_action : メソッド, only: [:アクション名]
    # ログイン済みユーザーかどうか確認
    def logged_in_user
      unless logged_in?
        flash[:danger] = "Please log in."
        redirect_to login_url
      end
    end
    
10.2.2 正しいユーザー要求
  before_action :correct_user,   only: [:edit, :update]
    # 正しいユーザーかどうか確認
    def correct_user
      @user = User.find(params[:id])
      redirect_to(root_url) unless @user == current_user
    end

session_helper.rb
  # 渡されたユーザーがログイン済みユーザーであればtrueを返す
  def current_user?(user)
    user == current_user
  end

ユーザーコントローラ
    # 正しいユーザーかどうか確認
    def correct_user
      @user = User.find(params[:id])
      redirect_to(root_url) unless current_user?(@user)
    end

10.2.3 フレンドリーフォワーディング
親切設計
今　　保護ページ→自分プロフ
親切　保護ページ→ログイン要求→ログインした後→さっきの保護ページ

sessions_helper.rb
  # 記憶したURL (もしくはデフォルト値) にリダイレクト
  def redirect_back_or(default)
    redirect_to(session[:forwarding_url] || default)
    session.delete(:forwarding_url)
  end

  # アクセスしようとしたURLを覚えておく
  def store_location
    session[:forwarding_url] = request.original_url if request.get?
  end

    # ログイン済みユーザーかどうか確認
    def logged_in_user
      unless logged_in?
        store_location　追加
        
セッションコントローラー
  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      log_in user
      params[:session][:remember_me] == '1' ? remember(user) : forget(user)
      redirect_back_or user　追加
    end

10.3 すべてのユーザー表示

def index
allとってきて

indexビューつくって

helpers/users_helper.rb
  # 渡されたユーザーのGravatar画像を返す
  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

cssつけて
ヘッダーつけて
          <li><%= link_to "Users", users_path %></li>
GET         	  /users    	index	      users_path	                 すべてのユーザーを一覧するページ

10.3.2 サンプルのユーザー
gemfile faker
seedsでサンプルユーザーを作る。

10.3.3 ページネーション
gem will_paginate
gem bootstrap-will_paginate
indexを will_paginateでくくって
コントローラで@User.paginate(page: params[:page])

10.3.4 ユーザー一覧のテスト
ok
10.3.5パーシャルフィルタリング
変更前後はtestしておく
<%= render user %>
users/_user.html.erb

10.4 ユーザーを削除する
管理ユーザーを設定して
admin権限が付与された人間のみが削除できる状態にする。

usersにadminカラムを追加。defaultはfalse
  <% if current_user.admin? && !current_user?(user) %>
    | <%= link_to "delete", user, method: :delete,
                                  data: { confirm: "You sure?" } %>
  <% end %>
  押したら即削除とならないようにする。
  

なんともいえない
11章 アカ有効化

１．有効化トークン、ダイジェスト関連付け
２．有効化リンクをメール送信
３．リンククリックで有効化

1.unactivated登録スタート
2.有効化トークン、有効化ダイジェスト生成
3.ダイジェストはDB トークンはメール
4.リンククリックでメアドをキーにユーザーを探してダイジェスト比較で有効化
5.activated

検索キー	string	          digest	            authentication
email   	password	        password_digest	    authenticate(password)
id      	remember_token  	remember_digest	    authenticated?(:remember, token)
email	    activation_token	activation_digest	  authenticated?(:activation, token)
email	    reset_token	      reset_digest	      authenticated?(:reset, token)
表 11.1: ログイン/記憶トークン/アカウントの有効化/パスワードの再設定で似ている点

11.1 AccountActivations アカ有効化

11.2 コントローラ

  resources :account_activations, only: [:edit]
  
名前付きルートの中身
about_path -> '/about'
about_url -> 'http://localhost:3000/about'
よって
メールurlからアクセスする場合はURLである必要がある。

11.1.2 データモデル
メールとDBのトークン照合　ハッシュ化
パスワード実装、記憶トークン実装と同様
仮想的属性でハッシュ化してDB保存

user.activation_token ユーザー認証
user.authenticated?(:activation, token) メソッド改良必要

usersカラム追加
activation_digest:string activated:boolean activated_at:datetime


前実行(トークンとそれに対応するダイジェストを割り当て
before_create :create_activation_digest
create_activation_digestを探す Userモデル内のみ(privateっ設定

model/user.rb
class User < ApplicationRecord
  attr_accessor :remember_token, :activation_token
  before_save   :downcase_email
  before_create :create_activation_digest
  validates :name,  presence: true, length: { maximum: 50 }
  .
  .
  .
  private

    # メールアドレスをすべて小文字にする
    def downcase_email
      self.email = email.downcase
    end

    # 有効化トークンとダイジェストを作成および代入する
    def create_activation_digest
      self.activation_token  = User.new_token
      self.activation_digest = User.digest(activation_token)
    end
end

サンプルではtrueに
             activated: true,
             activated_at: Time.zone.now)

11.2 有効化メール送信
app/views/user_mailer/に2種類htmlとtxtで存在 有効化リンクURLを飛ばす
app/mailers/にメール系の物がある。

11.2.2 送信メールのプレビュー

development環境のメール設定
config/environments/development.rb
host = 'rails-tutorial-mhartl.c9users.io'     # クラウドIDE
config.action_mailer.default_url_options = { host: host, protocol: 'https' }

test/mailers/previews/user_mailer_preview.rb　で確認できる。
もとのURL/rails/mailers/user_mailer/account_activation
もとのURL/rails/mailers/user_mailer/account_activation.txt

11.2.3 送信メールのテスト
assert_match( regexp, string, [msg] ) stringは正規表現 (regexp) にマッチすると主張する。
テストのドメインホストを設定する
config/environments/test.rb
  config.action_mailer.default_url_options = { host: 'example.com' }
  
11.2.4 ユーザーのcreateアクションを更新

  def create
    @user = User.new(user_params)
    if @user.save
      UserMailer.account_activation(@user).deliver_now 以前のようにログインしない。メール
      flash[:info] = "Please check your email to activate your account." 追加
      redirect_to root_url リダイレクト先をプロフィールページからルートURLに変更
    else
      render 'new'
    end
  end
  
  
11.3 アカウントを有効化
11.3.1 authenticated?メソッドの抽象化
有効化トークンとメールをそれぞれparams[:id]とparams[:email]で参照できる

メタプログラミング　プログラムでプログラムを作成する

これから実装するauthenticated?メソッドでは、受け取ったパラメータに応じて呼び出すメソッドを切り替える

def authenticated?(remember_token)
  digest = self.send("remember_digest")
  return false if digest.nil?
  BCrypt::Password.new(digest).is_password?(remember_token)
end
↓引数を一般化、文字列の式展開
def authenticated?(attribute, token)
  digest = self.send("#{attribute}_digest")
  return false if digest.nil?
  BCrypt::Password.new(digest).is_password?(token)
end
↓Rbuyらしく
def authenticated?(attribute, token)
  digest = send("#{attribute}_digest")
  return false if digest.nil?
  BCrypt::Password.new(digest).is_password?(token)
end

app/helpers/sessions_helper.rb
      if user && user.authenticated?(:remember, cookies[:remember_token])

test/models/user_test.rb
    assert_not @user.authenticated?(:remember, '')

11.3.2 editアクションで有効化
authenticated?を手直ししたのでeditが書ける。
!user.activated?　

controllers/account_activations_controller.rb
  def edit
    user = User.find_by(email: params[:email])
    if user && !user.activated? && user.authenticated?(:activation, params[:id])
      user.update_attribute(:activated,    true)
      user.update_attribute(:activated_at, Time.zone.now)
      log_in user
      flash[:success] = "Account activated!"
      redirect_to user
    else
      flash[:danger] = "Invalid activation link"
      redirect_to root_url
    end
  end
これでメールのURLを開くと有効化

controllers/sessions_controller.rb　有効じゃないユーザーのログイン阻止
  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      if user.activated?
        log_in user
        params[:session][:remember_me] == '1' ? remember(user) : forget(user)
        redirect_back_or user
      else
        message  = "Account not activated. "
        message += "Check your email for the activation link."
        flash[:warning] = message
        redirect_to root_url
      end

11.3.3 有効化のテストとリファクタリング
11.4 本番環境でのメール送信
starter tier設定


11がイマイチなのでこちらも同様
12章 パス再設定
changeビュー1つ login
newフォーム2つ lorgot password,  reset password

1.再設定リクでメアドをキーにDBから見つける
2.再設定トークンとリセットダイジェスト生成
3.ダイジェストはDB保存、再生っていトークンはメアドに入れとく
4.クリックでメアドからユーザーを探して、DB保存してた再設定用ダイジェストと比較
5.パスワード変更用フォーム

12.1 PasswordResetsリソース
12.1.1 PasswordResetsコントローラ
rails generate controller PasswordResets new edit --no-test-framework
（大文字にすると頭に_が付く）

  resources :password_resets,     only: [:new, :create, :edit, :update]
HTTPリクエスト	URL	                          Action	  名前付きルート
GET	            /password_resets/new	        new	      new_password_reset_path
POST	          /password_resets	            create	  password_resets_path
GET	            /password_resets/<token>/edit	edit	    edit_password_reset_url(token)
PATCH	          /password_resets/<token>	    update	  password_reset_url(token)
表 12.1: リスト 12.1のPasswordResetsリソースで提供されるRESTfulルーティング  
  

login画面にパスワード再設定画面へのリンクを追加

12.1.2 新しいパスワードの設定
users
rest_digest:string
rest_sent_at:detetime
追加

12.1.3 createアクションでパスワード再設定
メールアドレスでDBからみつける。パス再設定トークンと送信時タイムスタンプでDB属性更新
ルートURLにリダイレクトし、フラッシュメッセージをユーザーに表示

12.2 パスワード再設定のメール送信
11章　password_resetメソッド

12.2.1 パスワード再設定のメールとテンプレート
11章 Userメイラー⇒Userモデル移行のリファクタリング
同様のリファクタリングをパスワード再設定にも

12.2.2 送信メールのテスト
11.20 同様にメイラーメソッドテスト

12.3 パスワードを再設定する
PasswordResetsコントローラのeditアクションの実装
11.3.3と同様に統合テスト行う。

12.3.1 editアクションで再設定
送信メールにパス再設定フォームのビュー
ユーザー検索にedit update メアド必要

12.3.2 パスワードを更新する
updateにて
・期限切れ
・無効パスで失敗
・新しいパスが空文字列
・正しい場合の更新

期限切れ確認メソッドcheck_expiration
password_reset_expired?でif

if params[:user][:password].empty?    新しい文字列の空チェック

elseif @user.update_attributes(user_params) 正しい場合と無効の場合

12.3.3 パスワードの再設定をテストする
テスト

12.4 本番環境でのメール送信 (再掲)
11.4と同じ


かなりしつこくやっているので理解度は高め
13章 ユーザーのマイクロポスト

app/models/micropost.rb
  belongs_to :user
app/models/user.rb
  has_many :microposts
  
メソッド	                        用途
micropost.user	                  Micropostに紐付いたUserオブジェクトを返す
user.microposts	                  Userのマイクロポストの集合をかえす
user.microposts.create(arg)	      userに紐付いたマイクロポストを作成する
user.microposts.create!(arg)	    userに紐付いたマイクロポストを作成する (失敗時に例外を発生)
user.microposts.build(arg)	      userに紐付いた新しいMicropostオブジェクトを返す
user.microposts.find_by(id: 1)	  userに紐付いていて、idが1であるマイクロポストを検索する
表 13.1: user/micropost関連メソッドのまとめ


13.1.4 マイクロポストを改良する
順序

13.2.1 マイクロポストの描画

1つのマイクロポストを表示するパーシャル
_micropost.html.erb

app/controllers/users_controller.rb
  def show
    @user = User.find(params[:id])
    @microposts = @user.microposts.paginate(page: params[:page])
  end
  
マイクロポストの投稿数を表示
user.microposts.count

プロフィールにマイクロポストを表示
app/views/users/show.html.erb

13.2.2 マイクロポストのサンプル
サンプルデータにマイクロポストを追加してmigrate
CSS手直し

13.2.3 プロフィール画面のマイクロポストをテストする
テスト書き
プロフ画面テスト
タイトルなどの一致をみる

13.3 マイクロポストを操作する
作成と破棄
config/routes.rb
  resources :microposts,          only: [:create, :destroy]
  
HTTPリクエスト	URL	          アクション	名前付きルート
POST	          /microposts	  create	    microposts_path
DELETE	        /microposts/1	 destroy	  micropost_path(micropost)
表 13.2: Micropostsリソースが提供するリスト 13.30のRESTfulルート

13.3.2 マイクロポストを作成する
ホーム画面（ルートパス）にフォーム
現時点は Sing up now!のページになっているが

createアクション作る microposts_controller.rb
  def create
    @micropost = current_user.microposts.build(micropost_params)
    if @micropost.save
      flash[:success] = "Micropost created!"
      redirect_to root_url
    else
      render 'static_pages/home'
    end
  end

ビュー static_pages/home.html.erb
<% if logged_in? %>
  <div class="row">
    <aside class="col-md-4">
      <section class="user_info">
        <%= render 'shared/user_info' %>
      </section>
      <section class="micropost_form">
        <%= render 'shared/micropost_form' %>
      </section>
    </aside>
  </div>
<% else %>

・部分
サイドバーshared/_user_info.html.erb
投稿フォーム_micropost_form.html.erb

static_pages_controller.rb
  def home
    @micropost = current_user.microposts.build if logged_in?　ろぐいんしてる場合
  end

views/users/new.html.erb　登録時のエラー表示
    <%= form_for(@user) do |f| %>
      <%= render 'shared/error_messages', object: f.object %>　手直し

users/edit.html.erb
    <%= form_for(@user) do |f| %>
      <%= render 'shared/error_messages', object: f.object %>　手直し

views/password_resets/edit.html.erb　パス再設定
    <%= form_for(@user, url: password_reset_path(params[:id])) do |f| %>
      <%= render 'shared/error_messages', object: f.object %>　手直し

13.3.3 フィードの原型
マイクロポスト表示部分実装
フィード（配信用の加工文書

feedメソッド作成
models/user.rb
  # 試作feedの定義
  # 完全な実装は次章の「ユーザーをフォローする」を参照
  def feed
    Micropost.where("user_id = ?", id)
  end

?でセキュリティ上の何かよくなる。動画で確認
.where 

controllers/static_pages_controller.rb
  def home
    if logged_in?
      @micropost  = current_user.microposts.build
      @feed_items = current_user.feed.paginate(page: params[:page])
    end
    
views/shared/_feed.html.erb

Homeページにステータスフィードを追加

表示ok

13.3.4 マイクロポストを削除する
自分の投稿に対しての削除を作る

マイクロポストパーシャルで削除リンク追加して

destroyを書く
correct_userメソッドを作り
ユーザーが削除対象のマイクロポストを保有しているかどうかを確認

13.3.5 フィード画面のマイクロポストをテスト

  test "should redirect destroy for wrong micropost" do　間違ったユーザーによる削除
    log_in_as(users(:michael))
    micropost = microposts(:ants)
    assert_no_difference 'Micropost.count' do 差なしか比較
      delete micropost_path(micropost)
    end
    assert_redirected_to root_url ホームに戻ったか確認
  end

ここをやれればツイッター的なものからインスタ的なものになれる
13.4 マイクロポストの画像投稿
・画像うｐフォーム
・画像

13.4.1 基本的な画像アップロード
gem carrierwave
gem mini_magick
gem fog

rails generate uploader Picture 画像アップローダ生成

rails generate migration add_picture_to_microposts picture:string 画像のファイル名格納

mount_uploaderメソッド
mount_uploader :属性名シンボル, 生成アップローダクラス名
mount_uploader :picture, PictureUploader

Micropostモデルに画像を追加する
マイクロポスト投稿フォームに画像アップローダーを追加する
pictureを許可された属性のリストに追加する
マイクロポストの画像表示を追加する

13.4.2 画像の検証
拡張子・サイズ問題にバリデ

13.4.3 画像のリサイズ
大きすぎると困る（ImageMagickで対応
sudo yum install -y ImageMagick

app/uploaders/picture_uploader.rb　にコードを書く

13.4.4 本番環境での画像アップロード
fog gem
Simple Storage Service (S3)使用

ひとまず実装できればいいが
いずれは完全理解してもらう
14章 ユーザーフォロー
14.1 Relationshipモデル

14.1.1 データモデルの問題 (および解決策)
  Calvin
  fwer
  fwed +1
  
  Hobbes
  fwer +1
  fwed

user          ⇒relationships                        ⇒user
              follower_id      followerd_id
throught      フォロー側のID    フォローされた側のID   has_many

relationshipsテーブル作成
id follower_id followed_id created_at updated_at

マイグレーションファイル
add_indexでフォローとフォロワーの列に組み合わせをかける

14.1.2 User/Relationshipの関連付け

UserとRelationshipの関連付け　belongs_toとhas_many
user
↓多
relationships
↓多
user

勝手に推測機能が使えない<class>_id

app/models/user.rb
  has_many :active_relationships, class_name:  "Relationship",
                                  foreign_key: "follower_id",
                                  dependent:   :destroy　削除の
                                  
app/models/relationship.rb
  belongs_to :follower, class_name: "User"
  belongs_to :followed, class_name: "User"

メソッド	                                                    用途
active_relationship.follower	                                フォロワーを返します
active_relationship.followed	                                フォローしているユーザーを返します
user.active_relationships.create(followed_id: other_user.id)	userと紐付けて能動的関係を作成/登録する
user.active_relationships.create!(followed_id: other_user.id)	userを紐付けて能動的関係を作成/登録する (失敗時にエラーを出力)
user.active_relationships.build(followed_id: other_user.id)	  userと紐付けた新しいRelationshipオブジェクトを返す
表 14.1: ユーザーと能動的関係の関連付けによって使えるようになったメソッドのまとめ

メソッドさえわかればなんとかなる。

14.1.3 Relationshipのバリデーション
フォローとフォロワーidの存在バリデ

ここから本題
14.1.4 フォローしているユーザー
through（通して

Userモデルにfollowingの関連付けを追加する
app/models/user.rb
  has_many :following, through: :active_relationships, source: :followed　following配列の元はfollowed idの集合である

<<演算子 (Shovel Operator) で配列の要素追加


“following” 関連のメソッドをテスト
  test "should follow and unfollow a user" do
    michael = users(:michael)
    archer  = users(:archer)
    assert_not michael.following?(archer) していない状態
    michael.follow(archer)　フォロー
    assert michael.following?(archer)　している状態
    michael.unfollow(archer)　解除
    assert_not michael.following?(archer)　していない状態
  end

メソッドを定義
models/user.rb
  # ユーザーをフォローする
  def follow(other_user)
    following << other_user
  end

  # ユーザーをフォロー解除する
  def unfollow(other_user)
    active_relationships.find_by(followed_id: other_user.id).destroy
  end

  # 現在のユーザーがフォローしてたらtrueを返す
  def following?(other_user)
    following.include?(other_user)
  end

14.1.5 フォロワー
前でフォローを作ったのでフォロワーも同じような流れで作れるのではないか
受動的関係を使ってuser.followersを実装する
app/models/user.rb
今回はsourceを省略できる

テスト
    assert archer.followers.include?(michael)　フォロワーに含まれているか

14.2.1 フォローのサンプルデータ
db/seeds.rb

# リレーションシップ
users = User.all
user  = users.first
following = users[2..50]
followers = users[3..40]
following.each { |followed| user.follow(followed) }
followers.each { |follower| follower.follow(user) }
最初のユーザーにユーザー3からユーザー51までをフォローさせ、それから逆にユーザー4からユーザー41に最初のユーザーをフォロー

14.2.2 統計と [Follow] フォーム
プロフィールとhomeに
フォローとフォロワーを表示

routers.rbに追加
  resources :users do
    member do
      get :following, :followers
    end
  end
HTTPリクエスト	URL	                アクション	名前付きルート
GET	            /users/1/following	following	  following_user_path(1)
GET	            /users/1/followers	followers	  followers_user_path(1)
表 14.2: カスタムルールで提供するリスト 14.15のRESTfulルート

フォロワーの統計情報を表示するパーシャル
app/views/shared/_stats.html.erb
<% @user ||= current_user %>
<div class="stats">

  <a href="<%= following_user_path(@user) %>">　飛ばし
    <strong id="following" class="stat">　強調タグにidとクラス付与
      <%= @user.following.count %>　数表示
    </strong>
    following
  </a>
  
  <a href="<%= followers_user_path(@user) %>">　ほぼ同上
    <strong id="followers" class="stat">
      <%= @user.followers.count %>
    </strong>
    followers
  </a>
  
</div>

Homeページにフォロワーの統計情報を追加する
app/views/static_pages/home.html.erb
      <section class="stats">
        <%= render 'shared/stats' %>　パーシャルくっつけ
      </section>
      
CSS変更
完成

フォロー/フォロー解除フォームのパーシャル
app/views/users/_follow_form.html.erb
<% unless current_user?(@user) %>
  <div id="follow_form">
  <% if current_user.following?(@user) %>
    <%= render 'unfollow' %>
  <% else %>
    <%= render 'follow' %>
  <% end %>
  </div>
<% end %>


Relationshipリソース用のルーティングを追加する
config/routes.rb
  resources :relationships,       only: [:create, :destroy]
 ユーザーをフォロー・解除するフォーム
app/views/users/_follow.html.erb
app/views/users/_unfollow.html.erb


プロフィールページにフォロー用フォームとフォロワーの統計情報を追加する
app/views/users/show.html.erb
    <section class="stats">
      <%= render 'shared/stats' %>　_stats.html.erbステータスのパーシャルだし
    </section>
  </aside>
  <div class="col-md-8">
    <%= render 'follow_form' if logged_in? %>　_follow_form.html.erbフォームのパーシャル出し


14.2.3 [Following] と [Followers] ページ
フォローしているユーザーとフォロワーの両方を表示するshow_followビュー green
app/views/users/show_follow.html.erb
followingの場合、followersの場合、両方対応ビュー
コントローラーで@usersの中身を変えてrenderでビューに飛ばすことで対応している。

14.2.4 [Follow] ボタン (基本編)
rails generate controller Relationships

Relationshipsコントローラ
app/controllers/relationships_controller.rb
  def create
    user = User.find(params[:followed_id]) フォロワーidでみつけて
    current_user.follow(user)　ユーザーフォローして
    redirect_to user　飛ばす
  end

  def destroy
    user = Relationship.find(params[:id]).followed
    current_user.unfollow(user)
    redirect_to user
  end
end


14.2.5 [Follow] ボタン (Ajax編)
非同期にする。
ポートフォリオに取り入れたい機能。

Ajaxを使ったフォローフォーム
app/views/users/_follow.html.erb
<%= form_for(current_user.active_relationships.build, remote: true) do |f| %>　remote: true追加だけ

RelationshipsコントローラでAjaxリクエストに対応する
app/controllers/relationships_controller.rb
  def create
    @user = User.find(params[:followed_id])
    current_user.follow(@user)
    respond_to do |format|　respond_toメソッド if文を使った分岐処理に近い　html形式でもjs形式でも返してくれる
      format.html { redirect_to @user }
      format.js
    end
  end

  def destroy
    @user = Relationship.find(params[:id]).followed
    current_user.unfollow(@user)
    respond_to do |format|
      format.html { redirect_to @user }
      format.js
    end
  end
end

JavaScriptが無効になっていたときのための設定
config/application.rb
module SampleApp
  class Application < Rails::Application
    .
    .
    .
    # 認証トークンをremoteフォームに埋め込む
    config.action_view.embed_authenticity_token_in_remote_forms = true


JavaScriptと埋め込みRubyを使ってフォローの関係性を作成する
app/views/relationships/create.js.erb
$("#follow_form").html("<%= escape_javascript(render('users/unfollow')) %>"); jsなので;ある
$("#followers").html('<%= @user.followers.count %>');
 Ruby JavaScript (RJS) を使ってフォローの関係性を削除する
app/views/relationships/destroy.js.erb
$("#follow_form").html("<%= escape_javascript(render('users/follow')) %>");
$("#followers").html('<%= @user.followers.count %>');

これらのコードにより、プロフィールページを更新させずにフォローとフォロー解除ができるようになった


14.2.6 フォローをテストする
xhr (XmlHttpRequest) というオプションをtrueに設定すると、Ajaxでリクエストを発行する
xhr: true

[Follow] / [Unfollow] ボタンをテストする
今はよい

14.3 ステータスフィード
14.3.1 動機と計画

ステータスフィードのテスト red
test/models/user_test.rb
  test "feed should have the right posts" do
    michael = users(:michael)
    archer  = users(:archer)
    lana    = users(:lana)
    # フォローしているユーザーの投稿を確認
    lana.microposts.each do |post_following|
      assert michael.feed.include?(post_following)
    end
    # 自分自身の投稿を確認
    michael.microposts.each do |post_self|
      assert michael.feed.include?(post_self)
    end
    # フォローしていないユーザーの投稿を確認
    archer.microposts.each do |post_unfollowed|
      assert_not michael.feed.include?(post_unfollowed)
    end

14.3.2 フィードを初めて実装する
取得したuser_idをうまく処理して表示に使う。
とりあえず動くフィードの実装 green
app/models/user.rb
class User < ApplicationRecord
  .
  # パスワード再設定の期限が切れている場合はtrueを返す
  def password_reset_expired?
    reset_sent_at < 2.hours.ago
  end

  # ユーザーのステータスフィードを返す
  def feed
    Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
  end

  # ユーザーをフォローする
  def follow(other_user)
    following << other_user
  end
  .
end

小規模ならok

14.3.3 サブセレクト

SQLで改善

SQLサブセレクトを使う
フィードの最終的な実装 green
app/models/user.rb
  def feed
    following_ids = "SELECT followed_id FROM relationships フォロワーidを選択して リレーションシップを検索
                     WHERE follower_id = :user_id"　フォロワーidとユーザーidが同じ行を指定
    Micropost.where("user_id IN (#{following_ids})
                     OR user_id = :user_id", user_id: id)
  end




--20191121