【Ruby on Rails】愛犬管理アプリpart2 ~タスク編~【初心者向け】

# 本記事の内容
本記事は、[こちら](https://qiita.com/tak8_al/items/08274dd9228363642555)の記事の続編です。
その記事で実装した内容をもとに、実装していきますので、まだご覧になっていない方は、ぜひご覧ください。
以下の内容の実装方法だけ知りたいという方は、このままご覧ください。

## 本記事での実装内容
① 実行時間の情報を持ったタスクの作成機能
② 当日のタスクのみ表示する機能
③ 全ての実行日の情報を持ったカレンダー作成機能
④ どのペットに対するタスクか表示する機能
⑤ タスクを実行する人間を表示する機能
⑥ 実行時間に自動メール送信する機能
⑦ タスク完了時にメール送信機能

![スクリーンショット 2022-09-12 午前10.12.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1973643/1d6cbdbe-0a08-e64f-cc92-80ca9bea7941.png)

では実装していきましょう。

# 実行時間の情報を持ったタスクの作成機能
タスクの実行時間がindexページに表示されるタスクアプリケーションを作成します。
手順は2つです。!

* CRUD機能を持ったアプリケーションの作成
* 時間を日本時間に変更

順に実装していきましょう。

## CRUD機能を持ったアプリケーションの作成

Scaffoldというジェネレータを利用します。
Scaffold機能を使うことで、CRUD機能を持ったアプリケーションを自動で生成できます。
タスクアプリケーションに関して、今回作成するカラムは下記の写真の３つです。
<img width="400" alt="スクリーンショット 2022-09-10 午前10.43.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1973643/b594264d-a2d8-1914-de83-52d865d1e442.png">
外部キーの設定に関しては、後述します。

以下のコマンドを実行してください。
```:terminal
$ rails g scaffold Task title:string content:text start_time:datetime
```
生成が完了したらマイグレーションを実行します。
```:terminal
$ rails db:migrate
```
これでタスクアプリケーションの作成は完了です。
下記の状態になっていれば成功です。
![スクリーンショット 2022-09-07 午後1.33.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1973643/ac2dd38a-5a58-dc04-af47-3a615d83b324.png)


## 時間を日本時間に変更
今のままでは時間がUTC時間になっていますので、日本時間に変更します。
また、今回必要な情報はタスクを実行する時間だけですので、時間のみを表示するようにします。

### 日本時間への変更

```ruby:config/application.rb
module アプリ名
　　class Application < Rails::Application

　　　config.time_zone = 'Asia/Tokyo' #追加部分

　　end
end
```
rails sでサーバーを立ち上げ、確認します。

![スクリーンショット 2022-09-07 午後1.37.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1973643/ae5ea8c3-a33c-00bc-3c67-9f73c1307cb2.png)

これで表示される時間は日本時間になりました。

### 表記を時間情報のみにする
start_timeの部分のコードはデフォルトでは以下のようになっています。
```erb:app/views/tasks/index.html.erb
<td><%= task.start_time %></td>
```

この状態だと全ての情報を取り出してしまいます。
表示するものを選択したい場合は、.strftimeメソッドを使用します。

```erb:app/views/tasks/index.html.erb
<td><%= task.start_time.strftime("%Y-%m-%d %H:%M:%S") %></td>
```
%Yは年
%mは月
%dは日
%Hは時
%Mは分
%Sは秒
をそれぞれ表しています。

例えば、何時何分かの情報だけ取り出したい場合は
```erb:app/views/tasks/index.html.erb
<td><%= task.start_time.strftime("%H:%M") %></td>
```
と記述すれば取り出すことが可能です。
確認してみます。
![スクリーンショット 2022-09-07 午後4.02.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1973643/92a55bf0-2fe2-d061-db95-9e827d98f862.png)

時間と分数だけが表示されていますね。
これで完了です。<br><br>

# 当日のタスクのみ表示する機能
作成したタスクアプリケーションのindexファイルには、当日のみではなく、全ての日付のタスクが表示されています。
当日のタスクのみ表示するよう実装します。
手順は2つです。
* モデルにメソッドを定義
* 定義したメソッドを使用


## モデルにメソッドを定義
モデルに、実行日が当日のもののみ、配列から出力するメソッドを定義します。

```ruby:app/models/task.rb
    #メソッド名を定義する
    def self.today_tasks
        #配列を用意
        today_tasks = []
        #全てのタスクの情報が入っているタスクの配列から、1つずつ取り出す
        Task.all.each do |task|
        #設定したタスクの日付と、当日の日付が等しい場合
          if task.start_time.strftime("%Y-%m-%d") == Date.today.strftime("%Y-%m-%d")
            #タスクを先ほど用意した配列に入れていく
            today_tasks << task
          end
        end
        #出力する値を示す。(=戻り値)
        today_tasks
      end
```

## 定義したメソッドを使用

task.controller.rbへ移動し、indexメソッドに、定義したメソッドを使用します。

```ruby:app/controllers/task_controller.rb(変更前)
  def index
    @tasks = Task.all
  end

```

```ruby:app/controllers/task_controller.rb(変更後)
  def index
    @tasks = Task.today_tasks
  end
```
これで、実行日が当日のもののみ、インデックスページに表示されるようになりました。

# ③実行日の情報を持ったカレンダー作成機能
現在タスクのインデックスファイルでは、当日のタスクのみ見ることができる状態になっています。
しかし、未来に実行するタスクも見ることができるようにしたいです。
そこでカレンダーを作成し、そこに今後のタスクを表示できるようにします。
手順は2つです。
* カレンダーの作成
* カレンダーにタスク情報を反映

順に実装していきましょう。

## カレンダーの作成
カレンダーの作成方法にも手順は2つあります。
* simple calendarのインストール
* インデックスページへの反映

### simple calendarのインストール
Gemで、カレンダーを簡単に導入してくれます。
以下のコマンドをGemfileに記載してください。

```:Gemfile
gem "simple_calendar"
```

ターミナルでbundle installを入力します。
```:terminal
bundle install
```
インストールを終えたら、application.cssファイルにデフォルトのスタイルシートを読み込みます。
```css:app/assets/stylesheets/application.css
*= require simple_calendar
```
これでGemのインストールは完了です。

### インデックスページへの反映
インデックスページに導入したカレンダーを反映させます。
今回は1ヶ月分のカレンダーを表示させたいので、month_calendarメソッドを使用しますが、1ヶ月以外にも週単位で表示させる事もできたりします。

以下のコードをインデックスファイルに記載してください。
```erb:app/views/tasks/index.html.erb
<%= month_calendar do |date| %>
  <%= date %>
<% end %>
```
これで完了です。
めちゃめちゃ簡単ですよね？笑

では確認してみましょう。

![スクリーンショット 2022-09-07 午後2.23.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1973643/1652f46a-54f2-5375-c5e8-9dfdca471e63.png)

カレンダーが表示されていますね。
これでカレンダーの作成は完了です。

## カレンダーにタスク情報を反映
現時点では、1ヶ月単位のカレンダーが表示されているだけです。
ここに、現在と未来のタスクが反映されるように設定します。
* インデックスファイルの変更
* 新しいインスタンス係数の作成

順に実装していきます。

### インデックスファイルの変更

インデックスファイルに以下を追記してください。
```erb:app/views/tasks/index.html.erb
#タスクのインスタンス係数から、dateとタスクを取り出す
<%= month_calendar events: @tasks do |date, tasks| %>
  #日付を表示
  <%= date %>
　　　　#改行
  <div>
    #タスクを一つ一つ取り出す
    <% tasks.each do |task| %>
    #タスクのタイトルのみ表示するようにする。リンクからshowに飛べるようにしておく。
    <%= link_to task.title, task %>
  <% end %>
  </div>
```
これで、カレンダーにタスクの情報が反映されるようになりました。

### 新しいインスタンス係数の作成
現時点では、当日のタスクしか表示されません。
当日のみでなく、未来のタスクも表示されるようにしましょう。

全てのタスクの情報を持ったインスタンス係数をtask_controller.rbに作成します。
```ruby:app/controllers/task_controller.rb(変更後)
  def index
    @tasks = Task.today_tasks
    @calendars = Task.all
  end
```
インデックスファイルのインスタンス係数を、新しく作成したものに変更。
```erb:app/views/tasks/index.html.erb
#タスクのインスタンス係数から、dateとタスクを取り出す
<%= month_calendar events: @calendars do |date, tasks| %>
  #日付を表示
  <%= date %>
　　　　#改行
  <div>
    #タスクを一つ一つ取り出す
    <% tasks.each do |task| %>
    #タスクのタイトルのみ表示するようにする。リンクからshowに飛べるようにしておく。
    <%= link_to task.title, task %>
  <% end %>
  </div>
```

これで、登録した全ての日付のタスクがカレンダーに表示されるようになりました。

# ④ どのペットに対するタスクか表示する機能
ペットとタスクの紐付けを実装していきましょう。
手順は以下の3つです。
* インデックスファイルの変更
* 外部キーの設定
* アソシエーションの定義

## インデックスファイルの変更
インデックスファイルに以下の情報を追記してください。
```erb:app/views/tasks/index.html.erb
  <tbody>
    <% @tasks.each do |task| %>
      <tr>
        <td><%= link_to task.title , task %></td>
        <td><%= task.pet.name%></td>
      </tr>
    <% end %>
  </tbody>
```
今までのままでは、エラーが起きます。
petのメソッドが定義されていないためです。
正常に機能するためには、外部キーの設定、アソシエーションの定義が必要です。

## 外部キーの設定
外部キーの設定は、たった一つのコマンドを実行すれば完了します。
```:terminal
rails g migration AddPetRefToTasks pet:references
```


## アソシエーションの定義
それぞれのモデルに以下を追記してください。
```ruby:app/models/pet.rb
class Pet < ApplicationRecord
  belongs_to :family
  has_many :tasks
end
```
```ruby:app/models/task.rb
class Task < ApplicationRecord
  belongs_to :pet
end
```
以上にアソシエーションの定義は完了です。

# ⑤ タスクを実行する人間を表示する機能
タスク作成時に、タスクを実行できる人間を指定できるようにします。
さらに、インデックスファイルでその情報を一覧できるようにします。

* フォームファイルの変更
* コントローラーの変更
* インデックスファイルの変更


## フォームファイルの変更
以下のコードを追記します。
```erb:app/views/tasks/_form.html.erb
  <div class="field">
      <%= form.label :実行人 %>
      <%= form.collection_select(:user_id,@users,:id,:name)%>
  </div>
```
データの保存先としてuser_idのカラムと、usersのインスタンス係数を作成してあげます。

## コントローラーの変更
やることは2つ！
* usersのインスタンス係数の作成
* ストロングパラメーターの作成

### usersのインスタンス係数の作成
createメソッドに以下を追記します。
```ruby:app/controllers/task_controller.rb
  def create
    @task = Task.new(task_params)
    #自分が所属している団体のユーザーすべてを@usersに入れる。
    @users = current_user.families.first.users　#追記

    respond_to do |format|
      if @task.save
        format.html { redirect_to task_url(@task), notice: "Task was successfully created." }
        format.json { render :show, status: :created, location: @task }
      else
        format.html { render :new, status: :unprocessable_entity }
        format.json { render json: @task.errors, status: :unprocessable_entity }
      end
    end
  end
```

### ストロングパラメーターの作成
ストロングパラメーターを許可し、DBで保存できるようにしてあげます。
```ruby:app/controllers/task_controller.rb
  def create
    def task_params
      params.require(:task).permit(:title, :content, :start_time, :pet_id, :user_id)
    end
```

## インデックスファイルの変更
以下を追記します。
```erb:app/views/tasks/index.html.erb
          <tbody>
            <% @tasks.each do |task| %>
              <tr>
                #追記
                <td><%= task.user.name%></td>
              </tr>
            <% end %>
          </tbody>
```
アソシエーションの定義を行ったことで、.userでユーザーテーブルの情報をとってくることができるようになりました。

これで、タスクを実行する人間を表示する機能の実装は完了です。


# ⑥ 実行時間に自動メール送信する機能
タスクの実行時間になったら自動的にタスクを送信する機能を実装したいです。
しかしその前にまず、メールの送信機能を実装します。
# メール送信機能の実装
手順は3つです。
* メールの送信設定
* メーラーの作成
* 全体メーラーの更新

## メールの送信設定
gmailからの送信機能を実装します。
実装手順は2つ
* Gmail側の設定
* Rails側の設定

順に実装していきましょう。

### Gmail側の設定
詳しく解説してある記事を見つけましたので、[こちら](https://senrenseyo.com/20210830/rails6-devisegmail/)をご覧ください。

### Rails側の設定
development.rbに以下のコードを記載してください。

```ruby:config/environment/development.rb
  config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
  config.action_mailer.raise_delivery_errors = true
  config.action_mailer.delivery_method = :smtp
  config.action_mailer.smtp_settings = {
    address:              'smtp.gmail.com',
    port:                  587,
    domain:               'gmail.com',
    user_name:            '設定したgmailアドレス', 
    password:             '2段階認証したアカウントで発行したアプリパスワード',
    authentication:       'plain',
    enable_starttls_auto:  true
  }
end
```
これで設定は完了です。

## メーラーの作成
メーラーとは、メール送信におけるコントローラー的役割を果たすものです。
以下のコマンドで作成します。
```:terminal
$ rails g mailer TodoMailer
```
Mailer前は好きな名前で設定してください。

このコマンドを実行すると、app/mailer以下に次の2つのメーラーが作成されます。
* application.rb
* Todo_mailer.rb

前者が全体のメーラー、後者が個別のメーラーです。
次の項目では、全体のメーラーの設定を行います。

## 全体メーラーの更新
全体メーラーとは、送信元の設定をするファイルです。
開くと以下のような記述になっているはずです。
```ruby:app/mailers/application.rb(変更前)
class ApplicationMailer < ActionMailer::Base
  default from: "from@example.com"
  layout 'mailer'
end
```
以下のように変更してください。
```ruby:app/mailers/application.rb(変更前)
class ApplicationMailer < ActionMailer::Base
  default from: '送信元に設定したいメールアドレスを記載'
  layout 'mailer'
end
```
これで、メールの送信機能の実装は完了です。

# 実行時間に自動メール送信する機能
実装手順は以下の3つです。
* wheneverというgemのインストール
* 読み込みメソッドの設定
* メソッドの実行処理

## Wheneverというgemのインストール
インストールすることで、自動的に一分おきに読み込み、設定したメソッドを実行してくれます。
```:Gemfile
gem 'whenever', require: false
```
追記したら、bundle install を実行します。

## 読み込みメソッドの設定
* schedule.rbの生成
* 読み込みタイミングの設定

### schedule.rbの生成
以下のコマンドを実行することで生成されます。
```:terminal
$ bundle exec wheneverize .
```

### 読み込みのタイミングの設定
生成したschedule.rbに以下の記述をし、読み込みのタイミングの設定をしてあげます。
```ruby:config/schedule.rb
#railsアプリフォルダ内の./log/cron.logにログが出力されるようにする記述
set :output, "#{Rails.root}/log/cron.log"

#railsアプリ内の実行環境を変数に代入している
require File.expand_path(File.dirname(__FILE__) + '/environment')
set :path_env, ENV['PATH']
rails_env = ENV['RAILS_ENV'] || :development
set :environment, rails_env

#railsアプリ上の実行環境と同条件になるよう規定している
#:outputで事前にsetしたパスにログの出力先を設定
job_type :runner, "cd :path && PATH=':path_env' bin/rails runner -e :environment ':task' :output"

#読み込むタイミングを1分に設定
every 1.minute do 
    runner "Task.remind"
end
```
これで、設定したタイミングにメソッドを実行してくれます。

## メソッドの実行処理
1分おきに読み込み、設定した時間に処理を実行してくれるメソッドは作成しましたが、タイミングの指定と、送信のメソッドがまだできていません。
そこで、以下の機能を実装します。
* タスクの実行時間になったらメールを送信してくれるメソッドの定義
* 送信先の定義
* メール本文の定義

順に実装していきましょう。
## タスクの実行時間になったらメールを送信してくれるメソッドの定義
task.rbに以下のコードを記載します。
```ruby:app/models/task.rb
  def self.remind
    #mailer(boolean)がfalseのものを取得
    tasks = Task.where(mailer:false)
    tasks.each do |task|
      #タスクの実行時間を超えたらメールを送る処理
      if task.start_time < Time.now
        TodoMailer.send_at_the_time(task).deliver
        #送信したらbooleanをtrueに変更
        task.mailer = true
        task.save
      end
    end
  end
```
boolean(mailer)のカラムの追加が未実装なため、下記のコマンドを実装し、追加します。
```:terminal
$ rails generate migration AddColumnTasks
```
作成されたマイグレーションファイルに以下を記載してください。
```ruby:20220909090742_add_column_tasks.rb
class AddColumnTasks < ActiveRecord::Migration[6.1]
  def change
    add_column :tasks, :mailer, :boolean, default: false, null: false
  end
end
```
rails db:migrateを実行しましょう。

これで、タスクの実行時間になったらメールが送信されるメソッドの定義は完了です。

## 送信先の定義
送信先の定義はtodo_mailer.rbで行います。
以下のコードを記載してください。
```ruby:app/mailers/todo_mailer.rb
class TodoMailer < ApplicationMailer
    # task.rbで定義した引数を受け取る
    def send_at_the_time(task)
        @task = task
        @user = task.user
        mail to: @user.email, subject: 'タスクの時間です。'
    end
end
```
タスクを実行させたいユーザー宛にメールが送信されます。

## メール本文の定義
以下のコードをsend_at_the_time.html.erbに記載してください。
```erb:app/views/todo_mailer/send_at_the_time.html.erb
<!doctype html>
<html lang="ja">
<head>
  <meta content="text/html; charset=UTF-8" />
</head>
<body>
  <h2><%= @user.name %> 様</h2>
  <hr />
  <p>
    こんにちは！ <%= @user.name %>さん！
    <%= @task.title %>を実行してください。<br>
    <%= @task.content %>
    </p>
</body>
</html>
```
これで、タスク実行時間に、実行させたいユーザーにメールが自動的に送信されます。

# ⑦ タスク完了時にメール送信機能
手順は以下の4つです。
* メーラーの作成
* メソッドの定義
* 送り先の定義
* メール本文の定義

順に実装していきましょう！

## メーラーの作成
別のメーラーメソッドを実行したい場合、別のメーラーを生成します。
以下のコマンドを実行してください。
```:terminal
$ rails g mailer DoneMailer

      create  app/mailers/done_mailer.rb
      invoke  erb
      create    app/views/done_mailer
      invoke  test_unit
      create    test/mailers/done_mailer_test.rb
      create    test/mailers/previews/done_mailer_preview.rb
```
## メソッドの定義
今回はタスクの完了ボタンを押したら、メールが送信される機能を実装します。
手順は4つです。
* ルーティングの定義
* コントローラーの追記
* ボタンの作成
* 完了タスク一覧機能の実装

順に実装していきましょう。

### ルーティングの定義
```ruby:config/routes.rb
#　追記
post '/tasks/:id/done' => 'tasks#done',   as: 'done'
```

### コントローラーの追記
Doneボタンを押したら、メールが送信されるメソッドを追記します。
```ruby:app/controllers/tasks_controller.rb
 def done
    #　タスクがアップデートされたらbooleanがtrueになる。
    if @task.update(is_done: true )
      #メール送信メソッド
      DoneMailer.send_when_done(@task).deliver
      redirect_to tasks_path
    else 
      render 'index'
    end
  end
```
これで、ボタンを押したら完了メールが送信されるようになりました。
is_doneのカラムの追加は未実装なので、先ほどの要領で、実装してください。

### ボタンの作成
まだ肝心のボタンが実装できていないので、実装します。
インデックスファイルに記載します。
```erb:app/views/tasks/index.html.erb
 def done
    <tr>
        <td><%= link_to task.title , task %></td>
        <td><%= task.start_time.strftime("%H:%M") %></td>
        <td><%= task.user.name%></td>
        <td><%= task.pet.name%></td>
         #追記
        <td><%= link_to 'Done', done_path(task.id), method: :post %></td>
    </tr>
```

### 完了タスク一覧機能の実装(変更前)
完了したタスクと、未完了のタスクを一覧で見れるようにします。
インデックスファイルを以下のように変更してください。
```erb:app/views/tasks/index.html.erb
     <h2>タスク一覧</h2>
     <%= link_to 'New Task', new_task_path %>
     <hr />
     <br>
       <table>
         <thead>
            <tr>
              <th>Title</th>
              <th>Time</th>
              <th>User</th>
              <th>Pet</th>
              <th colspan="3"></th>
            </tr>
         </thead>
        　　<tbody>
            #変更ポイント
            <% @tasks.each do |task| %>
              <tr>
                <td><%= link_to task.title , task %></td>
                <td><%= task.start_time.strftime("%H:%M") %></td>
                <td><%= task.user.name%></td>
                <td><%= task.pet.name%></td>
                <td><%= link_to 'Done', done_path(task.id), method: :post %></td>
              </tr>
            <% end %>
          #変更ポイント
          </tbody>
      </table>
      <h2>完了</h2>
      <br>
      <hr />
      <table>
        <thead>
          <br>
          <tr>
            <th>Title</th>
            <th>User</th>
            <th>Pet</th>
          </tr>
        </thead>
        <tbody>
          #変更ポイント
          <% @tasks.each do |task| %>
            <tr>
              <td><%= link_to task.title , task %></td>
              <td><%= task.user.name%></td>
              <td><%= task.pet.name %></td>
            </tr>
          <% end %>
        #変更ポイント
        </tbody>
      </table>
```
```erb:app/views/tasks/index.html.erb
     <h2>タスク一覧</h2>
     <%= link_to 'New Task', new_task_path %>
     <hr />
     <br>
       <table>
         <thead>
            <tr>
              <th>Title</th>
              <th>Time</th>
              <th>User</th>
              <th>Pet</th>
              <th colspan="3"></th>
            </tr>
         </thead>
        　　<tbody>
            #ここから追記
            <% @tasks.filter{|task| !task.is_done? }.each do |task| %>
              <tr>
                <td><%= link_to task.title , task %></td>
                <td><%= task.start_time.strftime("%H:%M") %></td>
                <td><%= task.user.name%></td>
                <td><%= task.pet.name%></td>
                <td><%= link_to 'Done', done_path(task.id), method: :post %></td>
              </tr>
            <% end %>
          #ここまで追記
          </tbody>
      </table>
      <h2>完了</h2>
      <br>
      <hr />
      <table>
        <thead>
          <br>
          <tr>
            <th>Title</th>
            <th>User</th>
            <th>Pet</th>
          </tr>
        </thead>
        <tbody>
          #ここから追記
          <% @tasks.filter{|task| task.is_done? }.each do |task| %>
            <tr>
              <td><%= link_to task.title , task %></td>
              <td><%= task.user.name%></td>
              <td><%= task.pet.name %></td>
            </tr>
          <% end %>
        #ここまで追記
        </tbody>
      </table>
```
ちなみに、コントローラーでインスタンス係数を作成するやり方でも大丈夫です！
これで、タスクが完了したらメールを送信するメソッドの定義はできました。

## 送り先の定義
done_mailer.rbに以下を追記してください。
```app/mailers/done_mailer.rb
class DoneMailer < ApplicationMailer
    #コントローラーから引数を受け取る。
    def send_when_done(task)
        @task = task
        mail to: task.user.email, subject: 'タスクの時間です。'
    end
end
```
## メール本文の定義
最後にメール本文の定義をしましょう！
```app/views/mailers/send_when_done.html.erb
<!doctype html>
<html lang="ja">
<head>
  <meta content="text/html; charset=UTF-8" />
</head>
<body>
  <h2><%= @task.user.name %> 様</h2>
  <hr />
  <p>
    こんにちは！ <%= @task.user.name %>さん！<br>
    <%= @task.title %>が無事実行されました！
    </p>
</body>
</html>
```
これで、タスクの完了ボタンを押すと、メールが送信される処理の実装完了です。

# 終わりに
これでタスク機能に関する実装は完了です！
お疲れ様でした。
part2ではログイン機能実装編(devise)について実装します。
興味がある方はご覧ください。