(rails) SimpleCalenderGemのコードを読みながら、Report機能の処理を考え直す

  • SimpleCalenderのコードを読む

    • view_contextというViewクラスのインスタンスを操作していた
    • Calenderクラスを継承し、WeekCalender, MonthCalendeの2つのクラスが作られ処理を分けて書いていた。
  • params[:period]で条件分岐して、別のReportインスタンスを作成する(WeeklyReport, MonthlyReport)

  • それぞれのクラスでnext_period, prev_periodというメソッドを定義して、Viewからメソッドにアクセスできるようにする

  • SimpleCalender gem のコードを読もうとしたが、まだまだ分からない部分が多かった。プロを目指す人のためのRuby入門に書かれていたコードが多く出てきたが、理解しにくい。教科書と応用の差を感じた。

  • Railsの仕組みを少し知ることができた。(コントローラのインスタンス変数がViewでそのまま使えるのはなぜか?など)

class StatisticsController < ApplicationController
  before_action :require_user_logged_in, :set_current_user

  def report
    set_params(statistics_params)
    if period == :week
      @chart  = GenerateReport::Report::WeeklyReport.new(current_user, column, period, date_begin, date_end).call
    else period == :month
      @chart  = GenerateReport::Report::MonthlyReport.new(current_user, column, period, date_begin, date_end).call
    end
  end

private

  def set_params(params)
    column = params[:column].nil? ? :activity : params[:column].to_sym
    period = params[:period].nil? ? :week : params[:period].to_sym
    date_begin = params[:date_begin].nil? ? Date.today.begininng_of_week : params[:date_begin].to_date
    date_end = params[:date_end].nil? ? Date.today.end_of_week : params[:date_end].to_date
  end

  def statistics_params
    params.permit(:column, :date_begin, :date_end, :period).to_h
  end
end

(Rails) routingのcustom constraintsをlib直下のモジュールに定義する

アプリ制作

実現したいこと

体調グラフ機能で、クエリパラメータを使用せずにURI・コントローラを設計する。

取り組んだこと

  • TogglDashboardURIを観察してまとめる。

    • ルートは当日の週に設定
    • 当日の週or当月は /period/thisWeek(or Month)
    • 当日の前週or前月は/period/prevWeek(or Month)
    • それ以外の期間は/from/yyyy-mm-dd/to/yyyy-mm-dd
  • URIの設計を再度行う。

  /statistics => statistics#report 
  
  /statistics/:column/period/week or month => statistics#select_period
  
  /statistics/:column/from/yyyy-mm-dd/to/yyyy-mm-dd => select#select_date
    #=> key: 週の初めの日(Dateオブジェクト)
    #=> value: その週のレコードの配列
    group = diaries.group_by_week(series: true) { |n| n.diary_date }
    
    
    # 7月1週目の週のactivityカラムの集計をする
    date = Date.parse('2019-06-30', '%Y-%m-%d')
    
    group[date].map do |diary|
        diary.activity
    end
    #[-2, -1, 1, 0, -2, -2]
    ## コードめちゃ短くて済む!!
  • custom constraints の設定

    • URIのセグメントをバリデーションする処理をコントローラに記述していたが、コントローラが肥大化してしまう。routeのconstraintでそれを代わりに行うことで、コントローラを軽くする・
  # config/routes.rb
  Rails.application.routes.draw do
    
    get 'statistics/:column/period/:week_or_month', to: 'statistics#select_period', constraints: Constraint::SelectPeriodConstraint.new
    
    get 'statistics/:column/from/:date_begin/to/:date_end', to: 'statistics#select_date', constraints: Constraint::SelectDateConstraint.new
  end 
  
  # app/lib/constraint/select_date_constraint.rb
  module Constraint
    class SelectDateConstraint
      def matches?(request)
        date_regex = /\d{4}\-\d{1,2}\-\d{1,2}/
        date_regex.match(request.query_parameters['date_begin']) && date_regex.match(request.query_parameters['date_end'])
      end
    end
  end
  
  # app/lib/constraint/select_period_constraint.rb
  module Constraint
    class SelectPeriodConstraint
      PERIODS = ['thisWeek', 'thisMonth', 'prevWeek', 'prevMonth']
      
      def matches?(request)
        PERIODS.include?(request.query_parameters['week_or_month'])
      end
    end
  end

分からないこと

  • コントローラのbefore_actionで複数のメソッドを実行したい。1つのメソッドは引数を取る。

    • 下のようなことがしたい。これができるのかは分からない。Procとかまだやってない...
  class StatisticsController < ApplicationController
    before_action do
     require_user_logged_in,
     set_current_user,
     set_params(statistics_params)
    end
  # 省略
  end 
  • StatisticsControllerでReportを生成する処理をどうやって簡素に書くか?
    • 期間を表す単語(thisWeek, prevMonth等)で期間をしていする方法、date_begindate_endを直接指定する2つのやり方がある。
    • groupdate gem を使ってどう書けば良いか?
    • 重複の処理を書かないようにする。

プロを目指す人のためのRuby入門

  • 第9章 例外処理を理解する

StatisticsControllerの一部の処理をサービスクラスに移行する

アプリ制作

  • 週間・月間のグラフ描画機能

    • クエリパラメータで、column, date_begin, date_end, periodを指定して、それを元にグラフを描画できるようになった。
    • date_beginまたはdate_endとperiodを受け取って、ex_beginまたはnext_endを返すメソッドをstatistic_controller.rb のprivateに定義した。
  • グラフ描画のためのデータ(@report)を生成するためのサービスクラスを定義する

    • StatisticControllerが肥大化して分かりにくいため。
  • サービスクラスはapp/lib配下に置く必要がある。

  • app/lib/report/generate_report.rbの作成

結果

#app/controllers/statistics_controller.rb
class StatisticsController < ApplicationController
  before_action :require_user_logged_in, :set_current_user

  def report
    set_params(statistics_params)
    @chart  = Report::GenerateReport.new(current_user, @column, @period, @date_begin, @date_end).call
    @ex_begin = to_ex_or_next_period(@date_begin, @period)
    @next_end = to_ex_or_next_period(@date_end,@period)
  end

private

  def set_params(params)
    @column = params[:column].nil? ? :activity : params[:column].to_sym
    @period = params[:period].nil? ? :weeks : params[:period].to_sym
    @date_begin = params[:date_begin].nil? ? Date.today.begininng_of_week : parse_date(params[:date_begin])
    @date_end = params[:date_end].nil? ? Date.today.end_of_week : parse_date(params[:date_end])
  end

  def statistics_params
    params.permit(:column, :date_begin, :date_end, :period).to_h
  end

  def to_ex_or_next_period(date, period)
    case period
    when :months
      if date == date.beginning_of_month
        date.prev_month.beginning_of_month
      else date == date.end_of_month
        date.next_month.end_of_month
      end
    when :weeks
      if date.cwday == 1
        date.ago(7.days)
      else date.cwday == 7
        date.since(7.days)
      end
    else
      raise "pass valid argument"
    end
  end
end
#app/lib/report/generate_report.rb
module Report
  class GenerateReport
      def initialize(user, column, period, date_begin, date_end)
        @user = user
        @column = column
        @period = period
        @date_begin = date_begin
        @date_end = date_end
      end

      def call
        chart_data(@user, @column, @date_begin, @date_end, @period)
      end

      private

      def chart_data(user, column, date_begin, date_end, period)
        x_axis_data = (date_begin..date_end).to_a
        y_axis_data = data_generator(user, x_axis_data, column)
        chart_data = { labels: x_axis_data, datasets: y_axis_data}
      end

      def data_generator(user, x_axis_data, column)
        x_axis_data.map do |date|
        user.diaries.where(diary_date: date).pluck(column)[0]
      end
    end
  end
end

プロを目指す人のためのRuby入門

  • モジュールについてもっと詳しく

ローカル環境からCloud9環境への移行作業

アプリ制作

PCを修理に出すになったため、ローカル環境からCloud9にRailsアプリを移す作業を行なった。

などをエラーにぶつかりながら、3時間ほどかけて行なった。結果、Railsアプリにアクセスできた。

プロを目指す人のためのRuby入門

  • Module
    • include, extend
    • Enummerable, Compalableモジュール
    • Kernelモジュール

(Rails)前後の期間のリンクを生成するコードを短く書く

アプリ制作

実現したいこと

体調グラフ機能 

StatisticsControllerに:date_beginまたは:date_end,:period`を受け取り、始まりからperiodを引いた期間又は終わりからperiodを足した期間で情報を取得して、どちらかを返り値で返すメソッドを作る。

考えたこと

例えば、9月1日ー9月30日のグラフを描画する。そこから前月(8月1日ー8月31日)次月(10月1日ー10月31日)に飛びたい。

#前月の始まり, 終わり
ex_begin 
ex_end

# 表示している月の始まり, 終わり
date_begin
date_end

#次月の始まり, 終わり
next_begin
next_end

表示する期間が週/月で2パターンある。それぞれ条件分岐をすると長くなってしまうので、

date_beginまたは date_endのどちらかと periodを受け取り、ex_beginまたはnext_endを返すメソッドをコントローラに用意したい。

#app/controllers/statistics_controller.rb
class StatisticsController < ApplicationController

  #省略
  
  def report
    # クエリパラメータを@reportにセット(Structを使用)
    @report.date_begin = #Dateオブジェクト 2019-9-1
    @report.date_end = #Dateオブジェクト 2019-9-30
    @report.period = :months
    # 省略
    @ex_begin = to_ex_or_next_period(@report.date_begin, @report.period)
    @next_end = to_ex_or_next_period(@report.date_end, @report.period)
  end 

  private
    
  # date_beginまたはdate_endを受け取ってex_beginまたはnext_endを返す
  def to_ex_or_next_period(date, period)
    if (date.cwday == 7) || (date == date.beginning_of_month )
      date.advance(period => -1)
    elsif (date.cwday == 1) || (date == date.end_of_month )
      date.advance(period => 1) #ここがエラーの原因
    else
      raise "pass valid argument"
    end
  end
end 
<!-- app/views/statistics/report.html.erb -->
<%= link_to "前月", statistics_path(column: @report.column, date_begin: @ex_begin, date_end: @report.date_begin - 1, period: @report.period) %>

<p><%= @report.date_begin %>~<%= @report.date_end %></p>

<%= link_to "次月", statistics_path(column: @report.column, date_begin: @report.date_end + 1, date_end: @next_end, period: @report.period) %>

エラーと課題

次月(10月1日ー10月31日)へのリンクをクリックすると、クエリパラメータのdate_endが2019-10-30(月末は31日)となってしまい、raise "pass valid argument"が発動してしまう。

週の終わりまたは月の終わりを返さないといけないが、date.advance(months: 1)ではそれができない。

periodが:weeks, :monthsで条件分岐するとコード長くなってしまう。

プロを目指す人のためのRuby入門

  • include, exclude
  • SingletonMethod
  • DackTyping
  • 静的型付け言語とRuby(動的型付け言語)の違い

(Rails/Chart.js)前後の期間へのリンク生成 - 体調グラフ機能


アプリ制作

実現したいこと

体調グラフ機能

  • クエリパラメータでカラム・期間を指定してグラフを描画する

  • 異なるカラム・前後の期間へのリンクを生成する。

課題

以下の2つのメソッドを定義する必要があると考えた。

  • reportインスタンスdate_begin, date_end, periodを受け取って、前後の期間へのクエリパラメータを返すメソッド
  • date_begin, date_end, colmunを受け取って同時期の別カラムへのクエリパラメータを返すメソッド

ここまで考えたところで、メンターに質問して、並行して自分で進める。

実装 前後の期間へのリンク生成

  • URIでperiod=monthと指定しても、@report.period = :weekになる
    • periodとdate_begin, date_endの差を検証していない=> 後に回そう
    • StrongParameterでperiodパラメータがpermitされていなかった。
  • 以下のように実装し、一応動いた。
# app/helpers/statistics_helper.rb
def previous_period_generator(report)
    date_begin_and_end = []
    if report.period == :month
      date_begin = report.date_begin << 1
      date_end = report.date_end << 1
    elsif report.period == :week
      date_begin = report.date_begin - 1.week
      date_end = report.date_end - 1.week
    else
      date_begin = report.date_begin - 1.week
      date_end = report.date_end - 1.week
    end
    date_begin_and_end << date_begin
    date_begin_and_end << date_end
    date_begin_and_end
end

def next_period_generator(report)
    date_begin_and_end = []
    if report.period == :month
      date_begin = report.date_begin << -1
      date_end = report.date_end << -1
    elsif report.period == :week
      date_begin = report.date_begin + 1.week
      date_end = report.date_end + 1.week
    else
      date_begin = report.date_begin + 1.week
      date_end = report.date_end + 1.week
    end
    date_begin_and_end << date_begin
    date_begin_and_end << date_end
    date_begin_and_end
  end
# app/views/statistics/report.html.erb
<h3><%= @report.column %></h3>

<% before = previous_period_generator(@report) %>
<%= link_to "前", statistics_path(column: @report.column, date_begin: before[0], date_end: before[1]) %>
<p><%= @report.date_begin %>~<%= @report.date_end %></p>
<% after = next_period_generator(@report) %>
<%= link_to "次", statistics_path(column: @report.column, date_begin: after[0], date_end: after[1]) %>

Image from Gyazo

プロを目指す人のためのRuby入門

bashの設定

チェリー本の学習記録として写経したコードと主な実行結果をファイルに残している。

実行結果をターミナルからコピペする手間が無駄なので、以下の設定をした。

思ったこと

  • エラーもファイルに出力できるようにしたい
  • テストをかけるようにならないと、動作が正確か自信が持てない。手動のチェックが面倒
  • コードが長い...もっと短くかけるようになりたい...

(rails) 異なるカラム・前後の期間へのリンクを生成をする


アプリ制作

実現したいこと

クエリパラメータでカラム・期間を指定してグラフを描画する

異なるカラム・前後の期間へのリンクを生成する

img

わからないこと

リンクは自前で作成すべきなのか?簡単に済ませられるGemとかあるのか?

Datepickerを使用した方がよいのかと考えて調べすぎてしまった。自前でリンクを生成することにしよう。

- リンクの生成
- 以下の計算をHelperで行い、column, date_begin, date_endのクエリをlink_toに渡す

    - 時間を移動
      - weekly
        - to_next_week
          - @report.column
          - @report.date_begin + 1 week
          - @report.date_end  + 1 week
        - to_week_ago
          - @report.column
          - @report.date_begin - 1 week
          - @report.date_end - 1 week
      - monthly
        - to_next_month
        - to_month_ago

    - カラムを移動
      - @report.columnを指定されたカラムに上書き
      - その場合以下は固定
        - @report.date_begin
        - @report.date_end

プロを目指す人のためのRuby入門

  • 7.6 クラスの継承
  • 7.7 メソッドの公開レベル public, private, protected