LoginSignup
0
0

More than 1 year has passed since last update.

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

Posted at

目的

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

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

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

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

MVCモデル

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

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

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

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

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

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

Rooter

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

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

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

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

また、resource: に対してコントローラー名を渡すと、railsがよく使う7つのアクション(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)に飛びます。


2つ目のcreateアクションでは

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

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


3つ目の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では、ケースに分けて2つの動作が行われる複雑な処理がされます。

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

2、もしくは、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>

動作について

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


  • 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の挙動を整理したくて、まとめてみました。

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

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

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