フルスタックエンジニアに

フルスタックエンジニアを目指すブログです。主にRuby on RailsやJavaScriptについて書いていきたいと思います。

JavaScriptフレームワーク比較用サンプルアプリ 「TODO-Module」を作る (2/3)

TODO-Module-Rails-eye-catch

前回の続きです。

www.full-stack-engineer.com

ログイン画面ができたので次はユーザー登録機能を作っていきます。

ユーザー登録機能を作る

% bin/rails g controller users new create --skip-routes

/app/controllers/users_controller.rb

class UsersController < ApplicationController

  skip_before_filter :require_login, only: [:new, :create]
  wrap_parameters include: [:email, :password, :password_confirmation]

  def new
    if session[:new_user_params].present?
      @user = User.new(session[:new_user_params])
      @user.valid?
      session.delete(:new_user_params)
    else
      @user = User.new
    end
  end

  def create
    @user = User.new(user_params)

    if @user.save
      auto_login(@user)
      redirect_to tasks_path, notice: '登録しました。'
    else
      flash[:alert] = "入力エラーがあります。"
      session[:new_user_params] = user_params
      redirect_to new_user_path
    end
  end

  private

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

ここで一つ工夫をしています。scaffoldで生成されるcreateメソッドはバリデーションエラーが起きると render :newでnewのテンプレートを表示します。
最初はURLが/users/newなのに、バリデーションエラーが起こった時だけ 画面は一緒でURLが/usersなのは気持ちが悪いです。 また、今後作っていくクライアントと挙動も変わってしまうのでE2Eテストをしていく上でも都合が悪いです。 なのでrender :newせずにredirect_to new_user_pathでnewにリダイレクトしています。

/app/views/users/new.html.haml

%h1 新規登録

.well
  =simple_form_for @user do |f|
    =f.input :email, required: true
    =f.input :password, required: true
    =f.input :password_confirmation, required: true
    =f.submit "Save", class: "btn btn-primary btn-block"

createテンプレートは必要ないので削除します。

% rm app/views/users/create.html.haml

ルーティングを追記します。

config/routes.rb

Rails.application.routes.draw do
・
・
  resources :users, only: [:new, :create, :edit, :update]
・
・
end

ブラウザに戻り、右上の新規登録画面をクリックしましょう。

TODO-Module 新規登録

適当なメールアドレスとパスワードを入力するとユーザーが作成されてタスク一覧ページに飛びます。

TODO-Module タスク 途中

一旦右上のログアウトをクリックしてログアウトしてください。 その状態でhttp://localhost:3000/tasksにアクセスするとタスク一覧ページが表示されてしまいます。 ここはログインが必要なページですので、制限をかけましょう。 以下を追記してください。

/app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
・
・
・
  before_filter :require_login

  private

  def not_authenticated
    redirect_to root_path, alert: "ログインしてください。"
  end
end

これで、ログイン状態でないとトップページに飛ばされるようになりました。 先ほど登録したメールアドレスとパスワードでログインしてください。

タスク機能を作る

それではメインのタスク機能を作っていきましょう。

/app/controllers/tasks_controller.rb

class TasksController < ApplicationController

  before_action :set_count, only: [:index, :completed, :deleted]
  before_action :set_task, only: [:edit, :update, :destroy, :complete, :revert]
  before_action :set_new_task, only: [:index, :completed, :deleted]

  def index
    @tasks = current_user.tasks.inbox.order("id desc")
  end

  def completed
    @tasks = current_user.tasks.completed.order("id desc")
    render :index
  end

  def deleted
    @tasks = current_user.tasks.deleted.order("id desc")
    render :index
  end

  def edit
    if session[:task_params]
      @task.attributes = session[:task_params]
      @task.valid?
      session.delete(:task_params)
    end
  end

  def create
    @task = current_user.tasks.build(task_params)
    if @task.save
      redirect_to tasks_path, notice: '作成しました。'
    else
      redirect_to tasks_path, alert: "入力してください。"
    end
  end

  def update
    if @task.update(task_params)
      redirect_to tasks_path, notice: '更新しました。'
    else
      flash[:alert] = '入力エラーがあります。'
      session[:task_params] = task_params
      redirect_to edit_task_path
    end
  end

  def destroy
    @task.delete!
    redirect_to :back, notice: 'ゴミ箱に入れました。'
  end

  def complete
    @task.complete!
    redirect_to :back, notice: "完了にしました。"
  end

  def revert
    @task.revert!
    redirect_to :back, notice: "収集箱に戻しました。"
  end

  private

  def set_task
    @task = current_user.tasks.find(params[:id])
  end

  def task_params
    params.require(:task).permit(:title, :memo)
  end

  def set_new_task
    @new_task = Task.new
  end

  def set_count
    @inbox_count = current_user.tasks.inbox.count
    @completed_count = current_user.tasks.completed.count
    @deleted_count = current_user.tasks.deleted.count
  end
end

/config/routes.rb

Rails.application.routes.draw do
・
・
  resources :tasks, except: [:show] do
    collection do
      get "completed"
      get "deleted"
    end
    member do
      patch "complete"
      patch "revert"
    end
  end
end

特に難しいことはしていません。 普通のCRUDに加えて完了済みだけを取得するcompleted、削除済みだけを取得するdeleted、 完了にするcompleted、ゴミ箱に入れるdestroy、それと収集箱に戻すrevertメソッドを定義しています。
また、状態別のカウントを取得するset_countメソッドを定義して、 必要な場所でbefore_actionで呼んでいます。

/app/views/tasks/index.html.haml

.row
  #sidebar.col-sm-2
    %h3 収集
    %ul.list-unstyled
      =nav_link tasks_path, class: "clearfix" do
        .pull-left
          %i.glyphicon.glyphicon-inbox
          収集箱
        .pull-right.inbox-count=@inbox_count
    %h3 終了
    %ul.list-unstyled
      =nav_link completed_tasks_path, class: "clearfix" do
        .pull-left
          %i.glyphicon.glyphicon-ok-sign
          完了
        .pull-right.completed-count=@completed_count
      =nav_link deleted_tasks_path, class: "clearfix" do
        .pull-left
          %i.glyphicon.glyphicon-trash
          ゴミ箱
        .pull-right.deleted-count=@deleted_count


  .col-sm-10
    =form_for @new_task do |f|
      .input-group
        =f.text_field :title, class: "form-control", placeholder: "タスク"
        %span.input-group-btn
          =f.submit "登録", class: "btn btn-success"

    %ul#tasks.list-unstyled
      -@tasks.each do |task|
        =content_tag_for(:li, task, class: "clearfix") do
          .pull-left
            =link_to task.title, edit_task_path(task)
          .pull-right
            -if task.inbox?
              =link_to "完了", complete_task_path(task), method: "patch", class: "task-complete btn btn-primary"
            -unless task.inbox?
              =link_to "戻す", revert_task_path(task), method: "patch", class: "task-revert btn btn-default"
            -unless task.deleted?
              =link_to "削除", task_path(task), method: "delete", class: "task-delete btn btn-danger"

/app/views/tasks/edit.html.haml

%h2 タスク編集

.well
  =simple_form_for @task do |f|
    =f.input :title
    =f.input :memo
    =f.submit "Save", class: "btn btn-primary btn-block"

その他のテンプレートは必要ないので削除します。

% rm app/views/tasks/_form.html.haml app/views/tasks/index.json.jbuilder app/views/tasks/show.* app/views/tasks/new.html.haml

/app/assets/stylesheets/application.css.sass

a
  cursor: pointer

#sidebar
  height: 100%
  background-color: #fafafa
  border: 1px solid #e5e5e5
  padding-left: 0px
  margin-bottom: 20px
  h3
    color: #AAA
    font-size: 12px
    padding-left: 15px
  ul
    li
      line-height: 30px
      a
        padding-left: 25px
        display: block
      &.current
        border-left: 5px solid #819dc1
        a
          padding-left: 20px

#tasks
  li
    @extend .clearfix
    border-bottom: 1px solid #ededed
    line-height: 50px
    padding-left: 5px

.new-task-well
  padding: 10px
  .form-group
    margin-bottom: 0px

これで完成です!タスクを追加したり完了にしたりと、ちゃんと動作するか確認して下さい。

TODO-Module タスク 完成

次回はいよいよAPIを作っていきます。

www.full-stack-engineer.com