Rails: ファイルを役割別に整理する

# 目的
自己整理のために、railsで使用するファイルを役割別にまとめています。

なお、ログイン機能を実装した実際のコードを実例として載せています。

現時点では理解が及んでいない部分も多々ございます。

学習を進めながら、気づきが起こり次第、都度修正していきます。

# MVCモデル
設計モデルの１つで、Model, View, Controllerの頭文字をとったもの。
それぞれ役割が異なり、以下のように設計されています。

* Model(モデル)：データベースに問い合わせ、データの新規作成、更新、削除などを担当する

* View(ビュー)：クライアントに表示するページを生成する

* Controller(コントローラー)：rooterからの指示をもとにサーバー側で必要な処理を行う


【その他】
※Rooter(ルーター)：クライアントからのリクエストに応じて、コントローラーに指示を出す。

※Helper（ヘルパー）：コントローラーで使用するメソッドを定義しておける場所。
　　　　　　　　　　　

# Rooter
ルーターは、各コントローラに対してアクションを指定し、命令を出します。  

設定は、`routes.rb`というファイル１つに集約してまとめられています。

例えば、冒頭の一文`（get  '/home',    to: 'static_pages#home'）`は、次の意味を表します。  

■code意味：
`/homeのURLで、GETリクエストをクライアントから受けた場合、static_pagesコントローラーのhomeアクションを実行しなさい。`

また、resource: に対してコントローラー名を渡すと、railsがよく使う７つのアクション(index, show, new, create, edit, update, destory)を自動的に作成してくれます。

なお、rootはクライアントがはじめに飛ぶページを設定しています。



```config/routes.rb
Rails.application.routes.draw do
  get  '/home',    to: 'static_pages#home'
  get  '/about',   to: 'static_pages#about'
  get  '/contact', to: 'static_pages#contact'
  get  '/signup',  to: 'users#new'
  get    '/login',   to: 'sessions#new'
  post   '/login',   to: 'sessions#create'
  delete '/logout',  to: 'sessions#destroy'
  resources :users
  root 'static_pages#home'
end
```


# Controller

コントローラーは、ルーターからの指示を受けて、具体的な処理を行います。

生成したコントローラーの数だけファイルが存在し、`＜コントローラー名＞.controller.rb`というファイルに処理内容をまとめます。

実例では、はじめの`newアクション`は

`処理を未定義であるため、railsのデフォルト動作をします。`

`デフォルトでは、対応するビュー（＜コントローラー名＞/＜アクション名＞.html.erb）に飛びます。`  
<br>

２つ目の`createアクション`では

* クライアントから送信されたパラメーターに入っているセッション内のemailをもとにユーザー情報を検索し、user変数に格納します
    1. user変数がtrueでかつ、送信されてきたセッション内のパスワードを鍵にユーザー認証がされれば、
        2. ユーザをログインさせて
        3. もしクライアントがremember meのチェックボックスにチェックが入っていれば、PCにユーザ情報を覚えさせ、なければ忘れさせます。
    4. 一方で、ユーザー認証が失敗すれば
        5. １度だけ’emailとpasswordのバリデーション通りませんでした’というエラーメッセージを表示させて
        6. ログインページへ移動させる


といった処理を記述しています。  
<br>

３つ目の`destroyアクション`では

* もしログインしていた場合は、ログアウトさせ、ルートで設定されているURLに移動させる

といった処理を記述しています。

```app/controllers/＜コントローラー名＞_controller.rb
class SessionsController < ApplicationController
  def new; end

  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user&.authenticate(params[:session][:password])
      log_in user
      params[:session][:remember_me] == '1' ? remember(user) : forget(user)
      redirect_to user
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
  end

  def destroy
    log_out if logged_in?
    redirect_to root_url
  end
end
```

# Helper

ヘルパーでは、コントローラーで使用するメソッドを定義できます。

ヘルパーも生成されたコントローラーの数だけ、ファイルが存在します。

`＜コントローラー名＞_helper.rb`という名称で作成されます。

`ヘルパーで、分かりやすいメソッド名でより詳細の処理を定義して、コントローラー側では、可読性が高いコードを記述します。`

要するに、コントローラーでは、デフォルトメソッド＋ヘルパーメソッドを用いてコードを書き、最小限に文字数を抑えて、見やすいコードにします。

例えば、current_userでは、ケースに分けて２つの動作が行われる複雑な処理がされます。

１、セッションの中にユーザIDがあるなら：
　@current_userがnilの場合、DBからユーザIDを鍵にユーザー情報を取得する
　（@current_userがあれば、DBへの問合せをしない　＝　クエリ数を減らす）

２、もしくは、cookieの中にユーザIDがあるなら：
　DBからユーザIDを鍵にユーザー情報を取得して、userに格納して、
　そのuserのnilチェックをした上で、DBに保存しているremember_tokenで認証されれば
　ログインさせて（セッションにユーザIDを入れる）
　インスタンス変数に格納しておく（2回目以降はDB問い合わせ不要になる）

これをコントローラー側に定義すると、読み解くことが難しくなるので
current_user（現在のユーザー情報という意味）といったメソッドを
ヘルパー側で定義しています。

```app/helpers/＜コントローラー名＞_helper.rb
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 (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&.authenticated?(cookies[:remember_token])
        log_in user
        @current_user = user
      end
    end
  end

  def logged_in?
    !current_user.nil?
  end

  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
end

```


# Model

モデルは、DBへの問い合わせを行います。

生成したモデルの数だけ、`＜モデル名＞.rb`というファイルが存在します。

まず、上部コードについて簡単に解説ですが、
・attr_accessor　：　ゲッター・セッターの作成（ここではremember_tokenを指す）
・before_save　：　保存直前の動作指示
・validates　：　各種制限をかける（文字数や必須入力させる等）
・has_secure_password　：　authenticateなど特殊なメソッドを使用できるようにする

さらに、下部コードでは、`DBに関わる（作成、更新、削除）といけない動作は
各モデルのファイルにメソッドを定義します。`

例えば、rememberのように
User.new_tokenで定義されたランダムな文字列で生成されたものをremember_tokenに代入し、
User.digestで定義されたハッシュ化されたremember_tokenを
DBのremember_digestカラムに紐付けて、値を更新させる
といったメソッドは、更新する挙動を設定したいので、モデルのファイルで定義します。

また、forgetも同様で、
DBの:remember_digestに、nilを代入することで忘却させますので
更新する挙動を設定したいので、同ファイルに定義されています。



```app/models/＜モデル名＞.rb
class User < ApplicationRecord
  attr_accessor :remember_token

  before_save { self.email = email.downcase }
  validates :name, presence: true, length: { maximum: 50 }
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i },
                    uniqueness: true
  has_secure_password
  validates :password, presence: true, length: { minimum: 6 }

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

  def self.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)
    return false if remember_digest.nil?

    BCrypt::Password.new(remember_digest).is_password?(remember_token)
  end

  def forget
    update_attribute(:remember_digest, nil)
  end
end

```

# View

ビューは、クライアントに表示するページ生成を担当します。

コントローラーに定義しているアクションによって、表示先を指定することができます。

`＜コントローラー名＞/＜アクション名＞.html.erb`という名称でファイルを作成します。

拡張子に注目いただきたいのですが、`erbという埋め込みrubyのファイル形式`を使用します。

これで`HTML形式の中に<%= %>などを用いて、rubyコードが記述可能になります。`

また、railsには自動的にHTMLを生成してくれる便利なメソッドがあります。

その一つがform_withメソッドで、下記コードの場合は
クライアントから`/loginのURLで入力された情報を
POSTリクエストとして送信する`設定を行ってくれます。


```app/views/＜コントローラー名＞/＜アクション名＞.html.erb
<% provide(:title, "Log in") %>
<h1>Log in</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_with(url: login_path, scope: :session, local: true) do |f| %>

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

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

      <%= f.label :remember_me, class: "checkbox inline" do %>
        <%= f.check_box :remember_me %>
        <span>PCにログイン情報を記憶させる</span>
      <% end %>

      <%= f.submit "ログイン", class: "btn btn-primary" %>
    <% end %>

    <p>新規アカウント作成の場合は <%= link_to "こちらをクリック", signup_path %></p>
  </div>
</div>

```

# 動作について

１つのモデルケースの検証ですが、
`クライアントからのWEBサイトにアクセスがあり、
remember meをチェックされて
loginボタンを押された`ケースを想定すると  
<br>

* `View：new.html.erb`  
　URLを「/login」とPOSTリクエストを設定されたページで
　ユーザーが入力してリクエストを送る

↓  

* `Rooter:routes.rb`  
　ユーザーから送信された
　/loginのURLとPOSTリクエストから
　指令を出すべきコントローラーを判断して指示を出す

↓ 

* `Controller:sessions.controller.rb`  
　Rooterからの指示を受けて、createアクションを実行する。  
　（DBからユーザー検索をして、認証手続きをしてユーザーページに飛ばします。）
  
　・・・` Helper：＜コントローラー名＞_helper.rb` 
　　　　便利メソッドを定めて、Controllerの動作を補助する。
　　　　
↓

* `Model：user.rb`  
　クライアントがremember meにチェックしたことにより、digestに記録する
　トークンを生成して、DBに保存させる。  
　（ログアウトしない限り、2回目以降のログイン時にパスワードなど入力不要になる）

↓

* `View：show.html.erb`  
　ユーザー検索してヒットした該当ユーザーのページを表示させる
  

といった動作となります。



# おわりに

自分なりにrailsの挙動を整理したくて、まとめてみました。

まだ、理解が浅いので、随時で修正すると思います…

最後までお読みいただき、ありがとうございました！