カレンダーの上で同一の時間帯のタスクの登録／更新をできないようにする


#何がしたいか
このような状況を無くしたい
![スクリーンショット 2019-09-29 16.24.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468786/7c8ded26-5fe0-8f26-4cc3-d5fcef59c768.png)


目指す姿😇
![スクリーンショット 2019-09-29 16.27.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468786/84bd549d-47c7-8afc-0d97-af6f9e282a8b.png)

## 環境
Rails v5.2.3
ruby v2.6.3p62 

gem
fullcalendar-rails v3.9

### テーブル構造概要
![スクリーンショット 2019-09-29 16.36.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468786/bf80948e-0275-dd31-8f7e-7982341e1fdc.png)


## どうやって実現しよう...
modelにカスタムバリデーションを追加してエラーにしたい。
（controllerで行うと責務が増えすぎる気もした）
バリデーションでDBに保存済みの時間と画面から入力された時間を比較し、件数が0件か1件以上あるかで実装を頑張る。

条件としては
DBに保存済みの開始時間<入力された開始時間or入力された終了時間<DBに保存されている終了時間
にエラーとするべきと考えた。
⇨SQLでの抽出条件：
"DBに保存済みの開始時間 < 画面から入力された**開始**時間 
AND 画面から入力された**開始**時間 < DBに保存されている終了時間
OR DBに保存済みの開始時間 < 画面から入力された**終了**時間 
AND 画面から入力された**終了**時間 < DBに保存されている終了時間"


## 実装したソース
``` controller.rb
def create #新規登録
  act_history = current_user.activity_historys.build(
    activity_name:      @json_hash[:activity_name],
    from_time:          @json_hash[:from_time],
    to_time:            @json_hash[:to_time],
    remarks:            @json_hash[:remarks],
    action:             "create",      #アクセサにセット
    current_user:       current_user   #アクセサにセット
  )

...（略）
end

def update #更新
  ...（略）
  respond_to do |format|
    if act_history.update(
        activity_name:      @json_hash[:activity_name],
        from_time:          @json_hash[:from_time],
        to_time:            @json_hash[:to_time],
        remarks:            @json_hash[:remarks],
        action:             "update",       #アクセサにセット
        current_user:       current_user    #アクセサにセット
      )
  ...（略）
end
```

```model.rb
  # アクセサ
  attr_accessor :action, :current_user
  # バリデーション
  validate :check_time

 ...（略）

 private

  def check_time
    ...（略）
    same_time_zone_exists
  end
  ...（略）

 # 画面から受け取った開始時間、終了時間をもとにすでにDBに存在しているか検証する
  def same_time_zone_exists
    exists_act_historys = current_user.activity_historys.where(
      #SQL抽出条件
      "(from_time < ? AND ? < to_time OR from_time < ? AND ? < to_time)",
      from_time,
      from_time,
      to_time,
      to_time
    )

    
    if action == "create" && exists_act_historys.count > 0
      errors.add(:from_time, ": 同一時間帯の登録はできません")
      errors.add(:to_time, ": 同一時間帯の登録はできません")
    elsif action == "update" && exists_act_historys.count > 0
      exists_act_historys.pluck(:id).each do |exists_ids|
        if exists_ids != id
          errors.add(:from_time, ": 同一時間帯の登録はできません")
          errors.add(:to_time, ": 同一時間帯の登録はできません")
        end
      end
    end
  end
```

##実装後

![スクリーンショット 2019-09-29 18.04.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468786/25bed1b4-961e-36f5-33c2-2e85771db644.png)
⬇️エラーになるかテストその１
![スクリーンショット 2019-09-29 18.05.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468786/d628c32e-f3fd-e511-616c-3c6d7c5ccedc.png)
⬇️正しくエラーになる！
![スクリーンショット 2019-09-29 18.06.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468786/aed5c34d-0c59-d4b5-6fda-f6017a0154ea.png)


![スクリーンショット 2019-09-29 18.04.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468786/25bed1b4-961e-36f5-33c2-2e85771db644.png)
⬇️エラーになるかテストその２
![スクリーンショット 2019-09-29 18.07.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468786/79666b4c-52de-3830-5a92-5410b7e50ef1.png)
⬇️エラーにならない...
![スクリーンショット 2019-09-29 18.08.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468786/27baf3af-2b00-b3da-72c0-ebd70b88182d.png)

抽出条件が足りない模様。
入力された開始時間<DBに保存済みの開始時間〜終了時間<入力された終了時間
⇨SQLでの抽出条件：
"画面から入力された**開始**時間 < DBに保存済みの開始時間 
AND 画面から入力された**終了**時間 < DBに保存されている終了時間"



##完成版ソース
```model.rb
# 画面から受け取った開始時間、終了時間をもとにすでにDBに存在しているか検証する
  def same_time_zone_exists
    exists_act_historys = current_user.activity_historys.where(
      #抽出条件追加
      "(from_time < ? AND ? < to_time OR from_time < ? AND ? < to_time) OR
       (? < from_time AND to_time < ?)",
      from_time,
      from_time,
      to_time,
      to_time,
      from_time,
      to_time
    )

    if action == "create" && exists_act_historys.count > 0
      errors.add(:from_time, ": 同一時間帯の登録はできません")
      errors.add(:to_time, ": 同一時間帯の登録はできません")
    elsif action == "update" && exists_act_historys.count > 0
      exists_act_historys.pluck(:id).each do |exists_ids|
        if exists_ids != id
          errors.add(:from_time, ": 同一時間帯の登録はできません")
          errors.add(:to_time, ": 同一時間帯の登録はできません")
        end
      end
    end
  end
```

![スクリーンショット 2019-09-29 18.04.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468786/25bed1b4-961e-36f5-33c2-2e85771db644.png)
⬇️テスト再挑戦
![スクリーンショット 2019-09-29 18.07.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468786/79666b4c-52de-3830-5a92-5410b7e50ef1.png)
⬇️正しくエラーになることを確認
![スクリーンショット 2019-09-29 18.16.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468786/3f3534ff-35e4-e6bd-1468-a4667864260b.png)


##感想
とりあえず実装はできたので良いが、結構処理が複雑になっているように見えるので、もっとスリムにしたいです。他に方法があったらご指摘ください