[ 第１回　読んで理解するRubyRails ] カレンダー(Simple_Calendar) の 実装・詳細

※ Ruby on Rails 学習済みの方のための記事になります。
## 記事の目的
Ruby on Rails は、Gem という形で再利用された他人のコードを開放してくれたために、多くの開発者に、難しいコードを書かなくても、比較的簡単に開発を行える力を与えました。

便利ですよね。たった、たった数行書くだけで、画像アップロードができたりとか。

**でも そうこうしているうちに、Gem をよく理解していないまま開発する癖がついっちゃっていませんか？**

自分も、そんな一人です。

> このまま 中身をろくに理解せず、Gem のコピーアンドペイストを続けていて、本当にエンジニア力がつくのだろうか・・・

この記事は、そんな自分に対する自戒を込めて、そして もしかしたら心の中でそんな 違和感を持っている あなた のために、日頃使っている Gem を解説しよう！という記事です。

## 想定する読者
- Gem で全てが叶ってしまう Rails に飽きた人
- Rails 習ったけど、成長実感が湧かない人
- Ruby・Rails を もっと勉強したい人

## 「カレンダー」　Simple_Calender
第１回の今回は、**「カレンダー」「Simple_Calendar」** ！

意外と作ろうとすると、考えること多いですよね。カレンダー・・・

>　　そう思うと、ついつい使ってしまう 「Google検索」
　　えっとお・・・
　　「Rails カレンダー」でいいかな。カタカタカタ・・・
　　お。あった。何々「[【rails】simple_calendarを使ってカレンダーつきのブログ ...](https://qiita.com/isaatsu0131/items/ad1d0a6130fe4fd339d0)」って・・・
　　え！！！！

```erb:/app/views/blogs/index.html.erb
<%= month_calendar events: @blogs do |date, blogs| %>
  <%= date.day %>

  <% blogs.each do |blog| %>
    <div>
      <%= link_to blog.title, blog %>
    </div>
  <% end %>
<% end %>
```
>　　これだけ！！！！

面倒な実装考えないでよかった！ラッキー！
よし！これからは、Simple_Calendar 一択だな！

**そんなふうに考えていた時期が私にもありました。**

でも、気づいてしまったんだよ。そんなの、他人のコードを使って、楽したいだけの言い訳だって。

> 僕はもう逃げないよ。レール ズ。

では、早速、Simple_Calendar の実装を見てみましょう。

## Simple_Calendar 実装

[上の記事](https://qiita.com/isaatsu0131/items/ad1d0a6130fe4fd339d0) にあるように、10行程度でカレンダーが書けてしまう Simple_Calendar の Gem のソースコードは、[こちら](https://github.com/excid3/simple_calendar) にあります。

見てみると、[一番大きいファイル](github.com/excid3/simple_calendar/blob/master/lib/simple_calendar/calendar.rb) でも、127行しかない意外と軽量な Gem だったんです。

**まずは、全体像から見てみましょう！**

#### ファイル構成 ####
[こちら](https://qiita.com/9sako6/items/72994b8b1c00af4e61fe#31-gemの実装) にも書いているんですが、この Gem の中身の実装は、主に `/lib` にあります。

`/lib` の中 で主に使うのは、

- `/lib/simple_calendar/calendar.rb`　主要なカレンダーの機能
- `/lib/simple_calendar/month_calendar.rb`　calendar.rb を継承したMonthカレンダー
- `/lib/simple_calendar/week_calendar.rb`　calendar.rb を継承したWeekカレンダー
- `/lib/simple_calendar/view_helpers.rb`　一部機能 を View で使えるようにした Helper
- `/lib/simple_calendar/railtie.rb`　ActiveView に、上記 Helper 追加
- `/lib/generators/simple_calendar/views_generator.rb`　Railsの `/app/views` に `simple_calendars` 追加

の６ファイルになりますが、

最後の `/lib/generators/simple_calendar/views_generator.rb` で、関連する `/app/views/` の下の ファイルをコピーしています([こちら参照（英語）](https://stackoverflow.com/questions/7369947/how-to-copy-entire-directory-in-rails-generator-from-templates))。
コピーされるのは、この３つのファイルで、

- `/app/views/simple_calendar/_calendar.html.erb`　カレンダー向けView 
- `/app/views/simple_calendar/_month_calendar.html.erb`　Monthカレンダー向けView
- `/app/views/simple_calendar/_week_calendar.html.erb`　Weekカレンダー向けView


それぞれ、

- ＿calendar.html.erb は、`calendar(opt) do |d| block end`
- ＿month_calendar.html.erb は、`month＿calendar(opt) do |d| block end`
- ＿week_calendar.html.erb は、`week＿calendar(opt) do |d| block end`

と、関連付いています。
- - -
このように、Simple_Calendar のファイル構成は簡単に言えば、実装部分の `lib` と View部分の `app` に分かれています。

では、実際に、中身を見てみましょう。

## Simple_Calendar 実装
まずは、Simple_Calendar で使われている

```erb:/app/views/blogs/index.html.erb
<%= month_calendar events: @blogs do |date, blogs| %>
  <%= date.day %>

  <% blogs.each do |blog| %>
    <div>
      <%= link_to blog.title, blog %>
    </div>
  <% end %>
<% end %>
```

の ` month_calendar ` や ` calendar ` は、どこにあるのでしょうか？
それらは、` lib/simple_calendar/view_helpers.rb ` ( [リンク](https://github.com/excid3/simple_calendar/blob/master/lib/simple_calendar/view_helpers.rb) ) から 読み込まれることになっています。

中身を見ると、下の内容のような

```ruby:/lib/simple_calendar/view_helpers.rb　3～6行目
def calendar(options={}, &block)
  raise 'calendar requires a block' unless block_given?
  SimpleCalendar::Calendar.new(self, options).render(&block)
end
```

に 似たものが、calendar / month_calendar / week_calendar でそれぞれ作られています。

ここでは、
　1. block があるか確認。なかったら raise
　2. [SimpleCalendar::Calendar](https://github.com/excid3/simple_calendar/blob/master/lib/simple_calendar/calendar.rb) でインスタンス作り、render アクション呼び出しを行っています。

では、[SimpleCalendar::Calendar](https://github.com/excid3/simple_calendar/blob/master/lib/simple_calendar/calendar.rb) の ` new と render ` はそれぞれ、何をやっているのでしょう？

まずは、` new (initialize) ` から コードを見てみましょう。

```ruby:/lib/simple_calendar/calendar.rb　9～20行目
    def initialize(view_context, opts={})
      @view_context = view_context
      @options = opts

      # Next and previous view links should use the same params as the current view
      @params = @view_context.respond_to?(:params) ? @view_context.params : Hash.new
      @params = @params.to_unsafe_h if @params.respond_to?(:to_unsafe_h)
      @params = @params.with_indifferent_access.except(*PARAM_KEY_BLACKLIST)

      # Add in any additional params the user passed in
      @params.merge!(@options.fetch(:params, {}))
    end
```

ここでの引数、view_context は self (view_helper から呼び出されたview ) / opts は コードで入力されたパラメーター ( 上記の events: @blogs のようなもの ) を示しています。
view_context からは、params を取得して、Parameters型から Hash型に変換 ( [こちら参照](https://qiita.com/kanna/items/171118e479c2dc76bb7c) ) して、
最後に マージして、@params と @options を初期化しています。

次に、render(&block) を呼び出します。

```ruby:/lib/simple_calendar/calendar.rb　22～33行目
    def render(&block)
      view_context.render(
        partial: partial_name,
        locals: {
          passed_block: block,
          calendar: self,
          date_range: date_range,
          start_date: start_date,
          sorted_events: sorted_events
        }
      )
    end
```

ここで、[partical_name](https://github.com/excid3/simple_calendar/blob/master/lib/simple_calendar/calendar.rb#L74)　で 指定した /app/view 下にある .html.erb から View を呼び出し、

- [date_range](https://github.com/excid3/simple_calendar/blob/master/lib/simple_calendar/calendar.rb#L68)　取得した日時配列 
-  [start_date](https://github.com/excid3/simple_calendar/blob/master/lib/simple_calendar/calendar.rb#L111)　取得した開始日 
-   [sorted_event](https://github.com/excid3/simple_calendar/blob/master/lib/simple_calendar/calendar.rb#L90)　イベント情報配列 と、
-  self ( SimpleCalendar::Calendar の private 以外のメソッド含む ) 
-  block ( <%= date.day %> のような中身のブロック → [こちら参照](https://qiita.com/genya0407/items/1a34244cba6c3089a317)  ) 

を、render メソッドを通して、 [_calendar.html.erb](https://github.com/excid3/simple_calendar/blob/master/app/views/simple_calendar/_calendar.html.erb) のような View ファイルに渡しています。

```erb:_calendar.html.erb
<div class="simple-calendar">
  <div class="calendar-heading">
    <%= link_to t('simple_calendar.previous', default: 'Previous'), calendar.url_for_previous_view %>
    <span class="calendar-title"><%= t('date.month_names')[start_date.month] %> <%= start_date.year %></span>
    <%= link_to t('simple_calendar.next', default: 'Next'), calendar.url_for_next_view %>
  </div>

  <table class="table table-striped">
    <thead>
      <tr>
        <% date_range.slice(0, 7).each do |day| %>
          <th><%= t('date.abbr_day_names')[day.wday] %></th>
        <% end %>
      </tr>
    </thead>

    <tbody>
      <% date_range.each_slice(7) do |week| %>
        <%= content_tag :tr, class: calendar.tr_classes_for(week) do %>
          <% week.each do |day| %>
            <%= content_tag :td, class: calendar.td_classes_for(day) do %>
              <% if defined?(Haml) && respond_to?(:block_is_haml?) && block_is_haml?(passed_block) %>
                <% capture_haml(day, sorted_events.fetch(day, []), &passed_block) %>
              <% else %>
                <% passed_block.call day, sorted_events.fetch(day, []) %>
              <% end %>
            <% end %>
          <% end %>
        <% end %>
      <% end %>
    </tbody>
  </table>
</div>
```

この erb ファイルの中にある、calendar(メソッド呼び出ししている)・start_date・date_range・sorted_event・passed_block がそれぞれ、render メソッド の locals で渡された変数です。

その calendar は、まず 上の部分で

- url_for_previous_view・url_for_next_view での URL取得(header)
- previous・next での 月取得(header)

をして、その後 日時一覧である date_range.each_slice(7) で週ごとに分かれた week変数を、さらに day 変数に分けて、それぞれで、

- tr_classes_for(week)・td_classes_for(day) での class 生成


を実行しています。

それとは別に、各day 変数では、

call・capture_haml が、block (<%= date.day %> のような中身のブロック) と引数を受け取って、日時ごとの情報の表示をしています。

- - -

ここまでで、大まかな SimpleCalendar::Calendar の実装を見てみました。軽く触れたメソッドの細かい実装は是非とも見てみてください。

次に、継承された実装の例として week_calendar.rb を見てみましょう()。

## 継承　month_calendar
まず、 `/libs/simple_calendar/week_calendar.rb`（ [リンクはこちら](https://github.com/excid3/simple_calendar/blob/master/lib/simple_calendar/week_calendar.rb) ）を見てみると、render はありませんね。

```ruby:week_calendar.rb　１～２行目
module SimpleCalendar
  class WeekCalendar < SimpleCalendar::Calendar
```

の部分で継承されていますが、新しいメソッドで作り直しているようではないです。

中身は、week_numbers などで実装されています。是非とも見てみましょう。

## まとめ
この記事では、[Simple_Calendar](https://github.com/excid3/simple_calendar) の実装を見てみた。

まとめると、
　1. gem有の実装の calendar(～) do |d| は、[view_helpers.rb](https://github.com/excid3/simple_calendar/blob/master/lib/simple_calendar/view_helpers.rb) で生成されている
　2. 上記の helper で、[SimpleCalendar::Calendar](https://github.com/excid3/simple_calendar/blob/master/lib/simple_calendar/calendar.rb) の initialize・render が行われる
　3. 上記の render メソッドの partial_name で 呼び出す erb ファイルを指定し、locals で変数を渡します。その中の calendar を通じて、[SimpleCalendar::Calendar](https://github.com/excid3/simple_calendar/blob/master/lib/simple_calendar/calendar.rb) のメソッドを呼びます。

こう見てみたら、Gem で簡素化された割には難しい内容ではなかったのでは？と思います。


この Simple_Calendar で、何か作ってみたい機能を探して作ってみて、プルリクエストを送れるようになればいいですね。