(Chart.js/Rails) クエリパラメータで期間を指定してグラフを描画する

アプリ制作

実現したいこと

体調グラフ機能

コントローラでクエリパラメータを受け取り、@reportを生成する

Viewで@reportをもとにグラフを描画する

実装したもの

QueryParameterをpermitするメソッド

paramsのdate_begin, date_endをDateオブジェクトにparseするメソッド

# app/controllers/concerns/parse_date_params.rb
# application_controller.rbにinclude

require 'active_support/concern'

module ValidateDateParams
  extend ActiveSupport::Concern

  def valid_date?(str, format="%Y-%m-%d")
    Date.strptime(str,format) rescue false
  end
end

module ParseDateParams
  extend ActiveSupport::Concern
  include ValidateDateParams

  def parse_date_params(params_hash)
    params_hash.each do |key, value|
      if valid_date?(value)
        params_hash[key] = Date.strptime(value, '%Y-%m-%d')
      end
    end
  end
end

クエリパラメータで時期を指定してグラフを描画することができた。

/statistics?column=activity&date_begin=2019-09-22&date_end=2019-09-28

Image from Gyazo

わからないこと

QueryParameterに不備があった時のエラーの出し方?

(Rails)コントローラでクエリパラメータを取得できない

アプリ制作

実現したいこと

  • コントローラでクエリパラメータを取得したい

つまづいたところと解決法

  • コントローラでbinding.pryを起動してpramasにアクセスしようとしたが、NameError: undefined local variable or method `params'と出てしまう。
  • クエリパラメータの書き方におかしいところがないか、クエリパラメータへのアクセスの仕方を英語で検索し、記事を4つほど読み込んだ。
  • ふとbinding.pryの位置がおかしいのかもと頭に浮かび、試してみたところ解決した.。
# リクエスト
/statistics?column=activity&date_begin=2019-09-12&date_end=2019-09-21

# routing
get 'statistics' to: statistics#report

# app/controllers/statistics_controller
class StatisticsController < ApplicationController
  #省略
  binding.pry
  
    #省略
  def report
  #省略
  end
end 

# paramsを参照できない

[1] pry(StatisticsController)> params 
NameError: undefined local variable or method `params' for        StatisticsController:Classfrom (pry):1:in `<class:StatisticsController>'
  • よくよく見るとデバッガの位置を間違えていた。

    • ルーティングで`statistics => statistics#reportなので、reportメソッドの名前空間からでないとparamsにアクセスできない?
    • ここらへんの仕組みが理解できていない。
# app/controllers/statistics_controller
;
(省略)
def report
  binding.pry
  #省略
end 

# paramsを取得できた
[1] pry(#<StatisticsController>)> params
=> <ActionController::Parameters {"column"=>"activity", "date_begin"=>"2019-09-12", "date_end"=>"2019-09-21", "controller"=>"statistics", "action"=>"report"} permitted: false>

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

  • 7章クラスの作成を理解する
    • 7.1 イントロダクション
    • 7.2 オブジェクト指向プログラミングの基礎知識
    • 7.3 クラスの定義
    • 7.4 例題: 改札機プログラムの作成

Struct.newでWeeklyreportオブジェクトを作成してViewに渡し、週間テーブルを描画する

アプリ制作

実現したいこと

StatisticコントローラでDiaryの週間集計をする。

週間の各コンディションの値を下のようなテーブルにしたい。

そして配列にしてChart.jsに渡したい。

From: September 15

To: September 21

Image from Gyazo

どうすればこのテーブルを作れるのか?

  1. Statistic Pathのリンクをクリック
  2. Date.todayから週の初めの日、週の終わりの日を算出する
  3. Range(週の初め..週の終わり).to_a でlabel(グラフの横軸)が出来上がる。
  4. labelとカラムから各カラムの値の配列を返す
    1. 戻り値用の配列を初期化
    2. labelの配列とカラムを引数にとる
    3. labelから日付を1つずつ取り出し、その日のDiaryレコードを検索
    4. Diary.カラムで値を取り出して戻り値用の配列につっこむ
  5. labelと各カラムの配列を返す

StatisticControllerの実装

class StatisticsController < ApplicationController
  before_action :require_user_logged_in, :set_current_user

  WeeklyReport = Struct.new(
  :date_from,
  :date_to,
  :labels,
  :activity_array,
  :mood_array,
  :appetite_array
)

  def index
    activity = 'activity'
    @weekly_report = WeeklyReport.new.tap do |r|
                      r.date_from = Date.today.beginning_of_week
                      r.date_to   = Date.today.end_of_week
                      r.labels    = week_dates
                      r.activity_array = array_generator(week_dates, :activity)
                      r.mood_array = array_generator(week_dates, :mood)
                      r.appetite_array = array_generator(week_dates, :appetite)
                    end
  end

  private

  def week_dates
    today = Date.today
    (today.beginning_of_week..today.end_of_week).to_a
  end

  def array_generator(week_dates, colmun)
    week_dates.map do |date|
      current_user.diaries.where(diary_date: date).pluck(colmun)[0]
    end
  end
end

週間集計のテーブルを描画できた。 次は、これをChart.jsに渡してグラフにする。

Image from Gyazo

分からないところ

  • tapメソッド
# tapメソッドは、ブロック変数にレシーバ自身を代入してブロックを実行する。
# 戻り値はレシーバ自身

"hello".bytes.to_a.tap {|arr| p arr }

# "hello".bytes.to_a 
# => [104, 101, 108, 108, 111]
# このArrayがブロック引数(|arr|)に入る。 
# そして p arr が実行される。
# 戻り値はレシーバ自身なので、Arrayが返る。

"hello".bytes.to_a.tap {|arr| p arr }.collect {|byte| byte.to_s(16) }.tap {|arr| p arr }

# つまり...
[104, 101, 108, 108, 111].collect {|byte| byte.to_s(16) }.tap {|arr| p arr }

# mapのエイリアスなので、Arrayを一つずつブロック引数に入れて、ブロックを実行
[104, 101, 108, 108, 111].collect {|byte| byte.to_s(16) }
# => ["68", "65", "6c", "6c", "6f"]

# つまり...
["68", "65", "6c", "6c", "6f"].tap {|arr| p arr }

# レシーバがブロック引数に代入され、ブロックが実行される
p ["68", "65", "6c", "6c", "6f"]
# 戻り値はレシーバ自身 => ["68", "65", "6c", "6c", "6f"]

# 1行にできるのはわかったけど、逆に読みにくいのでは?笑

Rubyのレファレンスを読んで意味をつかんだ。

プロを目指す人のRuby入門

正規表現

  • 初心者歓迎!手と目で覚える正規表現入門・その1「さまざまな形式の電話番号を検索しよう」
  • 初心者歓迎!手と目で覚える正規表現入門・その2「微妙な違いを許容しつつ置換しよう」
    • 最後の方の解決策2まで

190918 Statisticコントローラの実装(体調グラフ描画のためのデータ生成)

アプリ制作

体調グラフ機能

  • Diaryのテストデータ生成
    • 直近3ヶ月分を毎日(グラフで可視化するため)
      • rails consoleでテストコード実行
  • Diaryモデルのインスタンスメソッド定義
  • Statisticコントローラ実装のためにあれこれ調べ、試す(未完)
Ruby Date.strftime('')
# %w - Day of the week (Sunday is 0, 0..6)
# %W - Week number of the year.  The week starts with Monday.  (00..53)

# 生成したいグラフ
例)
Week number: 37
↓みたいな

Image from Gyazo

必要なデータ
Year: 何年の?
Week number : 何番目の週?
Intial day of the week: 何日から
Last day of the week: 何日まで

activity : その週の最初の日から終わりの日までのそれぞれの日におけるactivityの値の配列
mood: 上に同じ
appetite: 上に同じ
#Diary.weekで週番号を返すメソッドを定義済み

user = User.last
diaries = user.diaries

diaries.group_by { |item| item.week }
# => { "week_number" => [ diary1, diary2,,,,]}

current_user.entries.group_by(&:week).each do |week, diaries|
end 
# weekに週番号の文字列、diariesにその週のdiaryインスタンスの配列が入っている
# diariesの配列を受け取って、activity, mood, appetiteの値を取り出すメソッドを定義しないといけない。

# メソッドのイメージ
activity = []
mood = []
appetite = []

(週の最初の日..週の最後の日).each do |day|
            activity    << diary.find(day).activity
end 

参考

プロを目指す人のRuby入門

  • ハッシュとシンボルの詳細
  • ||=!!などのイディオムについて

190606 Model作成の続き

作りたいもの

190509 アプリの完成イメージ - エンジニアになりたい日記

190517 日記アプリの要件を再考 - エンジニアになりたい日記


本日やること

Symtom, Memory モデル作成


Rails コンソール上で日本語入力ができない

Rails コンソール上で日本語を入力すると文字化けしてしまう。

Image from Gyazo


下のページのOption4を実行したらうまくいった。

Add Readline support to Ruby on Mac OS X · guard/guard Wiki · GitHub


Image from Gyazo


Model作成

Symptomモデル(症状), Memoryモデル(良かったこと)を作成した。

両方ともUserと1対多の関係。

DiaryモデルとSymptomモデルは多対多の関係。


イデア

Symptomの show ページで症状の発生回数の推移をグラフ表示する機能。

体調の推移が可視化されて、便利かもしれない。


190528 ブラウザは基本、DELETEリクエストを送らない。

作りたいもの

190509 アプリの完成イメージ - エンジニアになりたい日記

190517 日記アプリの要件を再考 - エンジニアになりたい日記


本日やったこと

  • 昨日のエラー解決
  • Diaryモデルの作成


問題

昨日のエラー解決の続き。

DELTEリクエストを送ってsessionを消し、ユーザをログアウトさせたい。

http://localhost:3000/logout

でも、以下のエラーが出る。

Routing Error
No route matches [GET] "/logout"
Rails.root: /Users/bupolang/projects/cocologue


考えたこと

そもそもユーザのログイン/ログアウト機能を実装するために、ログアウトの動作確認をしたい。bootstrapのドロップダウンメニューの中にLogoutボタンがあり、そのボタンからログアウトの動作確認をしようとした。しかし、dropdownが降りてこない。そこでブラウザからlogoutの動作確認をしようとしていた。


デフォルトのブラウザからはDELETEリクエストを送付できない。今回私はブラウザから /logout とリクエストを送れば自動的にDELETE(http verb), /logout(URI)になると思い込んでいた。しかし実際にはGET(http verb), /logout(URI)が送付されていた。なので、No route mathes [GET] /logoutとエラーが発生するのは当然だった。


もともとのdropdownが降りてこない不具合を治せば、ログアウトの動作確認ができる。そこで、そちらを修正することにした。

Rails5.系でbootstrapを有効にする

app/assets/javascript/application.jsを以下のように変更。1

//= require rails-ujs
//= require turbolinks
//= require jquery
//= require activestorage
//= require_tree .


結果、ログアウトできた。わーい😀

Image from Gyazo

Diaryモデル作成

cocologue $ rails g model Diary content:text woke_up_at:datetime slept_at:
datetime activity:integer mood:integer appetite:integer walking:integer us
er:references
      invoke  active_record
      create    db/migrate/20190528092030_create_diaries.rb
      create    app/models/diary.rb
      invoke    test_unit
      create      test/models/diary_test.rb
      create      test/fixtures/diaries.yml


class CreateDiaries < ActiveRecord::Migration[5.2]
  def change
    create_table :diaries do |t|
      t.text :content
      t.datetime :woke_up_at
      t.datetime :slept_at
      t.integer :activity
      t.integer :mood
      t.integer :appetite
      t.integer :walking
      t.references :user, foreign_key: true

      t.timestamps
    end
  end
end


cocologue $ rails db:migrate
== 20190528092030 CreateDiaries: migrating ===================================
=
-- create_table(:diaries)
   -> 0.0102s
== 20190528092030 CreateDiaries: migrated (0.0103s) ==========================
=


次回はモデルのバリデーションから。

190527 ログアウトのDELETEリクエストがエラーになる。

本日やったこと

ログイン機能の作成

問題

ブラウザからログアウトのリクエストを送付するとエラーが出てしまう。

http://localhost:3000/logout
Routing Error
No route matches [GET] "/logout"
Rails.root: /Users/bupolang/projects/cocologue


環境

各種ファイル

config/routes.rb はこのように書いています。   

Rails.application.routes.draw do
  root to: 'toppages#index'

  get 'login', to: 'sessions#new'
  post 'login', to: 'sessions#create'
  delete 'logout', to: 'sessions#destroy'

  get 'signup', to: 'users#new'
  resources :users, only: [:show, :new, :create]
end



app/views/layouts/application.html.erb は以下のように書いています。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>Cocologue</title>
    <%= csrf_meta_tag %>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
  </head>

  <body>
    <%= render 'layouts/navbar' %>

    <div class="container">
      <%= render 'layouts/flash_messages' %>

      <%= yield %>
    </div>
  </body>
</html>



試したこと・考えたこと

ルーティングでdelete 'logout', to: 'sessions#destroy'としているにもかかわらず、送られているリクエストのVerbがGETなのがおかしい。DELETEになるはず。

RailsAPIドキュメントの link_toの欄を参照すると、

「Note that if the user has JavaScript disabled, the request will fall back to using GET.」

とある。DELETEリクエストがGETに変わってしまっているのは、Javascriptが関係しているのかも?

下の記事を参照すると、

同じ問題が起きているユーザに対して「jquery/jquery_ujsなどのライブラリを必要とするビルトインのJavascriptライブラリを有効にしたか?」と聞いている回答者がいた。

No route matches [GET] "/logout" [rails] - Stack Overflow

そこで、Gemfileにgem 'jquery-rails'を追記し、 bundle update したけれど、やはり同じエラー。

今日はここまで。