Rails で簡単にカレンダーを扱える simple_calendar で週表示カレンダーのフォーム作成を試す

## はじめに

Rails でカレンダーを簡単に取り扱えそうな <a href="https://github.com/excid3/simple_calendar" target="_blank" rel="nofollow noopener">simple_calendar</a> という gem の存在を知りました。

そこで、週表示のカレンダーで、それぞれの日に予定を入れるフォームを作るとどんな感じになるか、サンプルを作りながら検証しました。

view は読みやすくするために haml も導入して試そうと思います。

## 環境の前提

* Rails 5.1.6
* ruby 2.4.3

## 目標

下記のような、日毎に入力フォームがある週表示カレンダーの作成を目指します。

![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f3232323033302f33393063663362642d353836372d653762392d383265372d3139333462633330653130332e706e67.png](https://qiita-image-store.s3.amazonaws.com/0/222030/0da5c4df-fc68-a375-b52a-6a4c06b7ea84.png)


## 手順

### 1. 初期設定

#### rails アプリケーション新規作成

以下コマンドを実行します。

```bash
$ rails new calendar-sample
$ cd calendar-sample
```

#### Gem インストール

今回利用する <a href="https://github.com/excid3/simple_calendar" target="_blank" rel="nofollow noopener">simple_calendar</a>, <a href="https://github.com/indirect/haml-rails" target="_blank" rel="nofollow noopener">haml-rails</a> をインストールします。

```ruby:Gemfile
# 追記
gem 'simple_calendar', '~> 2.0'
gem 'haml-rails'
```

```bash
$ bundle
```

#### simple_calendar の view テンプレートを生成

あとから simple_calendar のデザインを修正できるように、テンプレートを生成します。

```bash
$ rails g simple_calendar:views
```

#### haml に変換

haml-rails のコマンドで haml に変換します。

```
$ rake haml:erb2haml
```

#### サンプル Model作成

表示確認用の Meeting モデルを scaffold で作成します。

```
$ rails g scaffold Meeting name start_time:date content:string
```

#### simple_calendar のカレンダー表示設定

週表示でカレンダーを表示できるよう、simple_calendar を設定します。

```ruby:app/views/meetings/index.html.haml
-# 追記
= week_calendar do |date| 
  = date
```

#### 表示確認

Migrateしてサーバを起動します。

```bash
$ rails db:migrate
$ rails s
```

http://localhost:3000/meetings にアクセスし、週表示カレンダーが表示されていることを確認します。

<img width="662" alt="Screen Shot 2018-06-30 at 11.45.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/222030/4352a5ca-db86-6bf1-caad-984b0de18c31.png">


### 2. カレンダーのフォーム作成

#### route 登録

一括でデータ登録用のメソッドを routes に定義に定義します。

```config/routes.rb
Rails.application.routes.draw do
  resources :meetings do
    collection do
      post :update_week
    end
  end
end
```

#### view 実装

日毎に content が入力できるようなフォームを実装します。

```app/views/meetings/index.html.haml
-# 以下に書き換え
%h1 Week Meetings

= form_for :meeting, url: update_week_meetings_path, html: { method: :post } do
  = week_calendar do |date|
    = date
    - meeting = @meetings.find_or_initialize_by(start_time: date)
    - if meeting.id.nil?
      %p
        = hidden_field_tag "meeting[#{date}][start_time]", date
      %p
        = text_field_tag "meeting[#{date}][content]"
    - else
      = fields_for "meeting[]", meeting do |meeting_fields|
        %p
          = meeting_fields.hidden_field :start_time
        %p
          = meeting_fields.text_field :content
  = submit_tag
```

メソッドに ```find_or_initialize_by``` を使うことで、レコードの新規作成・編集どちらにも対応できるようにしています。

また、 ```meeting.id``` の有無で処理を分岐させていますが、これはクエリパラメータの形を統一するためです。

既存データはidをキーとしたハッシュで格納されるのに対し、新規データはidがないのでハッシュの配列で格納されます。

**idがある場合**

```
 "meeting"=>
  {"1"=>{"start_time"=>"2018-07-02", "content"=>"A"},
   "2"=>{"start_time"=>"2018-07-03", "content"=>"B"}}
```

**idがない場合**

```
 "meeting"=>
  [{"start_time"=>"2018-07-02", "content"=>""},
   {"start_time"=>"2018-07-03", "content"=>""}]
```



そのため、配列が入ったクエリパラメータをPOSTしようとすると下記エラーが生じるため、ハッシュを使う形に統一できるようにしています。

```
Rack::QueryParser::ParameterTypeError (expected Hash (got Array) for param `meeting'):
```

#### 参考URL

* <a href="https://qiita.com/t_oginogin/items/531b18f7f0173fc7f35f" target="_blank" rel="nofollow noopener">Railsで、同じFormで同じModelに対しての修正と登録を混在させる</a>

### controller  設定

view で入力された複数日のデータを一括で登録・更新するためのメソッドを実装します。

```app/controllers/meetings_controller.rb
  # 追記
  def update_week
    week_meetings_params.keys.each do |key|
      @meeting = Meeting.find_or_initialize_by(start_time: week_meetings_params[key][:start_time].to_date)
      @meeting.update_attributes(week_meetings_params[key])
    end
    redirect_to meetings_path
  end

  # 中略...

  private
    # 追記
    def week_meetings_params
      params.require(:meeting).permit!
    end
```

http://localhost:3000/meetings にアクセスし、週ごとのフォームが更新できることを確認します。

![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f3232323033302f33393063663362642d353836372d653762392d383265372d3139333462633330653130332e706e67.png](https://qiita-image-store.s3.amazonaws.com/0/222030/0da5c4df-fc68-a375-b52a-6a4c06b7ea84.png)



## 終わりに

突貫で作成したサンプルなので粗い部分もあるかと思いますが、simple_calendar を使った週表示のフォームを試すことが出来ました。カレンダー部分の処理をシンプルに取り扱えて便利だなと思いました。


## 参考URL

* <a href="https://github.com/excid3/simple_calendar" target="_blank" rel="nofollow noopener">GitHub - excid3/simple_calendar: A wonderfully simple calendar gem for Rails</a>
* <a href="https://github.com/indirect/haml-rails" target="_blank" rel="nofollow noopener">GitHub - indirect/haml-rails: let your Gemfile do the talking</a>
* <a href="http://twosquirrel.mints.ne.jp/?p=9361" target="_blank" rel="nofollow noopener">Railsで複数行の一括更新のリンク</a>
* <a href="https://www.te-nu.com/entry/2017/02/16/233416" target="_blank" rel="nofollow noopener">RailsのActiveRecordでcreate_or_updateを分岐なしでスマートにする方法</a>