0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-11-26

※ Ruby on Rails 学習済みの方のための記事になります。

記事の目的

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

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

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

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

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

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

想定する読者

  • Gem で全てが叶ってしまう Rails に飽きた人
  • Rails 習ったけど、成長実感が湧かない人
  • Ruby・Rails を もっと勉強したい人

「カレンダー」 Simple_Calender

第1回の今回は、「カレンダー」「Simple_Calendar」

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

  そう思うと、ついつい使ってしまう 「Google検索」
  えっとお・・・
  「Rails カレンダー」でいいかな。カタカタカタ・・・
  お。あった。何々「【rails】simple_calendarを使ってカレンダーつきのブログ ...」って・・・
  え!!!!

/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 実装

上の記事 にあるように、10行程度でカレンダーが書けてしまう Simple_Calendar の Gem のソースコードは、こちら にあります。

見てみると、一番大きいファイル でも、127行しかない意外と軽量な 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/viewssimple_calendars 追加

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

最後の /lib/generators/simple_calendar/views_generator.rb で、関連する /app/views/ の下の ファイルをコピーしています(こちら参照(英語))。
コピーされるのは、この3つのファイルで、

  • /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 で使われている

/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_calendarcalendar は、どこにあるのでしょうか?
それらは、lib/simple_calendar/view_helpers.rb ( リンク ) から 読み込まれることになっています。

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

/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 でインスタンス作り、render アクション呼び出しを行っています。

では、SimpleCalendar::Calendarnew と render はそれぞれ、何をやっているのでしょう?

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

/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型に変換 ( こちら参照 ) して、
最後に マージして、@params@options を初期化しています。

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

/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 で 指定した /app/view 下にある .html.erb から View を呼び出し、

  • date_range 取得した日時配列
  • start_date 取得した開始日
  • sorted_event イベント情報配列 と、
  • self ( SimpleCalendar::Calendar の private 以外のメソッド含む )
  • block ( <%= date.day %> のような中身のブロック → こちら参照 )

を、render メソッドを通して、 _calendar.html.erb のような View ファイルに渡しています。

_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リンクはこちら )を見てみると、render はありませんね。

week_calendar.rb 1~2行目
module SimpleCalendar
  class WeekCalendar < SimpleCalendar::Calendar

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

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

まとめ

この記事では、Simple_Calendar の実装を見てみた。

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

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

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

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?