【Rails】FullCalendarを用いたスケジュール管理昨日の実装

# 目標
![ezgif.com-video-to-gif (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/579893/5b8f05ee-d679-7760-166e-21445aafcc26.gif)

# 開発環境
・Ruby: 2.5.7
・Rails: 5.2.4
・Vagrant: 2.2.7
・VirtualBox: 6.1
・OS: macOS Catalina

# 前提

下記実装済み。

・[Slim導入](https://qiita.com/matsubishi5/items/b639ffddecc626856039)
・[Bootstrap3導入](https://qiita.com/matsubishi5/items/b117c95f4dd71340cc80)
・[Font Awesome導入](https://qiita.com/matsubishi5/items/a4c72f436e64ad106ee8)
・[ログイン機能実装](https://qiita.com/matsubishi5/items/5bd8fdd45af955cf137d)
・[投稿機能実装](https://qiita.com/matsubishi5/items/1a5aeab651b5822d1f2c)

# 実装

## 1.Gemを導入

```ruby:Gemfile
# 追記
gem 'jquery-rails'
gem 'fullcalendar-rails'
gem 'momentjs-rails'
```

```terminal:ターミナル
$ bundle
```

## 2.`application.scss`を編集

```scss:application.scss
 *= require_tree .
 *= require_self
 *= require fullcalendar /*追記*/
```

## 3.`application.js`を編集

```scss:application.js
//= require rails-ujs
//= require activestorage
//= require turbolinks
//= require jquery
//= require moment // 追記 
//= require fullcalendar // 追記
//= require_tree .
```

## 4.JavaScriptファイル作成・編集

```terminal:ターミナル
$ touch app/assets/javascripts/calendar.js
```

```js:calendar.js
$(function() {
  function eventCalendar() {
    return $('#calendar').fullCalendar({});
  }

  function clearCalendar() {
    $('#calendar').html('');
  }

  $('#calendar').fullCalendar({
    events: '/events.json',

    titleFormat: 'YYYY年 M月',
    dayNamesShort: ['日', '月', '火', '水', '木', '金', '土'],

    header: {
      left: '',
      center: 'title',
      right: 'today prev,next',
    },

    defaultTimedEventDuration: '03:00:00',
    buttonText: {
      prev: '前',
      next: '次',
      prevYear: '前年',
      nextYear: '翌年',
      today: '今日',
      month: '月',
      week: '週',
      day: '日',
    },
    timeFormat: 'HH:mm',
    eventColor: '#63ceef',
    eventTextColor: '#000000',
  });
});
```

### 【解説】

#### ① turbolinks対策の為、カレンダーを読み込む関数と削除する関数をそれぞれ用意する。

```js
function eventCalendar() {
  return $('#calendar').fullCalendar({});
}

function clearCalendar() {
  $('#calendar').html('');
}
```

#### ② 日本語で表示する。

```js
// カレンダー上部を年月で表示させる
titleFormat: 'YYYY年 M月',

// 曜日を日本語表示
dayNamesShort: ['日', '月', '火', '水', '木', '金', '土'],
```

#### ③ ボタンのレイアウトを整える。

```js
header: {
  left: '',
  center: 'title',
  right: 'today prev,next',
},
```

#### ④ イベントの設定をする。

```js
// 終了時刻がないイベントの表示間隔を設定
defaultTimedEventDuration: '03:00:00',
buttonText: {
  prev: '前',
  next: '次',
  prevYear: '前年',
  nextYear: '翌年',
  today: '今日',
  month: '月',
  week: '週',
  day: '日',
},

// イベントの時間表示を24時間にする
timeFormat: 'HH:mm',

// イベントの色を変える
eventColor: '#63ceef',

// イベントの文字色を変える
eventTextColor: '#000000',
```

## 4.モデルを作成

```terminal:ターミナル
$ rails g model Event user_id:integer title:string body:text start_date:datetime end_date:datetime
```

```rb:~_create_events.rb
class CreateEvents < ActiveRecord::Migration[5.2]
  def change
    create_table :events do |t|
      t.integer :user_id
      t.string :title
      t.text :body
      t.datetime :start_date
      t.datetime :end_date

      t.timestamps
    end
  end
end
```

```terminal:ターミナル
$ rails db:migrate
```

## 5.コントローラーを作成

```terminal:ターミナル
$ rails g controller events new show edit my_calendar
```

```ruby:events_controller.rb
class EventsController < ApplicationController
  before_action :set_event, only: [:show, :edit, :update, :destroy]

  def new
    @event = Event.new
  end

  def create
    @event = Event.new(event_params)
    @event.user_id = current_user.id
    @event.save ? (redirect_to event_path(@event)) : (render 'new')
  end

  def index
    @events = Event.where(user_id: current_user.id)
  end

  def show
  end

  def edit
    unless @event.user == current_user
      redirect_to root_path
    end
  end

  def update
    @event.update(event_params) ? (redirect_to event_path(@event)) : (render 'edit')
  end

  def destroy
    @event.destroy
    redirect_to my_calendar_path
  end

  def my_calendar
  end

  private

    def set_event
      @event = Event.find(params[:id])
    end

    def event_params
      params.require(:event).permit(:user_id, :title, :body, :start_date, :end_date)
    end
end
```

## 6.ルーティングを追加

```ruby:routes.rb
# 追記
resources :events
get 'my_calendar', to: 'events#my_calendar'
```

## 7.`json.jbuilder`ファイルを作成・編集

```ruby:index.json.jbuilder
json.array!(@events) do |event|
  json.extract! event, :id, :title, :body
  json.start event.start_date
  json.end event.end_date
  json.url event_url(event)
end
```

### 【解説】

#### ① `index`アクションで抽出したレコードを繰り返し処理し、配列を作成する。

```ruby
json.array! @category_children do |children|
```

#### ② 各データを `①` で作成した配列に格納する。

```ruby
json.extract! event, :id, :title, :body
json.start event.start_date
json.end event.end_date
json.url event_url(event)
```
## 8.ビューを編集

### `new.html.slim`を編集

#### ① HTMLを作成する。

```slim:new.html.slim
.row
  .col-xs-3

  .col-xs-6
    = form_with model: @event, url: events_path, local: true do |f|

      = f.label :title, 'スケジュール名'
      br
      = f.text_field :title, class: 'form-control'
      br

      = f.label :body, 'スケジュール内容'
      br
      = f.text_area :body, class: 'form-control'
      br

      = f.label :start_date, '開始日時'
      br
      = f.datetime_select :start_date, {}, class: 'form-control datetime-form'
      br
      br


      = f.label :end_date, '終了日時'
      br
      = f.datetime_select :end_date, {}, class: 'form-control datetime-form'
      br
      br

      = f.submit '新規登録', class: 'btn btn-success btn-block'

  .col-xs-3
```

#### ② CSSでデザインを整える。

```scss:application.scss
// 追記
.datetime-form {
  display: inline-block;
  width: auto;
}
```

### `show.html.slim`を編集

```slim:show.html.slim
.row
  .page-header
    h3
      | スケジュール管理

.row
  #calendar
  br

.row
  table.table.table-bordered
    tbody
      tr
        th.active
          | スケジュール名
        td
          = @event.title
      tr
        th.active
          | スケジュール内容
        td
          = @event.body
      tr
        th.active
          | 開始日時
        td
          = @event.start_date.to_s(:datetime_jp)
      tr
        th.active
          | 終了日時
        td
          = @event.end_date.to_s(:datetime_jp)

.row
  - if @event.user === current_user
    .pull-right
      = link_to 'スケジュール登録', new_event_path, class: 'btn btn-success'
      = link_to '編集', edit_event_path(@event), class: 'btn btn-primary'
      = link_to '削除', event_path(@event), method: :delete, data: { confirm: '本当に削除しますか？' }, class: 'btn btn-danger'
```

### `edit.html.slim`を編集

```slim:edit.html.slim
.row
  .col-xs-3

  .col-xs-6
    = form_with model: @event, url: event_path(@event), local: true do |f|

      = f.label :title, 'スケジュール名'
      br
      = f.text_field :title, class: 'form-control'
      br

      = f.label :body, 'スケジュール内容'
      br
      = f.text_area :body, class: 'form-control'
      br

      = f.label :start_date, '開始日時'
      br
      = f.datetime_select :start_date, {}, class: 'form-control datetime-form'
      br
      br


      = f.label :end_date, '終了日時'
      br
      = f.datetime_select :end_date, {}, class: 'form-control datetime-form'
      br
      br

      = f.submit '保存', class: 'btn btn-primary btn-block'

  .col-xs-3
```

### `my_calendar.html.slim`を編集

```slim:my_calendar.html.slim
.row
  .page-header
    h3
      | マイカレンダー

.row
  = link_to 'スケジュール登録', new_event_path, class: 'btn btn-success'

  #calendar
  br
```

# 注意

`turbolinks`を無効化しないとカレンダーが表示されない事があるので、必ず無効化しておきましょう。

[turbolinksを無効化する方法](https://qiita.com/matsubishi5/items/c4c8a5df03ae630ae534)