jekylog

All doggs wanna be a Geek

Ruby On Railsでタスク管理アプリを作ってみる

Vagrantで仮想環境の準備も整ったのでドットインストールを見ながら早速Ruby On Railsでシンプルなタスク管理アプリを作ってみる。

1. まずはVagrantで仮想環境にログイン

Vagrantでの仮想環境の準備が出来ている前提でまずは仮想環境にログイン。

$ vagrant ssh

2. アプリケーションを作成

アプリケーションに必要なコントローラ、モデル、ビューをまとめて生成。 なお、必要なbundleを既にインストールしている場合は--skip-bundleオプションを使用しても良い。

$ rails new taskapp

3. Gemfileを編集

アプリケーションディレクトリに移動して、therubyracerを有効化するためにGemfileを編集。

$ cd tasksapp
$ vi Gemfile

下記がコメントアウトされているのでアンコメントする。

gem 'therubyracer', platforms: :ruby 

編集後、改めてbundle installしておく。

4. サーバーを立ち上げてブラウザで確認してみる

$ rails s

3000番ポートなので192.168.33.10:3000を叩いて確認。

5. モデルを作る

モデルの名前(ここではProject)は大文字で始まり、単数形でなければならない。 なお、属性の初期値はstringなのでなくてもよい。

$ rails g model Project title:string

6. 作成したモデルをデータベースに反映する

$ rake db:migrate

7. テーブルが作成されているかデータベースを確認する

まずはデータベースにアクセス。

$ rails db

SQL文で確認。

# テーブルを確認
sqlite> .schema

# データの取得
sqlite> select * from projects

# 終了
sqlite> .exit

ダミー用にrails consoleでモデルを作っておく。

$ rails console
# モデルを作成
p = Project.new(title: "p1")

# モデルを保存
p.save

# モデルを確認
p

# ちなみに上記を一気に行う場合
Project.create(title: "p2")

# 全てのモデルを確認
Project.all

8. コントローラーを作る

コントローラー名は対象となるモデルの複数形にする。

$ rails g controller Projects

9. ルーティング用のファイルを編集してルーティングを設定

config/routes.rbを編集。

$ cd config
$ vi routes.rb

projectsに関するルーティングを設定。

Taskapp::Application.routes.draw do
    resources :projects
end

念のためルーティングを確認しておく。

$ rake routes

10. プロジェクトの一覧画面を作る

まずはコントローラー。 app/controllers/projects_controller.rbを下記のように編集。

class ProjectsController < ApplicationController
    def index
        @projects = Project.all
    end
end

次にビューを作る。 app/views/projectsにindex.html.erbを作成して下記のように編集。

<h1>Projects</h1>
<ul>
    <% @projects.each do |project| %>
    <li><%= project.title %></li>
    <% end %>
</ul>

11. ブラウザで一覧画面を確認する

$ rails s

192.168.33.10:3000/projects/を叩いて確認。

12. rootの設定する

http://192.168.33.10:3000/projects/の内容をhttp://192.168.33.10:3000/で表示したいので、config/routes.rbでルーティングの設定をする。

Taskapp::Application.routes.draw do
    resources :projects
    root 'projects#index'
end

192.168.33.10:3000を叩いて確認。

13. 共通テンプレートを編集する

app/views/layouts/application.html.erbが共通テンプレートなので編集してみる。

<!DOCTYPE html>
<html>
    <head>
        <title>Taskapp</title>
        <%= stylesheet_link_tag    "application", media: "all", "data-turbolinks-track" => true %>
        <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
        <%= csrf_meta_tags %>
    </head>
    <body>

    <!-- srcがapp/assets/images/logo.pngのimgタグを展開 -->
    <%= image_tag "logo.png" %>

    <%= yield %>

    <!-- 指定したパスへのリンクを展開 -->
    <p><%= link_to "Home[/]", "/" %></p>

    <!-- ちなみに変数を使ってもリンクを展開出来る(パスはrails routesで確認出来る) -->
    <p><%= link_to "Home[/projects/]", projects_path %></p>
    </body>

</html>

14. プロジェクトの詳細画面を作る

一覧画面に表示されるリストをクリックすると詳細画面に移動させたいので、まずはapp/views/projects/index.html.erbを編集。

<h1>Projects</h1>
<ul>
    <% @projects.each do |project| %>
    <li><%= link_to project.title, project_path(project.id) %></li>
    <% end %>
</ul>

app/controllers/projects_controller.rbを下記のように編集。

class ProjectsController < ApplicationController
    def index
        @projects = Project.all
    end
    def show
        @project = Project.find(params[:id])
    end
end

次にビューを作る。 app/views/projectsにshow.html.erbを作成して下記のように編集。

<h1><%= @project.title %></h1>

192.168.33.10:3000を叩いて確認。

15. プロジェクトの新規作成画面を作る

新規作成画面に移動するためのリンクを追加したいのでapp/views/projects/index.html.erbを下記のように編集。

<h1>Projects</h1>
<ul>
    <% @projects.each do |project| %>
    <li><%= link_to project.title, project_path(project.id) %></li>
    <% end %>
</ul>
<p><%= link_to "Add New", new_project_path %></p>

app/controllers/projects_controller.rbを下記のように編集。

class ProjectsController < ApplicationController
    def index
        @projects = Project.all
    end
    def show
        @project = Project.find(params[:id])
    end
    def new
        @project = Project.new
    end
end

次にビューを作る。 app/views/projectsにnew.html.erbを作成して下記のように編集。

<h1><%= @project.title %></h1>

192.168.33.10:3000を叩いて確認。

16. データを保存してみる

app/controllers/projects_controller.rbを下記のように編集。

class ProjectsController < ApplicationController
    def index
        @projects = Project.all
    end
    def show
        @project = Project.find(params[:id])
    end
    def new
        @project = Project.new
    end
    def create
        @project = Project.new(project_params)
        @project.save
        redirect_to projects_path
    end
    private
        def project_params
            params[:project].permit(:title)
        end
end

ちなみに

project_paramsをフィルタリングする手法はStrong Parametersと呼ばれ、 Mass Assignmentという攻撃を防御することができます。

とある。

192.168.33.10:3000を叩いて確認。

17. バリデーションを設定する

現状だと空のデータも登録できてしまうので、バリデーションを用いて登録出来ない仕組みを作る。

バリデーションはモデルに記述していくので、app/models/project.rbを下記のように編集。

class Project < ActiveRecord::Base
    validates :title, presence: true
end

このままだとエラーがあってもそのまま移動してしまうので移動しないようにapp/controllers/projects_controller.rbを編集。

class ProjectsController < ApplicationController
    def index
        @projects = Project.all
    end
    def show
        @project = Project.find(params[:id])
    end
    def new
        @project = Project.new
    end
    def create
        @project = Project.new(project_params)
        if @project.save
            redirect_to projects_path
        else
            render 'new'
        end
    end
    private
        def project_params
            params[:project].permit(:title)
        end
end

エラーメッセージも出すようにapp/views/projects/new.html.erbを編集。

<h1>Add New</h1>

<%= form_for @project do |f| %>
    <p><%= f.label :title %>:<%= f.text_field :title %></p>
    <% if @project.errors.any? %>
        <p class="error"><%= @project.errors.messages[:title][0] %><p>
    <% end %>
    <p><%= f.submit %></p>
<% end %>

メッセージを変えたい場合はモデル(app/models/project.rb)を下記のように編集。

class Project < ActiveRecord::Base
    validates :title, presence: { message: "入力してください" }
end

なお、空文字チェック以外にも長さをバリデートしたり複数登録出来る。

class Project < ActiveRecord::Base
    validates :title,
    presence: { message: "入力してください" }, # 空の時
    length: { minimum: 3, message: "短すぎます" } # 長さが2文字以下の時
end

ってかデフォルトの機能でfield_with_errorsなるエラー用のdivが出力されて邪魔なので調べて見たらconfig/application.rb内に下記を追記する事で出力しないように出来た。

# Applicationクラス内に記述する事
config.action_view.field_error_proc = Proc.new { |html_tag, instance| %Q(#{html_tag}).html_safe }

18. 編集フォームを作る

まずはトップページに編集ページヘのリンク先を追加するためにapp/views/projectsにindex.html.erbを下記のように編集。

<h1>Projects</h1>
<ul>
    <% @projects.each do |project| %>
    <li>
        <%= link_to project.title, project_path(project.id) %>
        <%= link_to "[edit]", edit_project_path(project.id) %>
    </li>
    <% end %>
</ul>
<p><%= link_to "Add New", new_project_path %></p>

アクションを設定するためにapp/controllers/projects_controller.rbを下記のように編集。

class ProjectsController < ApplicationController
    def index
        @projects = Project.all
    end
    def show
        @project = Project.find(params[:id])
    end
    def new
        @project = Project.new
    end
    def create
        @project = Project.new(project_params)
        if @project.save
            redirect_to projects_path
        else
            render 'new'
        end
    end
    def edit
        @project = Project.find(params[:id])
    end
    private
        def project_params
            params[:project].permit(:title)
        end
end

app/views/projectsにedit.html.erbを作成して下記のように編集。

<h1>Edit</h1>

<%= form_for @project do |f| %>
    <p><%= f.label :title %>:<%= f.text_field :title %></p>
    <% if @project.errors.any? %>
        <p><%= @project.errors.messages[:title][0] %></p>
    <% end %>
    <p><%= f.submit %></p>
<% end %>

192.168.33.10:3000を叩いて確認。

18. データを更新してみる

app/controllers/projects_controller.rbを下記のように編集。

class ProjectsController < ApplicationController
    def index
        @projects = Project.all
    end
    def show
        @project = Project.find(params[:id])
    end
    def new
        @project = Project.new
    end
    def create
        @project = Project.new(project_params)
        if @project.save
            redirect_to projects_path
        else
            render 'new'
        end
    end
    def edit
        @project = Project.find(params[:id])
    end
    def update
        @project = Project.find(params[:id])
        if @project.update(project_params)
            redirect_to projects_path
        else
            render 'edit'
        end
    end
    private
        def project_params
            params[:project].permit(:title)
        end
end

192.168.33.10:3000を叩いて確認。

19. 部品を共通化する

new.htmlとedit.htmlは同じformモジュールを使用しているので、共通化してみる。

まずはapp/views/projects/に_form.html.erbを作成し、form部分の記述のみ抜き出す。

<%= form_for @project do |f| %>
    <p><%= f.label :title %>:<%= f.text_field :title %></p>
    <% if @project.errors.any? %>
        <p><%= @project.errors.messages[:title][0] %></p>
    <% end %>
    <p><%= f.submit %></p>
<% end %>

edit.htmlを下記のように編集。

<h1>Edit</h1>

<%= render 'form' %>

new.htmlも同様に編集。

<h1>Add New</h1>

<%= render 'form' %>

まさにDRY。

20. データを削除する

一覧画面に表示されるリストをクリックすると削除画面に移動させたいので、まずはapp/views/projects/index.html.erbを編集。

<h1>Projects</h1>
<ul>
    <% @projects.each do |project| %>
    <li>
        <%= link_to project.title, project_path(project.id) %>
        <%= link_to "[edit]", edit_project_path(project.id) %>
        <%= link_to "[delete]", project_path(project.id), method: :delete, data: { confirm: "are you sure?" } %>
    </li>
    <% end %>
</ul>
<p><%= link_to "Add New", new_project_path %></p>

アクションを設定。

class ProjectsController < ApplicationController
    def index
        @projects = Project.all
    end
    def show
        @project = Project.find(params[:id])
    end
    def new
        @project = Project.new
    end
    def create
        @project = Project.new(project_params)
        if @project.save
            redirect_to projects_path
        else
            render 'new'
        end
    end
    def edit
        @project = Project.find(params[:id])
    end
    def update
        @project = Project.find(params[:id])
        if @project.update(project_params)
            redirect_to projects_path
        else
            render 'edit'
        end
    end
    def destroy
        @project = Project.find(params[:id])
        @project.destroy
        redirect_to projects_path
    end
    private
        def project_params
            params[:project].permit(:title)
        end
end

192.168.33.10:3000を叩いて確認。

21. before_actionを使ってcontrollerのコードをDRYに

共通する処理はbefore_actionを使うとコードをDRYに出来るのでapp/controllers/projects_controller.rbを下記のように編集。

class ProjectsController < ApplicationController
    before_action :set_project, only: [:show, :edit, :update, :destroy]
    def index
        @projects = Project.all
    end
    def show
    end
    def new
        @project = Project.new
    end
    def create
        @project = Project.new(project_params)
        if @project.save
            redirect_to projects_path
        else
            render 'new'
        end
    end
    def edit
    end
    def update
        if @project.update(project_params)
            redirect_to projects_path
        else
            render 'edit'
        end
    end
    def destroy
        @project.destroy
        redirect_to projects_path
    end
    private
        def project_params
            params[:project].permit(:title)
        end
        def set_project
            @project = Project.find(params[:id])
        end
end

192.168.33.10:3000を叩いて確認。

22. Tasksの設定をする

Projectsに紐付いた形でTasksの設定をする。

まずはモデルを作成。

$ rails g model Task title done:boolean project:references

モデルの初期値を設定するためにdb/migrate/以下の**************createtasks.rbに初期値を追加。

class CreateTasks < ActiveRecord::Migration
  def change
    create_table :tasks do |t|
      t.string :title
      t.boolean :done, default: false
      t.references :project, index: true

      t.timestamps
    end
  end
end

作成したモデルをデータベースに反映する。

$ rake db:migrate

コントローラーを作成。

$ rails g controller Tasks

モデルの関連付けを反映するためにapp/models/projects.rbを下記のように編集。

class Project < ActiveRecord::Base
    has_many :tasks
    validates :title,
    presence: { message: "入力してください" },
    length: { minimum: 3, message: "短すぎます" }
end

ルーティングを設定するためにconfig/routes.rbを下記のように編集。

Taskapp::Application.routes.draw do
    resources :projects do
        resources :tasks, only: [:create, :destroy]
    end
    root 'projects#index'
end

23. Tasksの新規作成フォームを作る

show.htmlにタスクの一覧を表示したいのでapp/views/projects/show.html.erbを下記のように編集。

<h1><%= @project.title %></h1>

<ul>
    <% @project.tasks.each do |task| %>
        <li><%= task.title %></li>
    <% end %>
    <li>
        <%= form_for [@project, @project.tasks.build] do |f| %>
            <%= f.text_field :title %>
            <%= f.submit %>
        <% end %>
    </li>
</ul>

24. Tasksを保存する

app/controllers/tasks_controller.rbを下記のように編集。

class TasksController < ApplicationController
    def create
        @project = Project.find(params[:project_id])
        @task = @project.tasks.create(task_params)
        redirect_to project_path(@project.id)
    end
    private
        def task_params
            params[:task].permit(:title)
        end
end

バリデーションも適用したいのでapp/models/task.rbを下記のように編集。

class Task < ActiveRecord::Base
    belongs_to :project
    validates :title, presence: true
end

192.168.33.10:3000を叩いて確認。

25. Tasksを削除する

一覧画面に表示されるリストをクリックすると削除画面に移動させたいので、まずはapp/views/projects/show.html.erbを編集。

<h1><%= @project.title %></h1>

<ul>
        <% @project.tasks.each do |task| %>
        <li>
                <%= task.title %>
                <%= link_to "[delete]", project_task_path(task.project_id, task.id), method: :delete, data: { confirm: "are you sure?" } %>
        </li>
        <% end %>
        <li>
                <%= form_for [@project, @project.tasks.build] do |f| %>
                        <%= f.text_field :title %>
                        <%= f.submit %>
                <% end %>
        </li>
</ul>

アクションを設定するためにapp/controllers/tasks_controller.rbを下記のように編集。

class TasksController < ApplicationController
    def create
        @project = Project.find(params[:project_id])
        @task = @project.tasks.create(task_params)
        redirect_to project_path(@project.id)
    end
    def destroy
        @task = Task.find(params[:id])
        @task.destroy
        redirect_to project_path(params[:project_id])
    end
    private
        def task_params
            params[:task].permit(:title)
        end
end

192.168.33.10:3000を叩いて確認。

26. check_box_tagを使ってチェックボックスを表示する

進捗管理用のチェックボックスタグを付けたいのでapp/views/projects/show.html.erbを編集。

<h1><%= @project.title %></h1>

<ul>
        <% @project.tasks.each do |task| %>
        <li>
                <%= check_box_tag '', '', task.done, {'data-id' => task.id, 'data-project_id' => task.project_id} %>
                <%= task.title %>
                <%= link_to "[delete]", project_task_path(task.project_id, task.id), method: :delete, data: { confirm: "are you sure?" } %>
        </li>
        <% end %>
        <li>
                <%= form_for [@project, @project.tasks.build] do |f| %>
                        <%= f.text_field :title %>
                        <%= f.submit %>
                <% end %>
        </li>
</ul>

Ajaxで該当するタスクの切り替えを行いたいので引き続きapp/views/projects/show.html.erbを編集。

<h1><%= @project.title %></h1>

<ul>
        <% @project.tasks.each do |task| %>
        <li>
                <%= check_box_tag '', '', task.done, {'data-id' => task.id, 'data-project_id' => task.project_id} %>
                <%= task.title %>
                <%= link_to "[delete]", project_task_path(task.project_id, task.id), method: :delete, data: { confirm: "are you sure?" } %>
        </li>
        <% end %>
        <li>
                <%= form_for [@project, @project.tasks.build] do |f| %>
                        <%= f.text_field :title %>
                        <%= f.submit %>
                <% end %>
        </li>
</ul>

<script>
        $(function(){
                $('input[type=checkbox]').click(function(){
                        var idProject = $(this).data('project_id'),
                            idTask = $(this).data('id');
                        $.post('/projects/' + idProject + '/tasks/' + idTask + '/toggle');
                });
        });
</script>

ルーティングを設定するためにconfig/routes.rbを下記のように編集。

Taskapp::Application.routes.draw do
    resources :projects do
        resources :tasks, only: [:create, :destroy]
    end
    post '/projects/:project_id/tasks/:id/toggle' => 'tasks#toggle'
    root 'projects#index'
end

アクションを設定するためにapp/controllers/tasks_controller.rbを編集。 なお、画面切り替えはしないのでrender nothing: trueも入れておく。

class TasksController < ApplicationController
        def create
                @project = Project.find(params[:project_id])
                @task = @project.tasks.create(task_params)
                redirect_to project_path(@project.id)
        end
        def destroy
                @task = Task.find(params[:id])
                @task.destroy
                redirect_to project_path(params[:project_id])
        end
    def toggle
        render nothing: true
        @task = Task.find(params[:id])
        @task.done = !@task.done
        @task.save
    end
        private
                def task_params
                        params[:task].permit(:title)
                end
end

192.168.33.10:3000を叩いて確認。

27. Tasksの数を表示する

プロジェクト一覧にタスクの数を表示させたいので、app/views/projects/index.html.erbを編集。

<h1>Projects</h1>
<ul>
        <% @projects.each do |project| %>
        <li>
                <%= link_to project.title, project_path(project.id) %><%= project.tasks.count %>
                <%= link_to "[edit]", edit_project_path(project.id) %>
                <%= link_to "[delete]", project_path(project.id), method: :delete, data: { confirm: "are you sure?" } %>
        </li>
        <% end %>
</ul>
<p><%= link_to "Add New", new_project_path %></p>

完了していない残りのタスクを表示させるためにapp/models/task.rbを編集。

class Task < ActiveRecord::Base
        belongs_to :project
        validates :title, presence: true
        scope :unfinished, -> { where(done: false) }
end

再度app/views/projects/index.html.erbを編集。

<h1>Projects</h1>
<ul>
        <% @projects.each do |project| %>
        <li>
                <%= link_to project.title, project_path(project.id) %><%= project.tasks.unfinished.count %>/<%= project.tasks.count %>
                <%= link_to "[edit]", edit_project_path(project.id) %>
                <%= link_to "[delete]", project_path(project.id), method: :delete, data: { confirm: "are you sure?" } %>
        </li>
        <% end %>
</ul>
<p><%= link_to "Add New", new_project_path %></p>

192.168.33.10:3000を叩いて確認。

Fork me on GitHub