Rails4で2つの日時情報を開始日時終了日時という範囲指定をおこなうように実装した時の備忘録です。

bootstrap-datetimepicker-railsのインストール

日時を指定するためのカレンダーをbootstrap3で実装しようと思い、調べたらTrevorS/bootstrap3-datetimepicker-railsというgemライブラリが存在しました。

Gemライブラリのインストール

TrevorS/bootstrap3-datetimepicker-railsをインストールします。

Gemfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# jQuery plugin for drop-in fix binded events problem caused by Turbolinks
gem 'jquery-turbolinks'

# See https://github.com/sstephenson/execjs#readme for more supported runtimes
gem 'therubyracer',  platforms: :ruby

# Install Twitter Bootstrap3
# https://github.com/twbs/bootstrap-sass
gem 'bootstrap-sass', '~> 3.2.0'

# -webkit-border-radius みたいなブラウザベンダープレフィックスをよしなに管理してくれる
# Parse CSS and add vendor prefixes to rules by Can I Use 
# https://twitter.com/autoprefixer
gem 'autoprefixer-rails'

# Istall of bootstrap3-datetimepicker-rails
# See https://github.com/TrevorS/bootstrap3-datetimepicker-rails
gem 'momentjs-rails', '>= 2.8.1'
gem 'bootstrap3-datetimepicker-rails', '~> 3.1.3'

ライブラリをインストールします。

1
bundle install

javascriptライブラリのinclude

app/assets/javascripts/application.jsに以下を追記します。

app/assets/javascripts/application.js
1
2
3
4
5
6
7
8
//= require jquery
//= require jquery.turbolinks
//= require jquery_ujs
//= require bootstrap-sprockets
//= require moment
//= require bootstrap-datetimepicker
//= require turbolinks
//= require_tree .

スタイルシートの読み込み

app/assets/stylesheets/application.css.scssに以下を追記します。

app/assets/stylesheets/application.css.scss
1
2
3
@import "bootstrap-sprockets";
@import "bootstrap";
@import 'bootstrap-datetimepicker';

2つの期間を指定するdatetimepickerを実装

Entryリソースの生成

今回は2つの期間を指定したエントリを保存するためのEntryリソースをscaffoldで生成します。 開始日時をdatetime型start_atアトリビュート、終了日時をdatetime型end_atアトリビュートとして定義しています。

1
2
3
4
5
6
7
8
9
bundle exec rails g scaffold Entry name:string start_at:datetime end_at:datetime description:string
bundle exec rake db:migrate

cat << EOT > config/routes.rb
Rails.application.routes.draw do
  root 'entries#index'
  resources :entries
end
EOT

javascriptの記述

http://eonasdan.github.io/bootstrap-datetimepicker/#example9を参考にしました。

app/views/entries/_form.html.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<script type="text/javascript">
  $(function(){

    // bootstrap-datetimepicker-rails configurations
    //   - references
    //     1. http://eonasdan.github.io/bootstrap-datetimepicker/#options
    //     2. http://shibuso.github.io/datetimepicker_test/datetimepicker_02.html


    // .datepicker_start へのdatetimepicker設定
    $('.datepicker_start').datetimepicker({

      // カレンダー表示言語
      // bootstrap-datetimepicker.ja.js が必要
      //language: 'ja',

      // 月日ピッカーと時間ピッカーを隣に表示する
      //sideBySide: true,

      // 日時フォーマット
      format: 'YYYY-MM-DD HH:mm',

      // 分の選択可能間隔
      minuteStepping: 5,

    });

    // .datepicker_end へのdatetimepicker設定
    $('.datepicker_end').datetimepicker({

      // カレンダー表示言語
      // bootstrap-datetimepicker.ja.js が必要
      //language: 'ja',

      // 月日ピッカーと時間ピッカーを隣に表示する
      //sideBySide: true,

      // 日時フォーマット
      format: 'YYYY-MM-DD HH:mm',

      // 分の選択可能間隔
      minuteStepping: 5,

    });

    // set a minimum date of start_at
    $('.datepicker_start').on("dp.change",function (e) {
      $('.datepicker_end').data("DateTimePicker").setMinDate(e.date);
    });

    // set a maximum date of end_at
    $('.datepicker_end').on("dp.change",function (e) {
      $('.datepicker_start').data("DateTimePicker").setMaxDate(e.date);
    });

  });
</script>

viewの記述(ERB)

app/views/entries/_form.html.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<%= form_for ( @entry ) do |f| %>
  <% if @entry.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@entry.errors.count, "error") %> prohibited this entry from being saved:</h2>

      <ul>
      <% @entry.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="col-sm-8 col-md-8">
    <div class="form-group">
      <%= f.label :name, '名前' %><br>
      <%= f.text_field :name, :class => 'form-control', :placeholder => "エントリーの名前を入力してください..." %>
    </div>
  </div>

  <div class="col-sm-8 col-md-8">
    <div class="form-group">
      <%= f.label :start_at, '開始日時' %><br>
      <div class='input-group datepicker_start'>
        <%= f.text_field :start_at, :class => 'form-control', :readonly => 'true', :placeholder => "開始日時を選択してください..." %>
        <span class="input-group-addon">
          <span class="glyphicon glyphicon-calendar"></span>
        </span>
      </div>
    </div>
  </div>

  <div class="col-sm-8 col-md-8">
    <div class="form-group">
      <%= f.label :end_at, '終了日時' %><br>
      <div class='input-group datepicker_end'>
        <%= f.text_field :end_at, :class => 'form-control', :readonly => 'true', :placeholder => "終了日時を選択してください..." %>
        <span class="input-group-addon">
          <span class="glyphicon glyphicon-calendar"></span>
        </span>
      </div>
    </div>
  </div>

  <div class="col-sm-8 col-md-8">
    <div class="form-group">
      <%= f.label :description, '説明' %><br>
      <%= f.text_field :description, :class => 'form-control', :placeholder => "エントリーの説明を入力してください..." %>
    </div>
  </div>

  <div class="col-sm-8 col-md-8">
    <div class="actions">
      <%= f.submit 'エントリーを作成', :class => 'btn btn-primary' %>
    </div>
  </div>

<% end %>

参考にさせて頂いた情報

http://qiita.com/kidachi_/items/9f76a27890d96b9f5838
http://www.workabroad.jp/posts/974
http://eonasdan.github.io/bootstrap-datetimepicker/#example9

エントリー期間内に複数のイベントを定義できるように実装する

定義したエントリーの中には複数のイベントを持つことができるようにEventリソースを生成します。イベントは食事などのカテゴリーを持つようにしたいのでCategoryリソースを先に生成します。

イベントに付けるカテゴリーのリストを生成する

1
bundle exec rails g scaffold category name:string icon_name:string

イベント用のEventリソースを生成する

1
bundle exec rails g scaffold event entry:references name:string start_at:datetime end_at:datetime description:string category:references
1
bundle exec rake db:migrate

モデルのアソシエーションを記述する

app/models/entry.rb
1
2
3
class Entry < ActiveRecord::Base
  has_many :events
end
app/models/event.rb
1
2
3
4
class Event < ActiveRecord::Base
  belongs_to :entry
  belongs_to :category
end
app/models/category.rb
1
2
3
class Category < ActiveRecord::Base
  has_many :events
end

エントリーの編集ページにイベント作成ページへのリンクを設置

イベントは特定のエントリーに属するのでエントリーの編集画面から追加できるようにします。

1
<%= link_to "#{@entry.name} にイベントを追加", "/events/#{@entry.id}/new" %>

リンクURLがRESTFulなルーティングリソースではないのでconfig/routes.rbにルーティングを追記します。

config/routes.rb
1
2
resources :events
get 'events/:entry_id/new' => 'events#new'

イベント作成ページの`Entry_id`のテキストフィールドにエントリーIDを表示

app/controllers/events_controller.rb
1
2
3
4
5
  # GET /events/:entry_id/new
  def new
    entry_id = params.require(:entry_id)
    @event = Event.new( :entry_id => entry_id )
  end
app/views/events/_form.html.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
<script type="text/javascript">

  $(function(){

    // bootstrap-datetimepicker-rails configurations
    //   - references
    //     1. http://eonasdan.github.io/bootstrap-datetimepicker/#options
    //     2. http://shibuso.github.io/datetimepicker_test/datetimepicker_02.html

    var start_at = new Date( <%= DateTime.parse( @entry.start_at.to_s ).utc.to_i*1000 %> );
    var end_at = new Date( <%= DateTime.parse( @entry.end_at.to_s ).utc.to_i*1000 %> );

    // define datepicker of start_at
    $('.datepicker_start').datetimepicker({

      // カレンダー表示言語
      // bootstrap-datetimepicker.ja.js が必要
      //language: 'ja',

      // 月日ピッカーと時間ピッカーを隣に表示する
      //sideBySide: true,

      // 日時フォーマット
      format: 'YYYY-MM-DD HH:mm',

      // 分の選択可能間隔
      minuteStepping: 5,

      // 選択可能な最小日
      minDate: start_at,

      // 選択可能な最大日
      maxDate: end_at,

    });

    // define datepicker of end_at
    $('.datepicker_end').datetimepicker({

      // カレンダー表示言語
      // bootstrap-datetimepicker.ja.js が必要
      //language: 'ja',

      // 月日ピッカーと時間ピッカーを隣に表示する
      //sideBySide: true,

      // 日時フォーマット
      format: 'YYYY-MM-DD HH:mm',

      // 分の選択可能間隔
      minuteStepping: 5,

      // 選択可能な最小日
      minDate: start_at,

      // 選択可能な最大日
      maxDate: end_at,

    });

  });

</script>

iv class="field">
 |f| %>
  <% if @event.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@event.errors.count, "error") %> prohibited this event from being saved:</h2>

      <ul>
      <% @event.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :entry_id %><br>
    <%= f.text_field :entry_id, :readonly => true %>
  </div>
  <div class="field">
    <%= f.label :name %><br>
    <%= f.text_field :name %>
  </div>

  <!-- 開始日時カレンダー -->
  <div class="col-sm-8 col-md-8">
    <div class="form-group">
      <%= f.label :start_at, '開始日時' %><br>
      <div class='input-group datepicker_start'>
        <%= f.text_field :start_at,
                         :value => @entry.start_at,
                         :class => 'form-control',
                         :readonly => 'true',
                         :placeholder => "開始日時を選択してください..." %>

        <!-- glyphicon -->
        <span class="input-group-addon">
          <span class="glyphicon glyphicon-calendar"></span>
        </span>

      </div><!-- /.input-group datepicker_start -->
    </div><!-- /.form-group -->
  </div><!-- /.col-sm .col-md -->

  <!-- 終了日時カレンダー -->
  <div class="col-sm-8 col-md-8">
    <div class="form-group">
      <%= f.label :end_at, '終了日時' %><br>
      <div class='input-group datepicker_end'>
        <%= f.text_field :end_at,
                         :value => @entry.end_at,
                         :class => 'form-control',
                         :readonly => 'true',
                         :placeholder => "終了日時を選択してください..." %>
        <!-- glyphicon -->
        <span class="input-group-addon">
          <span class="glyphicon glyphicon-calendar"></span>
        </span>

      </div><!-- /.input-group datepicker_start -->
    </div><!-- /.form-group -->
  </div><!-- /.col-sm .col-md -->

  <div class="field">
    <%= f.label :description %><br>
    <%= f.text_field :description %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>
<%= f.label :entry_id %><br>
<%= f.text_field :entry_id, :readonly => true %>

“`