プログラミング学習 備忘録

Railsを学習していく上での技術メモ。学んだことや解決したエラーなどを記録していきます。

【決断くん】 決断回数に上限を設ける & 決断回数を視覚化

一日の決断回数に上限を設け、一定数を超えると警告画面に強制的に飛ばされる機能を実装しました。
また、自分のプロフィール画面で決断回数を閲覧できるようにしました。


タイムゾーンの変更

まずは0時ちょうどに決断回数をリセットできるように設定。
今のままではアプリ内時間がUTCになっているので、日本時間のJSTに変更。

下のコードを追記。

# config/application.rb
    config.time_zone = 'Tokyo'
    config.active_record.default_timezone = :local

その日の決断回数を取得するメソッドを定義

Usersモデルに、以下のように定義した。

 def count_today_choices
    from = Time.current.beginning_of_day
    to = Time.current.end_of_day
    self.choices.all.where(created_at: from...to).count
  end


beginning_of_dayで、その日の00:00を取得できる。
end_of_dayで、その日の23:59を取得できる。
そしてwhere(created_at: from...to)で、本日の00:00から23:59までの間に作成されたレコードを全て取得できる。

メソッドを使い決断回数を視覚化し、上限を設ける

あとは先ほど作成したメソッドを使用するだけ。

# users/show/:id内
p #{@user.count_today_choices}


アクセス制限はbefore_actionで実装

class ChoicesController < ApplicationController
  before_action :today_choices_too_many?, only: [:new]
.
.
.
  def today_choices_too_many?
      redirect_to alert_choices_path if current_user.count_today_choices > 10
  end

今までselfを効果的に使えたことがなかったので、今回だいぶ勉強になった。

【決断くん】 細部の調整(バリデーションとかアクセス制限とか)

アプリの基本機能が整ったので、いったん新規実装をストップし、バリデーションやアクセス制限などの細部の調整を行いました。 その時に学んだことや、初めて使用したコードなどを記します。

request.refererでURLの直打ちを無効にする

確認画面を通らないと、決断結果画面を閲覧できないように設定したい。
つまり、URLの直打ちに制限をかけたい。

  def result_throuth_confirm?
    if request.referer.nil?
      redirect_to new_choice_path 
      flash[:danger] = 'お題と選択肢を入力してください'
    end
  end
before_action :result_throuth_confirm?, only: [:result]

request.referer(referrer)で、遷移元urlを絶対パスで取得できる。 これは直接URLを入力した場合はnilになるので、上のコードのように書くことでURLを直打ちをした場合はアクセスできないように設定できる。

ちなみに、URI(request.referer).path相対パスを取得できるので、
unless URI(request.referer).path == confirm_choices_path
と書くこともできる。

before_validatinonコールバック

何気に初めて使用したコールバック。

Choiceモデルに記載し、validationを通る際に@choiceのtitleが未入力の場合になしと自動的に登録されるように設定。

  before_validation :set_choice_title

  private

  def set_choice_title
    self.title = 'なし' if title.blank?
  end

掲示板の匿名機能など、使用する機会は多そう。

【決断くん】確認画面とリザルト画面の実装

今回実装したいこと

1、Sqlを発行せずに、1つ前の選択肢入力画面で入力した情報を遷移先の画面で表示したい。

2、確認画面の抽選ボタンを押した時、2つの選択肢からランダムに1つを選びChoiceテーブルのresultカラムに代入する。 その後、お題、選択肢、リザルトをまとめてChoiceテーブルに登録する。

https://i.gyazo.com/28fd242c1c6976c733431825b24425b6.gif



確認画面の実装

# choices_controller
  def new
    @choice = Choice.new
  end

  def confirm
    @choice = current_user.choices.build(choice_params)
    if @choice.invalid?
      render :new
    end
  end


単純だが、@choice = current_user.choices.build(choice_params)で@choiceに前の画面で入力されたparamを渡すことで@choice.titleなどが機能するようにした。

# /choices/new
  .ui.form
    = form_with model: @choice, url: confirm_choices_path, local: true do |f|
      .field  
        = f.label :お題
        = f.text_field :title
      .field
        = f.label :1つ目の選択肢
        = f.text_field :option_1
      .field 
        = f.label :2つ目の選択肢
        = f.text_field :option_2
      
        = f.submit '確認する', class: 'ui secondary button'


form_withで url: confirm_choices_pathで情報を渡す先を指定するのがポイント。
参考にしたURL
【Rails】インスタンスの状態を保ちながら入力 → 確認画面 → 保存 を実装する方法 - Qiita


リザルト画面の実装

# choice_controller内
  def create
    @choice = current_user.choices.build(choice_params)
     # 空の文字列をoptionsとして定義
    options = []
  # 文字列optionsに、pushメソッドで受け取った選択肢を追加
    options.push(@choice.option_1, @choice.option_2)
     # sampleメソッドによって文字列内からランダムに1つを取得し、resultカラムに格納。
    @choice.result = options.sample
    if @choice.save
      redirect_to "/choices/result/#{@choice.id}" 
    else
      render :new
    end
  end

  def result
    @choice = Choice.find(params[:id])
  end


# routes.rb
get '/choices/result/:id', to: 'choices#result'


結構簡単に実装できた。
最初はoptions << @choice.option_1のように<<を使っていたが、 これだと同時に2つの要素を登録できずスマートではなかったので、pushに変更。
ルーティングについては最初

# routes.rb
  resources :choices, only: %i[new create edit update] do
    collection do
        get :result
    end
  end

としていたのだが、これだとparams[:id]が取得できず、choiceテーブルからレコードを引っ張ってくるのが難しかったので変更し、慣れたやり方で実装することにした。

エラー

確認画面をリロードした際、リクエストがgetになってしまいno route matchエラーが出る

どうやらturbolinksのせいらしい。 Application.jsからrequire(“turbolinks”).start()を削除したらpostリクエストが送られるようになった!
参考にしたURL
【Rails】POST後の画面をリロードしたのにURLをGETしてしまうときの対処法 - Qiita

【ポートフォリオ】ズバッと決断くん 初期設定してみた 

昨日から実装を開始したのだが、最初に入れるべきgemを入れてなかったりデータベースをsqlite3から変えてなかったりでガバガバだったので、とりあえず入れてみた。

開発途中でデータベースをmysqlに変更する

参考url
初学者既存アプリのDBをMySQLに変更する方法 - Qiita

※パスワードは必ず設定すること。設定しないとrails db:createでエラーが出る。 ※Gem: mysql2はバージョン指定をしなくても自分の場合はエラーは出ずに動いた。

rubocopを導入する

参考url
【Rails】RuboCop 導入編(2020年10月版) - Qiita

おなじみのrubocop。rubicon.yml内で、設定を甘くしたり特定のファイルを無視したりもできる。 おすすめの設定がたくさん公開されているのでそちらを使わせてもらうといいかも。

rails-best-practicesを導入する

参考url
素敵なgem、rails_best_practicesを導入する - まえとうしろ
Rails Best Practices の警告をちゃんと考える - Qiita

Rubocopと少し似ているが、rubocopが主にコードの表面をチェックしてくれるのに対し、こちらは実装の中身がrailsの規約に沿っているかどうかもチェックしてくれる。

better_errorsとbinding_of_callerを導入する

参考url
【Rails】better_errorsとbinding_of_callerで自分でエラーを解決できるようになろう【初心者向け】 - Qiita
エラー画面をとっっても見やすくしてくれるgem。 教材のアプリのエラー画面やたらお洒落やなーって思ってたけど今になって原因判明。

slim-railsとhtml2slimを導入する

最初は普通にerbで実装しようと思ったのだが、現場ではslimを使っているところが多いらしいので勉強も兼ねてslimで最後まで実装することにした。 使いこなせばコーディング早くなりそう。
参考url
railsにslimを導入し、erbファイルをslimファイルに一括変更する方法 - Qiita

【ポートフォリオ】ズバッと決断くん ヘッダー、フッター、静的ページを作成

とりあえず、ヘッダーとフッター及び、静的コンテンツを完成させてみました。 一からアプリを立ち上げるのは久々だったので、rails newrails g controllerをするだけでも抵抗を感じました。

f:id:yukimura907:20210106130441p:plain
決断くん トップページ

semantic UIのデザインは、やっぱりbootstrapよりも自分好み。
ただ、ウリである直感的なclass名指定は、確かにわかりやすいものの少しコードが長くなりがちなのが難点かなと思いました。
あとは、グリッドを指定するときにthreeとかfourではなくて「3」とか「4」を使えたら便利だなと感じました。
しかし、感触は良好なのでこのまま使っていくことに。
以下学んだ点



semantic UIの導入方法


とりあえず挙動を確かめたかったので、gemやファイルはダウンロードせずに、CDNを使ってサクッと導入。
導入方法は至ってシンプルで、application.html.erbにコードを加えるだけ!

<!DOCTYPE html>
<html>
  <head>
    <title>KetsudannKun</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>
<!-- 以下の3行を追記する -->
    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
    
    <script src="https://code.jquery.com/jquery-3.1.1.min.js" crossorigin="anonymous"></script>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css">
    <script src="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.js"></script>
<!-- ここまで! -->
  </head>
  <body>
    <%= render 'shared/header' %>
    <%= yield %>
    <%= render 'shared/footer' %>
  </body>
</html>


これだけで適用されるようになる。
欠点としては、cssファイルやJSファイルをダウンロードせずにクラウド上から引っ張ってきているので、少し柔軟性に欠けること?
ストレスなく使いたい場合は、公式HPから直接ダウンロードしてアプリに組み込むのが良さそう。

試しにApplication.css

body {
  font-family: sans-serif;
  background-color: black;
}

と記載してもsemantic.min.cssが優先されて適用されずだった。

画面遷移ボタンの作成


何気に今回初めて作ったのが、単純に画面を遷移させるだけのボタン。
よく考えたら今まではデータを送信するボタンしか作っていなかった。
コードは簡単。

    <button type='button' onclick="location.href='#'" class="ui secondary button">
      <i class='icon user plus'></i>
      新規登録して決断!
    </button>


これだけでsemantic UIが適用されたアイコン付きの立派なボタンが完成した。
概要ページやプライバシーポリシーの文言を完成させるのは後回しにして、次はsorceryを使ったログイン機能を実装していこうと思う。
久々に使うから詰まらないか心配。

【ポートフォリオ】ズバッと決断くん

あけましておめでとうございます

プログラミングスクール合同ポートフォリオコンテストが迫ってきたので、 最近まで行っていたrailsの既存のコードの修正や、RSpecの勉強をいったん中断してポートフォリオ作成に取り掛かることにしました。
自分で一からサービスを作り上げるのは初めてなので不安がありますが、その過程は今までの勉強のいい復習になると思いますし、readmeを書くコツや、画面遷移図、URLの選定、データベース設計、heroku以外のデプロイ方法など、学ぶことも多いと思うので頑張りたいと思います。
それに伴い、しばらくはポートフォリオ関連のアウトプットをしていきます。



アプリの名前は、「ズバッと決断くん」に決定。 自分が優柔不断な性格で、なかなか決断を下すことができないのでその決断を支援するアプリを作成してみることに。
README
ketsudann_kun/README.md at main · yukimura907/ketsudann_kun · GitHub

画面遷移図
https://xd.adobe.com/view/31b5853a-9e52-4720-baa9-73bd3e0b2e7d-64e6/

簡単なアプリケーションなので、経験者はVue.jsとFirebaseの組み合わせで、フロントに動きを付けつつさくっと作ることができるのかもしれない。
しかし自分は当然そんな芸当はできないので、復習も兼ねてrailsで実装しようと思う。

デザインのフレームワークは、当初はbootstrapにしようと思ったのだが、それだとあまりにも味気ない気がしたのでbootstrapよりもデザインが好みだったsemantic UIを使用してみることにした。

合わせて、要所要所のフロントの動きはVue.jsで付けようと思ったのだが、semantic UIのデフォルトがjqueryなので悩み中。Vue.jsを使う理由ってただ単に触ってみたいからってだけなので、今回は使わないでいいかな〜?

12/18 本日学んだこと(パンくず、nilガード)

パンくずについて

パンくずとは自分がwebサイトのどの場所にいるのかを階層構造で表示したもの。 ユーザビリティの向上やseo対策にもなる。 Railsでは、Gem:gretelを使うことで簡単に実装できる。

参考url 【Rails】gretelを使ってパンくずリストを追加してみる。 - Qiita

nilガード

nilを防ぐための書き方はいくつかある

body.to_s body ||= ''

2番目の||=の方がスマートであり、空文字を明示しているので推奨されている。

RSpec

要素のテストについて

当初の自分のコード

 it 'タグ一覧画面に Home > タグ というパンくずが表示されている' do
      visit admin_tags_path
        expect(page).to have_selector '.breadcrumb', text: 'Home'
      expect(page).to have_selector '.breadcrumb', text: 'タグ'
  end

下のコードでも実装できる。

 fit 'タグ一覧画面に Home > タグ というパンくずが表示されている' do
      visit admin_tags_path
      within('.breadcrumb') do
        expect(page).to have_content 'Home'
        expect(page).to have_content 'タグ'
      end
    end

Withinでそのセレクタの中の要素を色々検証できるのでこっちの方がDRYで汎用性も高そう。

    it 'Home のパンくずをクリックした時にダッシュボード画面に遷移すること' do
      visit admin_tags_path
      within('.breadcrumb') do
        click_link 'Home'
      end
      expect(current_path).to eq(admin_dashboard_path)
    end

このようにその中のリンクをクリックする時にも使える。 Homeリンクが同じページ内に2つ以上ある時などに有効。