20200907のRailsに関する記事は20件です。

【Rails】paranoia(gem)を使ってイベント終了機能(論理削除)を実装する

そもそも論理削除とは?

RailsのActiveRecordの削除(delete)は、通常、物理削除になっており、実際にデータがデータベースから削除されます。それに対して、論理削除は実際にはデータを削除せずに、削除されたと見なすフラッグと呼ばれるカラムを設定して、ユーザーには削除しているかのように振る舞い、必要時には元の状態に戻せるものです。「ユーザーが退会した後も、システム上一定期間はユーザーのデータを保持しなければならない。」や「違反等があり、一時的にアカウントを凍結したい」などの場合に、論理削除が使えると思います。
Railsではparanoiaというgemライブラリを利用することで簡単に実装することができます。このgemはacts_as_paranoidというgemを再実装したものです。

引用 (https://remonote.jp/rails-gem-paranoia)

分かりやすい記事があったので引用させて戴きました。通常は上述されている様に管理ユーザーが違反等があり、一時的にアカウントを凍結したいなどの場合に使われる機能みたいですが僕の場合はイベントの削除機能と終了機能を分けるために論理削除を実装しました。

どういうことかと申しますと、イベント参加アプリケーションを実装しているときにユーザーが過去に参加したイベントが見れたらいいよね。となりました。イベントが終われば当然、イベントのデータは削除されてイベント一覧では見れない様にするのですが僕が実装したかったのは一覧には表示されないけどユーザーのマイページで参加したイベントが見れるというもの。つまり表面的には削除されていながらレコードの情報は残っているということです。ですが、どの様に実装したらいいのか分かりませんでした。

最初は、イベントを削除したときに違うテーブルにそのイベントの情報を丸々移動出来ないかと模索していましたがなかなか情報が見つからないときに論理削除が簡単に実装できてしまうparanoiaというgemを見つけました。

前置きが長くなりましたが、早速導入~実装までの流れをまとめていきたいと思います。

step1: 導入

gem 'paranoia'

例の如くbundle install

続いて、カラムを追加します。

rails g migration AddDeletedAtToEvents deleted_at:datetime:index

イベントモデルにdeleted_atというカラムを追加します。

class AddDeletedAtToEvents < ActiveRecord::Migration[6.0]
  def change
    add_column :studies, :deleted_at, :datetime
    add_index :studies, :deleted_at
  end
end
rails db:migrate

最後に、対象モデルに acts_as_paranoid を追記します。

Event.rb
class Event < ApplicationRecord
  acts_as_paranoid
end

導入完了!!

step2: ルーティングを設定する

routes.rb
Rails.application.routes.draw do
  root "events#index"
  resources :events do
    member do
      delete 'finish'
    end
  end
end

resourcesで生成される七つのアクションの中に終了するアクションは無いのでイベントにネストする形でfinishアクションのルーティングを設定します。

step:3 コントローラーにfinishアクションを定義する

events_controller.rb
 def destroy
   @event = current_user.events.find(params[:id])
   @event.really_destroy!
   redirect_to root_path, notice: "イベントを削除しました"
 end

 def finish
   @event = current_user.events.find(params[:id])
   @event.destroy!
   redirect_to root_path, notice: "イベントを終了しました"
  end

上記のdestroyアクションとfinishアクションの違いは@eventの後に、本当にレコードから削除したい場合はreally_destroyと記述しています。
では、finishアクションでは普通にdestroyと記述していますが実際データーベースにどの様な形で残っているのでしょうか?

746eace97e67b286975d2e087964212e.png
destroyされたレコードは画像の様にdeleted_atというカラムに論理削除された時間が残ります。

step:4 論理削除されたデータをfindメソッドで取ってくる

users_controller.rb
  def show
     @user=User.find(params[:id])
     @events = @user.events.only_deleted
  end

ユーザーの詳細ページで過去のイベントを表示したいのでユーザーコントローラーにonly_deletedと記述することで論理削除されたレコードだけを取ってくることができます。

最後に、、

paranoiaは他にも色々なメソッドが使えるみたいなので是非、公式ドキュメントを参考にしてください^^
公式リンク(https://github.com/rubysherpas/paranoia)

最後まで見て頂きありがとうございました^^

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

windows10,Ruby2.6.6でrails serverコマンドを実行して「cannot load such file -- sqlite3/sqlite3_native」がでた

はじめに

@shuheyさんの記事の通りにやればほぼ解決します.
Ruby2.6.6でやったときにうまくいかなかったのでメモ程度に...

環境

windows10 Home
Ruby+Devkit 2.6.6-1 (x64)
Rails 5.1.6

直したはずなのにエラーが

Ruby on RailsチュートリアルRails Girls インストール・レシピ (Windows 用セットアップ(WSLが使えない方向け))の通りに環境構築をしてrails serverコマンドを実行したところ以下のようなエラーが出ました.(@shuheyさんの記事ではRuby26-x64 -> Ruby25-x642.6.0 -> 2.5.0に変わってます)

C:/Ruby26-x64/lib/ruby/gems/2.6.0/gems/sqlite3-1.3.13-x64-mingw32/lib/sqlite3.rb:6:in `require': cannot load such file -- sqlite3/sqlite3_native (LoadError)

ここで@shuheyさんの記事に倣ってC:\Ruby26-x64\lib\ruby\gems\2.6.0\gems\sqlite3-1.3.13-x64-mingw32\lib\sqlite32.5ディレクトリを新規作成し,その中にsqlite3_native.soをコピーしてもう一度rails serverコマンドを実行!

これで解決かと思いきやまだ同じエラーが...

解決方法

さっきのエラーメッセージのC:/Ruby26-x64/lib/ruby/gems/2.6.0/gems/sqlite3-1.3.13-x64-mingw32/lib/sqlite3.rbを見てみる.すると3,4行目に

sqlite3.rb
  RUBY_VERSION =~ /(\d+\.\d+)/
  require "sqlite3/#{$1}/sqlite3_native"

なるほど.
2.5ディレクトリはRubyのバージョンのことだったのか!(なんで気づけなかった笑)

というわけで2.6ディレクトリを新規作成し,sqlite3_native.soをコピーしてrails serverコマンドを実行します.
やっとRailsアプリの起動に成功.

=> Booting Puma
=> Rails 5.1.6 application starting in development
=> Run `rails server -h` for more startup options
*** SIGUSR2 not implemented, signal based restart unavailable!
*** SIGUSR1 not implemented, signal based restart unavailable!
*** SIGHUP not implemented, signal based logs reopening unavailable!
Puma starting in single mode...
* Version 3.9.1 (ruby 2.6.6-p146), codename: Private Caller
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop

ブラウザでlocalhost:3000にアクセスして動作確認

キャプチャ.PNG

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

int型カラム登録で大文字の場合でも半角数字にしたい時

今回はオリジナルアプリを作っている際数字で登録するところをうっかり大文字数字で記入しても勝手に半角数字にしちゃうメソッドを作ったのでまとめたいと思います。

投稿の際に使用するテーブルはこんな感じです。

mysql> show columns from sakes;
+-------------+--------------+------+-----+---------+----------------+
| Field       | Type         | Null | Key | Default | Extra          |
+-------------+--------------+------+-----+---------+----------------+
| id          | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| name        | varchar(255) | NO   |     | NULL    |                |
| rice_rate   | varchar(255) | NO   |     | NULL    |                |
| sake_degree | varchar(255) | NO   |     | NULL    |                |
| type_id     | int(11)      | NO   |     | NULL    |                |
| degree      | int(11)      | NO   |     | NULL    |                |
| company     | varchar(255) | NO   |     | NULL    |                |
| rice        | varchar(255) | YES  |     | NULL    |                |
| user_id     | bigint(20)   | NO   | MUL | NULL    |                |
| created_at  | datetime(6)  | NO   |     | NULL    |                |
| updated_at  | datetime(6)  | NO   |     | NULL    |                |
+-------------+--------------+------+-----+---------+----------------+
11 rows in set (0.01 sec)

この中にdegree(アルコール度数)の部分を投稿の際i int型にするためにモデルにメソッドを作ります。

sake.rb
def degree=(value)
    value.tr!('0-9', '0-9') if value.is_a?(String)
    super(value)
  end

まず最初のdegree=(value)はメソッドの定義で何か代入しているように見えるがdefree= と言うメソッドに仮引数(value)が設定されている形となる。

まず大文字でも胃が入力された時はstringになるのでis_a?メソッドが真の場合処理が行われます。

trメソッドの公式リファレンスを見てみると、、

pattern 文字列に含まれる文字を検索し、それを replace 文字列の対応する文字に置き換えます。

例はこんな感じです。

p "foo".tr("f", "X")      # => "Xoo"
p "foo".tr('a-z', 'A-Z')  # => "FOO"
p "FOO".tr('A-Z', 'a-z')  # => "foo"

# シーザー暗号の復号
p "ORYV".tr("A-Z", "D-ZA-C") # => "RUBY"

# 全角英数字といくつかの記号の半角化
email = "ruby−lang@example.com"
p email.tr("0-9a-zA-Z.@−", "0-9a-zA-Z.@-")
# => "ruby-lang@example.com"


ざっくり言うと第一引数の内容を第二引数に変更するといった感じです。

なのでtrメソッドで内容を変更しています。
これで変わると思ったがエラー。。。

super(value)の記述を付け加えて無事できました。

今までなんとなくで触っていましたがカラムはメソッドとして使用できるということをわかっていませんでした。

最後の super(value)の親クラスはActiveRecord::Baseとなります。

ここらへんのことはまだふわっとしているので次の記事でまとめたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

git push heroku masterする際のエラー解消方法

【概要】

1.結論

2.どのようなエラーか

3.なぜエラーが起きるのか

4.どのように解決するのか

1.結論

githubの左上メニューでCompare to Blanchを押してmasterにmargeする!

2.どのようなエラーか

ターミナル
%  git push heroku master
To https://git.heroku.com/*****.git
 ! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to 'https://git.heroku.com/*****.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

このようなエラーが出てしまいました。
ローカルレポジトリ(master)からherokuへのmasterにpushしようとした際に拒否されてしまったエラーです。
hintが書いてありますが全く解決できずで、調べてもなかなか解決には至りませんでした。


3.なぜエラーが起きるのか

masterとローカルレポジトリにあるブランチが反映されていない状態になっており食違いがあったことが原因でした。

4.どのように解決するのか

なのでgithubの左上メニューでCompare to Blanchを押してmasterにmargeしたところ、エラーは無事に解決しました。

❶github desktopを開きますとMacですと左上にメニューバーが出てきます。そこで"Branch"を押下します。
スクリーンショット 2020-09-07 21.47.57.png

❷そしてCompare to Blanchを押します。
スクリーンショット 2020-09-07 21.48.06.png

❸そしてgithub desktopの画面に戻ってhistoryを見ていきます。

❹”1(1以上になっている)⬇︎”になっていると思うのでそれをmasterに統合します。
スクリーンショット 2020-09-07 21.48.35.png
写真では0⬇︎になっていますが、エラーが起きていた際は1⬇︎になっていました。その証拠に隣が1⬆︎になっています。
そうすることで再度、ターミナルで” git push heroku master”してあげるとうまく行きました。

そこで、いろいろと思い返しました。
Pay.jpでjavascriptに環境変数を記載して、herokuにpushする際にわざとプログラムを変更して読み込ませるようにしました。変更を加えた際に、ブランチを作ったような気がしました。やはり気のせいではなくブランチを作ってありました。


  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Webアプリケーションにおけるセキュリティ

セキュリティ対策をしていないwebアプリケーションの開発は非常に危険です。
セキュリティとは何なのか。
セキュリティ対策をしていないと
・なぜ危険なのか
・どういった事が起こるのか
・どのように対策するのか
について学んだ内容をまとめました。(Rubyでの開発)

前提
プログラミング初学者(1~2ヶ月)が学んだ内容です。
実際の現場で通用しないことや間違った内容が含まれている可能性があります。
間違っている部分や浅い部分については、追記やご指摘いただけると幸いです。

セキュリティ対策していないとなぜ危険なのか

セキュリティ対策をしていないwebサービスはなぜ危険なのか、何が起こるのか。
結論
利用者は個人情報が漏れる。
管理者はwebページを勝手にいじられる。

もう少し細かく見ると、
・個人情報を勝手に閲覧される
・webページの内容が改ざんされる
・webページ自体が利用不可能になる
その結果
・webサービス利用者へ大きな損害
・公開したwebページの社会的信用を失う

webサービスにおけるセキュリティとは何か

脆弱性があるwebサービスはセキュリティが低いと言えます。

脆弱性

脆弱性とはセキュリティホールとも呼ばれ、ソフトウェアの起こりうる欠陥や使用上の問題点のこと。
悪意のある人に攻撃を仕掛けられる際の弱点といえます。
この脆弱性があると開発者利用者どちらにも様々な被害が生じます。

セキュリティ攻撃の種類

・JavaScriptによるセキュリティ攻撃
・セッションを用いた攻撃
・不正なリクエストを行う攻撃
・SQLによる攻撃
攻撃というのは個人情報を抜き取る行為と思って下さい。

JavaScriptによる攻撃

JavaScriptはクライアントサイドで動くプログラミング言語です。
何らかの方法で悪意あるJavaScriptを埋め込めば、その埋め込んだページを表示した時にユーザーにそのプログラムを実行させることができます。
このような攻撃方法をXSS(クロスサイトスクリプティング)と呼びます。
スクリプトを埋め込んで実行させることをスクリプティングといいます。
その内容は個人情報を悪意ある人のサーバーへ送付するものが多いようです。

JavaScriptによる攻撃

JavaScriptはクライアントサイドで動くプログラミング言語
何らかの方法で悪意のあるJavaScriptをユーザーがwebアプリケーションを表示したブラウザに埋め込めば、ユーザーにそのプログラムを実行させることができる。
このような攻撃をXSSという

XSS(クロスサイトスクリプティング)

攻撃者が脆弱性のあるwebアプリケーション上に悪意のあるJavaScriptのプログラム(スクリプト)を埋め込み、そのサイトの利用者を攻撃する手法
悪意あるスクリプトをwebアプリケーションを表示した時に実行させることをゴールとした手法がXSS
スクリプトを埋め込んで実行させることを「スクリプティング」という。
内容は、ユーザーの個人情報などを悪意ある人のサーバーなどへ送付するスクリプトが多い。

XSSの攻撃パターン

XSSの攻撃パターンには反射型XSSと格納型XSSの2タイプがあります。

反射型XSS

ユーザーが悪意のあるURLをクリックすると、脆弱性のあるwebアプリケーション上でスクリプトが実行されるようにする手法
手順
①ユーザーにURLをクリックさせる
攻撃者が、ユーザーに悪意のあるURL(脆弱性のあるwebアプリケーションへ遷移するURLに細工をしたもの)をクリックさせる。
→脆弱性のあるアプリケーションは攻撃者のもと?それとも全く関係ないサイト?
前者なら納得、後者なら後者のサイトはセキュリティが弱くて利用されているだけ?
②ユーザーがクリックすると、脆弱性のあるwebアプリケーション上でスクリプトが動く
悪意のあるURLでは脆弱性のあるwebアプリケーション上で発火するスクリプトが仕込まれている。そのため、脆弱性のあるwebアプリを開くとスクリプトが発火する。
③スクリプトが発火すると、攻撃者のサーバーへ個人情報が送られる
発火(発動)したスクリプトは個人情報を抜き取るようなものでした。
クリックしたユーザーの個人情報が攻撃者のサーバーへ記録されてしまいます。

このようにURLをクリックしてレスポンスがあると即座にスクリプトが実行されることから反射型と呼ばれる。
別サイト(メールなども含む)から脆弱性のあるwebアプリケーションへ遷移した後にスクリプトが実行される。
よくチャットアプリなんかでURLが貼られると、そのページをクリックする前に
安全なサイトですか?
といった確認が出ると思います。
この反射型XSSを防ぐために、表示させるようにしているのだと思います。
これがクロスサイトスクリプティングのクロスサイト(webサイト間をまたぐ)を意味しています。

格納型XSS

脆弱性のあるwebアプリケーションの投稿などに悪意のあるスクリプトを埋め込み、ユーザーがそのwebアプリケーションを訪れた時にスクリプトが発火するような攻撃手法
例えば、twitterの投稿で悪意のあるスクリプトを投稿します。
その投稿が見られるページに遷移した途端スクリプトが発火します。
その投稿をみようとしなくても、その投稿があるページを開いた瞬間攻撃されます。
手順
①攻撃者が、脆弱性のあるwebアプリケーションにスクリプト付きの投稿をする
フォームからコンテンツを投稿する際にスクリプトを埋め込んで投稿する。
②ユーザーがその投稿のあるページを訪れるとスクリプトが発火する。
スクリプトが埋め込まれた投稿ページを開くとそのスクリプトが発火する。
画像にマウスを合わせるとスクリプトが発火するような仕掛けになっていることもある。
③スクリプトが発火すると攻撃者のサーバーへ個人情報が送られる。
脆弱性のあるwebアプリケーションにスクリプトを格納し、ユーザーが訪れたタイミングで発火するようになっていることから格納型と呼ばれる。

XSSの対処

XSSは現在でも多く事例が報告されている。
脆弱性をつく攻撃の被害はXSSに起因するものが最も多くの割合を占めている。

対処方法

XSSが発生する主要因として、フォームから入力されたスクリプトタグ()などがスクリプトとしてそのままブラウザに反映されてしまっていることがあげられる。
XSSを防ぐためにHTMLにおいて意味を持つ"や<を何らかの別の文字列に変換する。この変換の際に使われる記法を文字参照という。

文字参照

ブラウザ上で表示できない特殊文字を表記する際に用いられる記法。

変換前 変換後
< & It;
> & gt;
& & amp;
" & quot;
' & #39;

このようにプログラムを記述している時に使っている記号を特殊文字に変換すると、外部から埋め込まれたスクリプトが実行されにくくなります。

Ruby on Railsにおける対処法

ヘルパーメソッド<% %>でXSSの対策を行うことができる。
この中に書かれた記述は文字列として表示されるため、プログラムの記述にはならない。
このプログラムとしてではなく、文字列として特殊文字を表示することをエスケープ?というようです。

まとめ

反射型
特定のURLにアクセスすることででスクリプトを発火させる。
格納型
投稿自体をみるとスクリプトが発火するので、普通のサイトで悪意のあるユーザーの投稿にスクリプトが埋め込まれていて、それがあるページにあくせすしてしまうと発火する。
Rubyの場合はRuby on Railsのヘルパーメソッドで回避できる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CircleCI: parallelismを使うときにSimpleCovの結果をまとめ、Artifactsに置く

CircleCIでparallelismを使ってRspecを並列実行すると、SimpleCovの結果がそれぞれのcoverageディレクトリにばらばらに出力されてしまいます。これを1つにまとめ、さらにCircleCI上で結果のHTMLが見えるようにしたい。

image.png

参考にしたページ:

複数のジョブのテスト結果を1つのディレクトリにコピー

SimpleCovの結果は、coverageディレクトリの下のJSONファイル .resultset.jsonに 出力されます。以下の「Copy coverage results」のように、Rspecの実行後にJSONファイルを /tmp/coverage/.resultset-番号.json へコピーします。

さらにpersist_to_workspaceを使うと、/tmp/coverage 下のファイルをジョブ間で共有できます。RSpecの実行が終わるたびに、.resultset-番号.json がこのディレクトリに溜まっていきます。

なお、/tmp/coverage というディレクトリ名はてきとうです。

.circleci/config.yml
  test:
    parallelism: 4
# 略
    steps:
# 略
      - run:
          name: rspec
          command: |
            bundle exec rspec --format RspecJunitFormatter \
                              --out test_results/rspec.xml \
                              --format documentation \
                              $(circleci tests glob "spec/**/*_spec.rb" | circleci tests split)
      - store_test_results:
          path: test_results

      - run:
          name: Copy coverage results
          command: |
            mkdir /tmp/coverage
            cp coverage/.resultset.json "/tmp/coverage/.resultset-${CIRCLE_NODE_INDEX}.json"
      - persist_to_workspace:
          root: /tmp/coverage
          paths:
            - .resultset-*.json

SimpleCovの結果をまとめるタスク

最近のSimpleCovには、複数のJSONから1つのHTMLを出力する機能ができましたので、これをそのまま使うrakeファイルを書きます。

lib/tasks/coverage_report.rake
namespace :coverage do
  desc "Collates all result sets generated by the different test runners"
  task :report do
    require 'simplecov'

    SimpleCov.collate Dir["/tmp/coverage/.resultset-*.json"], 'rails'
  end
end

並列テストが全部終わったら結果をまとめる

CircleCIのworkflowsの設定を行い、testジョブが全部終わったらreportジョブを走らせるようにします(以下の workflows - build_and_test - jobs - report の部分)。

reportジョブではrakeタスクの coverage:report を動かします。このとき、attach_workspace で 複数のJSONファイルが置かれた /tmp/coverage が使えるようにしておきます。これで、coverage の下にSimpleCovのHTMLができあがります。

さらに、store_artifacts を使うと coverage ディレクトリがCircleCIのArtifactsで一定期間保管され、ブラウザーから閲覧できるようになります。

.circleci/config.yml
jobs:
  build:
# 略
  test:
# 略
  report:
    docker:
      - image: cimg/ruby:2.6.6-node
    steps:
      - checkout
      - ruby/install-deps:
          bundler-version: 2.1.4
      - attach_workspace:
          at: /tmp/coverage
      - run:
          name: Collates all result sets
          command: bundle exec rails coverage:report
      - store_artifacts:
          path: coverage

workflows:
  version: 2
  build_and_test:
    jobs:
      - build
      - test:
          requires:
            - build
      - report:
          requires:
            - test

今後の課題

Rails 6のパラレルテストはRspecで使えるのか、CircleCI上ではどうすべきなのか、調査する。

私のCircleCI記事:

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

railsのハッシュタグ検索の実装

今回は、railsでハッシュタグ機能を実装しました。
https://glodia.jp/blog/3936/
のサイトを参考にさせていただきました。

完成図
商品を出品する時にタグをつけると

スクリーンショット 2020-09-07 18.44.13.png

リンクが踏めるハッシュタグが投稿詳細の際に確認でき、
スクリーンショット 2020-09-07 18.45.37.png

リンクの先には同じタグを持った商品が並ぶ
スクリーンショット 2020-09-07 18.57.37.png

実装方法
1 商品テーブルitemsとタグテーブルtagsは中間テーブルで結びついている
2 ヘルパーに、タグ名をリンクにするための記述をする

items_helper.rb
def render_with_hashtags(tag_name, tag_id)
tag_name.gsub(/[##][\w\p{Han}ぁ-ヶヲ-゚ー]+/){|word| link_to word, "/item/hashtag/#{word.delete("#")}?tag_id=#{tag_id}"}.html_safe
end

第一引数でタグの名前、第二引数で中間テーブルに保存されているタグのidを渡してます。
gsubメソッドでurlに置換しhtml_safeメソッドをつけることでエスケープ処理を避け、ビューに「#名前」の形でリンクが踏めるようになります。
?tag_id=#{tag_id} の記述で、タグのidをリンクの中に仕込む

3 ルーティングの実装

routes.rb
 get '/item/hashtag/:name', to: "items#hashtag"

本来はitemコントローラーの中にネストするのがベストだと思います

4 コントローラーの実装

items_controller.rb
def hashtag
  @tag = Tag.find(params[:tag_id])
  @items = @tag.items
end

2でリンクの中に仕込んだtag_idをparamsで渡し、該当するタグを探す
該当するタグを持つ商品を変数に入れる

5 投稿詳細一覧を作る時と同様、eachメソッドを@itemsを使用してビューを実装

完成です!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails on Dockerの環境構築手順

環境

  • MacOS Catalina 10.15.6
  • Docker 2.3.0.3
  • Ruby 2.7.1
  • Rails 6.0.3.2
  • Bundler 2.1.4
  • Gem 3.1.2
  • Yarn 1.22.5

手順

予めDockerをインストールしておいて下さい。

Rubyイメージの取得

$ docker pull ruby

Dockerコンテナを起動

<NAME><DIRECTORY>は適時変更して下さい。

$ docker run -i -t --name <NAME> -p 3000:3000 -v "$PWD":<DIRECTORY> /bin/bash

Railsの導入

作業ディレクトリへ移動します。

$ cd <DIRECTORY>

次に、Gemfileの生成

$ bundle init

生成されていることを確認。

$ ls
Gemfile

編集用にvimをインストール。
(ホストPCのディレクトリから直接編集してもOK)

$ apt-get install vim-tiny

編集します。
# gem "rails"のコメントアウト#を外します。

$ vim.tiny Gemfile
編集後
# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem "rails"

Gemをインストール。

$ bundle install

新規Railsプロジェクトの作成。

$ bundle exec rails new .

NodeJSをインストール。

$ apt-get install nodejs

Webpackerをインストールしますが、その前にYarnが必要なのでインストールします。
しかし、そのままapt-get installすると0.32+gitというバージョンでインストールされてしまい、
webpack:installした際に行われるyarnのバージョンチェック時にエラーが出てしまうようです。
参考文献: 【Rails】ArgumentError: Malformed version number string 0.32+gitでwebpacker:installが実行できない場合の対処方法

$ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
$ echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
$ apt-get update && apt-get install yarn

Webpackerをインストール。

rails webpacker:install

Rails Server起動

起動できるはずです。

$ bundle exec rails server

自分の環境の場合、うまくローカルで接続できなかったので明示的に指定すると無事接続できました。

$ bundle exec rails server -b 0.0.0.0
=> Booting Puma
=> Rails 6.0.3.2 application starting in development 
=> Run `rails server --help` for more startup options
Puma starting in single mode...
* Version 4.3.6 (ruby 2.7.1-p83), codename: Mysterious Traveller
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop
Started GET "/" for 172.17.0.1 at 2020-09-07 09:47:16 +0000
Cannot render console from 172.17.0.1! Allowed networks: 127.0.0.0/127.255.255.255, ::1
   (1.5ms)  SELECT sqlite_version(*)
Processing by Rails::WelcomeController#index as HTML
  Rendering /usr/local/bundle/gems/railties-6.0.3.2/lib/rails/templates/rails/welcome/index.html.erb
  Rendered /usr/local/bundle/gems/railties-6.0.3.2/lib/rails/templates/rails/welcome/index.html.erb (Duration: 6.3ms | Allocations: 295)
Completed 200 OK in 21ms (Views: 12.1ms | ActiveRecord: 0.0ms | Allocations: 1651)

Yay! You're on Rails!

image.png

おわり

参考文献: 知識0から始めるRails on Docker

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

preventDefaultはなぜ必要なのか

動作環境
Ruby 2.6.5
Rails 6.0.3.2

JavaScriptにおけるpreventDefaultがどういった働きをしているかをようやく理解することができたので、整理するために投稿してみました。

preventDefaultの意味

preventDefaultの必要性について話していく前に、そもそもpreventDefaultが何をしているのかを説明します。

default(デフォルト)の挙動をprevent(妨害)しています。

説明になっていないと思うかもしれませんが、厳密な説明をしようとすると、おそらくかなり長い行数が必要となってしまいますし、具体的な例を提示した方がわかりやすいと思い、端的に説明しました。早速、具体的な例を元に説明していきます。

preventDefaultの具体例

index.rb
<%= form_with url:  "/posts", method: :post do |form| %>
  <%= form.text_field :fuga  %>
  <%= form.submit '投稿する' , id: "submit" %>
<% end %>

index.rbにて投稿するボタンをクリックすると、fugaに入力した内容が投稿されるとします。
このままだと、投稿するボタンをクリックすると、ページの読み込みが始まってしまうので、下記のJavaScriptを記載することで非同期通信にしたとします。

function hoge() {
  const submit = document.getElementById("submit");
  submit.addEventListener("click", (e) => {
    // 投稿するボタンをクリックした時と同じ挙動のコード(長くなってしまうので、省略)
  });
};
window.addEventListener("load", hoge);

しかし、ここで1つ問題が生じてしまいます。それは、非同期通信にしたことによって同じ挙動が追加で行われてしまうので、1回の投稿で2回分投稿したことになってしまうということです。
具体的に説明すると、投稿するボタンをクリックすることで非同期通信が行われ、ページの読み込みなしで投稿が1つ増え、ページの再読み込みをすることで、本来の投稿するボタンをクリックした時の挙動が反映されて、投稿が1つ増えます。そのため、結果として同じ投稿が2回されるということが起きてしまいます。

この問題を解決するためにprevent.Defaultを使います。以下のように1行追加するだけです。

function hoge() {
  const submit = document.getElementById("submit");
  submit.addEventListener("click", (e) => {
    e.preventDefault();
    // 投稿するボタンをクリックした時と同じ挙動のコード(長くなってしまうので、省略)
  });
};
window.addEventListener("load", hoge);

これによって、デフォルトの挙動を妨害することができるので、1回の投稿で2回分投稿することがなくなりました。
しかし、ここで私は「そもそもデフォルトの挙動って何?」「なぜ都合よく非同期通信だけの挙動が妨害されていないの?」
といった疑問を抱いておりました。

デフォルトの挙動とは?

基本的にRuby on Railsでのデフォルトの挙動は、ビューでの操作によって、ビューからコントローラーに情報が送られ、コントローラー内のいずれかのアクションが動くという認識で問題ないようです。
今回でいうと、投稿するボタンをクリックしたことでcreateアクションが動き、fugaを保存するということがデフォルトの挙動です。通常のRuby on Railsの流れですね。
そのため私は、preventDefaultを「通常のRuby on Railsの流れを妨害するもの」と捉えております。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rubyオブジェクトをJSON形式に変換

引数にハッシュ、配列等のrubyオブジェクトを入れJSON形式で返してくれます

JSON.generate

pry(main)> JSON.generate({"hoge"=>{"fuga"=>1, "fugafuga"=>2}})
=> "{\"hoge\":{\"fuga\":1,\"fugafuga\":2}}"

JSON.pretty_generate

pry(main)> JSON.pretty_generate({"hoge"=>{"fuga"=>1, "fugafuga"=>2}})
=> "{\n  \"hoge\": {\n    \"fuga\": 1,\n    \"fugafuga\": 2\n  }\n}"

JSON.generateに比べて見やすい文字列に返してくれます

人が読むものではない限りgenerateのほうが容量が軽いためgenerateを使用した方がいいとのことでした

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RSpecとminitestのおおまかな違い

はじめに

railsで最もよく使われるテスティングフレームワークはRSpecという話をよく聞きますが、
ruby標準で使用できるminitestとの違いについてまとめてみました。

RSpecについて

RSpecを使う理由は「最初からいろんな機能が全部入ってて便利だから」。
ちょっとテストコードのここだけを共通化したい、とか、モックで少し凝ったことをやりたい等、RSpecの標準機能でほとんど完結させる事ができる。

minitestについて

簡単なRubyプログラムの動作確認をしたりするときに使う。
デフォルトでRubyにインストールされるので、すぐに使えて便利。
RSpec並みに凝ったことをやろうとすると、プラグインをたくさん入れたり、継承、モジュール等を駆使することになるため、業務レベルのテストコードではminitestはあまり使われていない。

統合テスティング環境としての RSpec

実利的な観点から minitest よりも RSpec のほうが好ましい点がある。
minitestが 「テスティングフレームワーク」であるのに対して、Rspec が「統合テスティン グ環境」であることを目指しているという点。

RSpec は、振舞定義用の DSL の提供に加えて、様々なテスト関連ライブラリや周辺ツールを統合したり、標準サポートしたりといった試みを積極的に続けている。以下にその代表的なものを記述する。

・RSpec 実行結果の色付け (レッドバー/ グリーンバー表示) の標準サポート
・モック や スタブ といったテスト技法の支援、関連ライブラリとの統合
・保留 (pending) のサポート
・Rake や RCov、Heckle、AutoTest (ZenTest) といった周辺ツールとの統合
・Ruby on Rails のサポート
・様々な形式による実行結果レポート (例:HTML 出力) 。
・TextMate との統合
・などなど

※各項目について詳しくはRSpec 公式サイトの該当ページ参照

上記周辺ツールへの積極的な統合・サポートにより、RSpecの標準機能でできる事が多いため、広く使用されているのではないかと推測できる。

今後、RSpecを使用したテストについて学んで行きたい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

enumからセレクトボックスを作る際に、表示する文言を変えたいとき

あまり無いケースだとは思うが、enumからセレクトボックスを作成する際に、文言を変えたい場合の方法をメモしておく。
前提として、gemに enumerizereform を使用している。

product_form.rb
extend Enumerize

enumerize :fluit, in: {
  apple:      1,
  orange:     2,
  peach:      3,
  strawberry: 4,
  cherry:     5,
}, predicates: { prefix: true }

property :fluit
ja.yml
product:
  fluit:
    apple: りんご
    orange: オレンジ
    peach: もも
    strawberry: いちご
    cherry: さくらんぼ

普通に書けば、これで良い。

new.slim
= f.select :fluit, f.object.class.fluit.options, {prompt: '果物を選択'}

しかし、「『りんご』と『さくらんぼ』は『(特別セール中)』という文言を入れたい」と言われたとする。
とはいえ、ja.ymlを変えたくはない。
f.object.class.fluit.options を使わずに1つずつ書くのも、メンテナンス性が悪い。

そこで、f.object.class.fluit.options が生成する Array を map で回して、特定の value の時のみ、文言を足して返すメソッドを作った。

product_form.rb
def fruit_select_display
  self.class.fluit.options.map do |key, value|
    if value == 'apple' || value == 'cherry'
      [key + '(特別セール中)', value]
    else
      [key, value]
    end
  end
end
new.slim
= f.select :fluit, f.object.fruit_select_display, {prompt: '果物を選択'}

これでいったん用は足せた。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsで現在いるページの判定をする

current_page?メソッド

viewで使用

# http://hoge.com/fugaかどうか
current_page?(http://hoge.com/fuga)

# /hogehogeかどうか
current_page?(/hogehoge)

# hoge_pathかどうか
current_page?(hoge_path)

# 任意のアクションかどうか
current_page?(action: 'new')

# 任意のコントローラ/アクションかどうか
current_page?(controller: 'hoge', action: 'new')
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby on Railsで現在いるページの判定をする

current_page?メソッド

viewで使用

# http://hoge.com/fugaかどうか
current_page?(http://hoge.com/fuga)

# /hogehogeかどうか
current_page?(/hogehoge)

# hoge_pathかどうか
current_page?(hoge_path)

# 任意のアクションかどうか
current_page?(action: 'new')

# 任意のコントローラ/アクションかどうか
current_page?(controller: 'hoge', action: 'new')
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails]モデルの単体テスト実装方法

前提条件

今回は新規ユーザー登録に関するモデルの単体テストを実装していきながら、方法をアウトプットします。ユーザー登録の仕様書は以下のとおり。

ニックネームが必須       
メールアドレスは一意である
メールアドレスは@とドメインを含む必要がある
パスワードが必須
パスワードは7文字以上
パスワードは確認用を含めて2回入力する
ユーザー本名が、名字と名前でそれぞれ必須
ユーザー本名は全角で入力させる
ユーザー本名のフリガナが、名字と名前でそれぞれ必須
ユーザー本名のフリガナは全角で入力させる
生年月日が必須

マイグレーションファイルは以下のとおり。

20200823050614_devise_create_users.rb
class DeviseCreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string  :nickname,              null: false
      t.string  :email,                 null: false, default: ""
      t.string  :encrypted_password,    null: false, default: ""
      t.string  :family_name,           null: false
      t.string  :family_name_kana,      null: false
      t.string  :first_name,            null: false
      t.string  :first_name_kana,       null: false
      t.date    :birth_day,             null: false

      ## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at

      ## Rememberable
      t.datetime :remember_created_at

      ## Trackable
      # t.integer  :sign_in_count, default: 0, null: false
      # t.datetime :current_sign_in_at
      # t.datetime :last_sign_in_at
      # t.string   :current_sign_in_ip
      # t.string   :last_sign_in_ip

      ## Confirmable
      # t.string   :confirmation_token
      # t.datetime :confirmed_at
      # t.datetime :confirmation_sent_at
      # t.string   :unconfirmed_email # Only if using reconfirmable

      ## Lockable
      # t.integer  :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
      # t.string   :unlock_token # Only if unlock strategy is :email or :both
      # t.datetime :locked_at
      t.timestamps null: false
    end

    add_index :users, :email,                 unique: true
    add_index :users, :reset_password_token,  unique: true
    # add_index :users, :confirmation_token,   unique: true
    # add_index :users, :unlock_token,         unique: true
  end
end

モデルは以下のとおり。

user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

    # バリデーションの設定(空の文字列を保存させない為と一意性制約)
    validates :nickname,               presence: true
    validates :encrypted_password,     presence: true
    validates :family_name,            presence: true
    validates :family_name_kana,       presence: true
    validates :first_name,             presence: true
    validates :first_name_kana,        presence: true
    validates :birth_day,              presence: true

    # アソシエーション
    has_many :cards,          dependent: :destroy
    has_many :shipping_infos, dependent: :destroy
    has_many :comments,       dependent: :destroy
    has_many :items
end

実装手順

① RSpecの準備
② RSpecの設定
③ factory_botを導入
④ factory_botの実装
⑤ 正規表現の記述
⑥ テストコードの実装
⑦ テスコードの実行

RSpecの準備

まずGemをインストールします。

Gemfile.rb
group :development, :test do

  # 省略

  gem 'rspec-rails'
end

group :development do
  gem 'web-console'
end

# web_console は既に記述がある場合は、記述場所を移動するだけでOK。追記してしまうと重複してしまう可能性があるため注意してください。

Gemfileを編集したら、忘れずにbundle installを実行。

RSpecの設定

続いてRSpecの基本設定を行う。
まずはRSpec用の設定ファイルを作成する。

ターミナル.
$ rails g rspec:install

# 以下のようにファイルが作成されれば成功 ▼
 create  .rspec
 create  spec
 create  spec/spec_helper.rb
 create  spec/rails_helper.rb

次に、.rspecに以下を追加。

rspec.
--format documentation

続いて、RSpec用ディレクトリを設定します。

RSpecによるテストコードが書かれたファイルのことを、specファイルと呼び、全てのspecファイルは、先程のrails g rspec:installコマンドで生成されたspecディレクトリの中に格納するので、ここで作成しておきましょう。

モデルに関するテスト用ファイルであればspec/models/以下に、コントローラーに関するテスト用ファイルであればspec/controllers/以下に格納されます。appディレクトリ以下にあるテストの対象となるコードの在り処は全てこのディレクトリです。

specファイルは対応するクラスを持つファイル名spec.rbという名前になります。今回はまず「user.rb」に関するspecファイルを作成するので、その場合の名前は`「userspec.rb」`になります。

ディレクトリの作成が完了したら、この時点で一度RSpecが正常に利用できるかの確認を行いましょう。

ターミナル.
$ bundle exec rspec

# 以下のような結果になれば正常に動作しているのでOK
No examples found.

Finished in 0.00031 seconds (files took 0.19956 seconds to load)
0 examples, 0 failures

factory_botを導入

続いてfactory_botを導入します。
Gemをインストール。

Gemfile.rb
group :development, :test do

  # 省略

  gem 'rspec-rails'
  gem 'factory_bot_rails'
end

その後、bundle installを実行。

factory_botの実装

次に、specディレクトリ直下にfactoriesというディレクトリを作成し,その中に、作成したインスタンスの複数形のファイル名でRubyのファイルを作成します。今回の場合は、users.rbになります。

users.rbを以下のように編集。

users.rb
FactoryBot.define do

  factory :user do
    nickname              {"tarou"}
    email                 {"sample@gmail.com"}
    password              {"1234567"}
    encrypted_password    {"1234567"}
    family_name           {"山田"}
    family_name_kana      {"ヤマダ"}
    first_name            {"太郎"}
    first_name_kana       {"タロウ"}
    birth_day             {"2000-01-01"}
  end
end

この記述は、仕様書に沿った内容をダミーデーターとして作成しており、こうすると、specファイルの中で特定のメソッド(buildやcreate)により簡単にインスタンスを生成したり、DBに保存したりできるようになります。

正規表現記述

続いて、仕様書に沿って、バリデーションへ正規表現を設定していきます。
必要な正規表現は以下のとおり。

パスワードは7文字以上
ユーザー本名は全角で入力させる
ユーザー本名のフリガナは全角で入力させる

これらを踏まえてモデルへ記述すると以下のとおり。

user.rb
class User < ApplicationRecord
    # 省略

    validates :nickname,               presence: true
    validates :encrypted_password,     presence: true, length: { minimum: 7 } # ここが文字数の正規表現
    validates :family_name,            presence: true, format: {with: /\A[ぁ-んァ-ン一-龥]/ } # ここがユーザー本名全角の正規表現
    validates :family_name_kana,       presence: true, format: {with: /\A[ァ-ヶー-]+\z/ } # ここがフリガナ全角の正規表現
    validates :first_name,             presence: true, format: {with: /\A[ぁ-んァ-ン一-龥]/ } # ここがユーザー本名全角の正規表現
    validates :first_name_kana,        presence: true, format: {with: /\A[ァ-ヶー-]+\z/ } # ここがフリガナ全角の正規表現

    # 省略
end

Rubyの正規表現は下記サイトを参考にしてください。
https://gist.github.com/nashirox/38323d5b51063ede1d41

テストコードの実装

それではいよいよテストコードの実装です!
テスコードを記述するときに心掛ける点は以下のとおり。

①各exampleで期待する値は1つ
②期待する結果をはっきりわかりやすく記述
③起きて欲しいことと起きてほしくないこと両方をテストする
④可読性を考えつつ、適度にDRYにする

user_spec.rbに以下のコードを記述。

user_spec.rb
require 'rails_helper'

describe User do
  describe '#create' do

  end
end

今回はUserモデルの単体テストなのでdescribe User doとなり、新規ユーザーを作成するのでdescribe '#create' doとしており、2行目のdescribの中にテストコードを記述します。

まず、全ての項目の入力が存在すれば登録できることをテストします。
コードとその解説は以下の通り。

user_spec.rb
require 'rails_helper'

describe User do
  describe '#create' do
    # 入力されている場合のテスト ▼

    it "全ての項目の入力が存在すれば登録できること" do # テストしたいことの内容
      user = build(:user)  # 変数userにbuildメソッドを使用して、factory_botのダミーデータを代入
      expect(user).to be_valid # 変数userの情報で登録がされるかどうかのテストを実行
    end
  end
end

続いてテストすることは以下の通り。

nicknameがない場合は登録できないこと
emailがない場合は登録できないこと
passwordがない場合は登録できないこと
encrypted_passwordがない場合は登録できないこと
family_nameがない場合は登録できないこと
family_name_kanaがない場合は登録できないこと
first_nameがない場合は登録できないこと
first_name_kanaがない場合は登録できないこと
birth_dayがない場合は登録できないこと

これらの条件は仕様書の必須項目(null:false, presence: true)をテストする為のものです。
それでは記述していきましょう。解説はnicknameがない場合は登録できないことのテスト箇所で行います。

user.spec.rb
require 'rails_helper'

describe User do
  describe '#create' do

      # 入力されている場合のテスト ▼

      it "全ての項目の入力が存在すれば登録できること" do
        user = build(:user)
        expect(user).to be_valid
      end

      # nul:false, presence: true のテスト ▼

      it "nicknameがない場合は登録できないこと" do # テストしたいことの内容
        user = build(:user, nickname: nil) # 変数userにbuildメソッドを使用して、factory_botのダミーデータを代入(今回の場合は意図的にnicknameの値をからに設定)
        user.valid? #バリデーションメソッドを使用して「バリデーションにより保存ができない状態であるか」をテスト
        expect(user.errors[:nickname]).to include("を入力してください") # errorsメソッドを使用して、「バリデーションにより保存ができない状態である場合なぜできないのか」を確認し、.to include("を入力してください")でエラー文を記述(今回のRailsを日本語化しているのでエラー文も日本語)
      end

      it "emailがない場合は登録できないこと" do
        user = build(:user, email: nil)
        user.valid?
        expect(user.errors[:email]).to include("を入力してください")
      end

      it "passwordがない場合は登録できないこと" do
        user = build(:user, password: nil)
        user.valid?
        expect(user.errors[:password]).to include("を入力してください")
      end

      it "encrypted_passwordがない場合は登録できないこと" do
        user = build(:user, encrypted_password: nil)
        user.valid?
        expect(user.errors[:encrypted_password]).to include("を入力してください")
      end

      it "family_nameがない場合は登録できないこと" do
        user = build(:user, family_name: nil)
        user.valid?
        expect(user.errors[:family_name]).to include("を入力してください")
      end

      it "family_name_kanaがない場合は登録できないこと" do
        user = build(:user, family_name_kana: nil)
        user.valid?
        expect(user.errors[:family_name_kana]).to include("を入力してください")
      end

      it "first_nameがない場合は登録できないこと" do
        user = build(:user, first_name: nil)
        user.valid?
        expect(user.errors[:first_name]).to include("を入力してください")
      end

      it "first_name_kanaがない場合は登録できないこと" do
        user = build(:user, first_name_kana: nil)
        user.valid?
        expect(user.errors[:first_name_kana]).to include("を入力してください")
      end

      it "birth_dayがない場合は登録できないこと" do
        user = build(:user, birth_day: nil)
        user.valid?
        expect(user.errors[:birth_day]).to include("を入力してください")
      end
  end
end

続いて、emailの一意性制約のテストを行います。

user.spec.rb
require 'rails_helper'

describe User do
  describe '#create' do

      # 省略

      # email 一意性制約のテスト ▼

      it "重複したemailが存在する場合登録できないこと" do
        user = create(:user) # createメソッドを使用して変数userとデータベースにfactory_botのダミーデータを保存
        another_user = build(:user, email: user.email) # 2人目のanother_userを変数として作成し、buildメソッドを使用して、意図的にemailの内容を重複させる
        another_user.valid? # another_userの「バリデーションにより保存ができない状態であるか」をテスト
        expect(another_user.errors[:email]).to include("はすでに存在します") # errorsメソッドを使用して、emailの「バリデーションにより保存ができない状態である場合なぜできないのか」を確認し、その原因のエラー文を記述
      end
  end
end

続いて、確認用パスワードがなければ登録できないテストを実行します。

user.spec.rb
require 'rails_helper'

describe User do
  describe '#create' do

      # 省略

      # 確認用パスワードが必要であるテスト ▼

      it "passwordが存在してもencrypted_passwordがない場合は登録できないこと" do
        user = build(:user, encrypted_password: "") # 意図的に確認用パスワードに値を空にする
        user.valid?
        expect(user.errors[:encrypted_password]).to include("を入力してください", "は7文字以上で入力してください")
      end
  end
end

ここまでで、以下のテストは完了しました。

# 完了したテスト▼

ニックネームが必須 # 完了
メールアドレスは一意である # 完了
メールアドレスは@とドメインを含む必要がある # 完了
パスワードが必須 # 完了
パスワードは確認用を含めて2回入力する # 完了
ユーザー本名が、名字と名前でそれぞれ必須 # 完了
ユーザー本名のフリガナが、名字と名前でそれぞれ必須 # 完了
生年月日が必須 # 完了

# これから実装するテスト▼

パスワードは7文字以上
ユーザー本名は全角で入力させる
ユーザー本名のフリガナは全角で入力させる

それでは、ここからは先ほど記述した正規表現のテストを行います。
まずパスワードは7文字以上のテストから。

起きて欲しいことと起きてほしくないことの両方をテストします。

user.spec.rb
require 'rails_helper'

describe User do
  describe '#create' do

      # 省略

      # パスワードの文字数テスト ▼

      it "passwordが7文字以上であれば登録できること" do
        user = build(:user, password: "1234567", encrypted_password: "1234567") # buildメソッドを使用して7文字のパスワードを設定
        expect(user).to be_valid
      end

      it "passwordが7文字以下であれば登録できないこと" do
        user = build(:user, password: "123456", encrypted_password: "123456") # 意図的に6文字のパスワードを設定してエラーが出るかをテスト 
        user.valid?
        expect(user.errors[:encrypted_password]).to include("は7文字以上で入力してください")
      end
  end
end

最後に全角入力と全角カナ入力のテストを行います。

user.spec.rb
require 'rails_helper'

describe User do
  describe '#create' do

      # 省略

      # 名前全角入力のテスト ▼

      it 'family_nameが全角入力でなければ登録できないこと' do
        user = build(:user, family_name: "アイウエオ") # 意図的に半角入力を行いエラーを発生させる
        user.valid?
        expect(user.errors[:family_name]).to include("は不正な値です")
      end

      it 'first_nameが全角入力でなければ登録できないこと' do
        user = build(:user, first_name: "アイウエオ") # 意図的に半角入力を行いエラーを発生させる
        user.valid?
        expect(user.errors[:first_name]).to include("は不正な値です")
      end

      # カタカナ全角入力のテスト ▼

      it 'family_name_kanaが全角カタカナでなければ登録できないこと' do
        user = build(:user, family_name_kana: "あいうえお") # 意図的にひらがな入力を行いエラーを発生させる
        user.valid?
        expect(user.errors[:family_name_kana]).to include("は不正な値です")
      end

      it 'first_name_kanaが全角カタカナでなければ登録できないこと' do
        user = build(:user, first_name_kana: "あいうえお") # 意図的にひらがな入力を行いエラーを発生させる
        user.valid?
        expect(user.errors[:first_name_kana]).to include("は不正な値です")
      end
  end
end

これで仕様書に沿った機能のテストは以上です。
最終的には以下のようになります。

user.spec.rb
require 'rails_helper'

describe User do
  describe '#create' do

      # 入力されている場合のテスト ▼

      it "全ての項目の入力が存在すれば登録できること" do
        user = build(:user)
        expect(user).to be_valid
      end

      # nul:false, presence: true のテスト ▼

      it "nicknameがない場合は登録できないこと" do
        user = build(:user, nickname: nil)
        user.valid?
        expect(user.errors[:nickname]).to include("を入力してください")
      end

      it "emailがない場合は登録できないこと" do
        user = build(:user, email: nil)
        user.valid?
        expect(user.errors[:email]).to include("を入力してください")
      end

      it "passwordがない場合は登録できないこと" do
        user = build(:user, password: nil)
        user.valid?
        expect(user.errors[:password]).to include("を入力してください")
      end

      it "encrypted_passwordがない場合は登録できないこと" do
        user = build(:user, encrypted_password: nil)
        user.valid?
        expect(user.errors[:encrypted_password]).to include("を入力してください")
      end

      it "family_nameがない場合は登録できないこと" do
        user = build(:user, family_name: nil)
        user.valid?
        expect(user.errors[:family_name]).to include("を入力してください")
      end

      it "family_name_kanaがない場合は登録できないこと" do
        user = build(:user, family_name_kana: nil)
        user.valid?
        expect(user.errors[:family_name_kana]).to include("を入力してください")
      end

      it "first_nameがない場合は登録できないこと" do
        user = build(:user, first_name: nil)
        user.valid?
        expect(user.errors[:first_name]).to include("を入力してください")
      end

      it "first_name_kanaがない場合は登録できないこと" do
        user = build(:user, first_name_kana: nil)
        user.valid?
        expect(user.errors[:first_name_kana]).to include("を入力してください")
      end

      it "birth_dayがない場合は登録できないこと" do
        user = build(:user, birth_day: nil)
        user.valid?
        expect(user.errors[:birth_day]).to include("を入力してください")
      end

      # パスワードの文字数テスト ▼

      it "passwordが7文字以上であれば登録できること" do
        user = build(:user, password: "1234567", encrypted_password: "1234567")
        expect(user).to be_valid
      end

      it "passwordが7文字以下であれば登録できないこと" do
        user = build(:user, password: "123456", encrypted_password: "123456")
        user.valid?
        expect(user.errors[:encrypted_password]).to include("は7文字以上で入力してください")
      end

      # email 一意性制約のテスト ▼

      it "重複したemailが存在する場合登録できないこと" do
        user = create(:user)
        another_user = build(:user, email: user.email)
        another_user.valid?
        expect(another_user.errors[:email]).to include("はすでに存在します")
      end

      # 確認用パスワードが必要であるテスト ▼

      it "passwordが存在してもencrypted_passwordがない場合は登録できないこと" do
        user = build(:user, encrypted_password: "")
        user.valid?
        expect(user.errors[:encrypted_password]).to include("を入力してください", "は7文字以上で入力してください")
      end

      # 本人確認名前全角入力のテスト ▼

      it 'family_nameが全角入力でなければ登録できないこと' do
        user = build(:user, family_name: "アイウエオ")
        user.valid?
        expect(user.errors[:family_name]).to include("は不正な値です")
      end

      it 'first_nameが全角入力でなければ登録できないこと' do
        user = build(:user, first_name: "アイウエオ")
        user.valid?
        expect(user.errors[:first_name]).to include("は不正な値です")
      end

      # 本人確認カタカナ全角入力のテスト ▼

      it 'family_name_kanaが全角カタカナでなければ登録できないこと' do
        user = build(:user, family_name_kana: "あいうえお")
        user.valid?
        expect(user.errors[:family_name_kana]).to include("は不正な値です")
      end

      it 'first_name_kanaが全角カタカナでなければ登録できないこと' do
        user = build(:user, first_name_kana: "あいうえお")
        user.valid?
        expect(user.errors[:first_name_kana]).to include("は不正な値です")
      end
  end
end

 テスコードの実行

テストを記述したアプリのディレクトリのターミナルにて下記コマンドを実行。

ターミナル.
$ bundle exec rspec         

すると下記画像のように処理が行われればテストが成功しています。
image.png

最後に

モデルの単体テストは基本文法と、エラーを発生させる処理と発生させない処理を把握し、それぞれのメソッドを理解することができれば新たに機能が追加された場合でもコードを記述できると思います。

何か気になる点がございましたらコメント欄にてお申し付けください!
最後までご覧いただきありがとうございました!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

byebugの導入から使い方まで

Railsの勉強の際にデバッグツールは使っていますか?
デバッグツールとは、プログラミングにつきもののデバッグ作業をサポートしてくれるツールのことで、ステップ実行といって一行ごとにプログラムを実行してその時に宣言されている変数の中身をみながら動作を確認する事が出来ます。

それではRailsの開発に使っているbyebugについて紹介していきます。

まずはbyebugのgemをインストールして、デバックしたいコードに「byebug」というメソッドを仕込んでおくと、プログラムがそこを通過した時に止まり、デバッグモードが開始、対話形式でコマンドを打ち込んでおくと自由にメソッドを実行したり変数の値を確認したり出来ます。

インストール方法

インストールはGemfaileに

gem 'byebug'

を追記するだけです。 (railsをインストールしている場合はデフォルトでインストールされています)

追記したら

bundle install

でgemをインストールしてきます。

byebugの使い方

まずbyebugを開始するには、デバッグをしたいところにbyebugというメソッドを書いて上げます。
(今回の場所は例となります。)

スクリーンショット 2020-09-03 10.44.03.png

今回はstatic_pages/homeページの部分にbyebugを挿入してみます。

byebugを挟み込んだら、実際にメソッドを叩いてみます。

スクリーンショット 2020-09-03 10.45.14.png

このように処理が止まり入力プロンプトが出てきます。
ここでnextを入力してEnterを押すと

スクリーンショット 2020-09-03 10.46.37.png

次の行に移動しました。ここでhelpメソッドを調べる為に入力してEnterを押すと

スクリーンショット 2020-09-03 10.48.24.png

helpの中身を確認する事が出来ます。(変数の中身も見れます)
実際のデバッグ作業ではこのように変数の値やメソッドを実行してみて、期待通りの動きをしているかを確認していきます。

よく使うコマンド一覧

next       一行進む
continue   次のブレイクポイントに進む
step       メソッドの内部にステップインする
list       ソースコードを表示する
up         ソースコードの上を表示する
down       ソースコードの下を表示する

他にも使ってみたいコマンドがあればhelpを入力すると表示されるので確認してみて下さい♪

ここまで覚えてしまえば、あとは実際に書いてみて慣れるのが一番早いと思います。

まとめ

byebugの紹介をしましたが、いかがでしたでしょうか?
byebugではそれぞれの変数の型も見れるのでブラックボックス化しがちなプログラミングの細かいところを見れるようになり、開発スピードの高速化と一石二鳥です。

慣れればとても使えるツールだと思うのでぜひ使ってみてください♪

駆け出しエンジニアなので間違っている部分やわかりにくい点などあるかと思います。
その場合はコメントなど入れていただけるとこれからの成長につながりますのでよろしければお願いします。
【LGTM】を押していただけると飛び跳ねて喜びます。
Twitteの方でも呟いたりしておりますのでもしよろしければ遊びにきてください♪

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails 6で認証認可入り掲示板APIを構築する #2 gitとrubocop導入

Rails 6で認証認可入り掲示板APIを構築する #1 環境構築

Githubに上げる

前回の続きから。
とりあえず動くところまでいったのでGithubにpushしましょう。

New repositoryを選択。

その後適当な名前を付けて作成。
他の人からソースコードを見られたくなければPrivateに。問題なければPublicに。

rep.png

Cloud9はデフォルトでgitが入っているのでそのままgitコマンドが使えます。

$ git add -A
$ git commit -m 'initial commit'
$ git remote add origin https://github.com/{YOUR_ACCOUNT}/{YOUR_REPOSITORY}.git
$ git branch -M master
$ git push -u origin master

上記コマンドのYOUR_部分は作ったばかりのリポジトリに書いてあるのでそこを参照してください。

キーペアを作成し、公開鍵をGithubに登録する

上記対応だと、pushするたびにGithubのユーザーIDとパスワードを聞かれます。

$ ssh-keygen -t rsa

Enterキー連打でキーペアを作ります。

$ cat ~/.ssh/id_rsa.pub 

で表示される文字列が公開鍵なのでコピーします。

Githubに移動し、右上の自分のアイコンから『Settings』→『SSH and GPG keys』→『New SSH key』と遷移。
タイトルは分かりやすいものを付けて、keyに先程の公開鍵を貼り付けて保存。

ただ、これでもpush時にまだIDパスを聞かれるはず。

$ git remote set-url origin git@github.com:{YOUR_ACCOUNT}/{YOUR_REPOSITORY}.git

とすればOK。
これで、次回以降pushやpull時にIDパスを都度聞かれなくなります。

参考:GitHubでssh接続する手順~公開鍵・秘密鍵の生成から~

testディレクトリを消す

今回はRSpecを使ってテスト実装していくので、minitest用であるtestディレクトリを消します。

$ git rm -r test/

rubocopを入れる

まずは静的解析してくれるrubocopを入れましょう。
というのもコーディング規約をチェックしてくれるgemなのですが、後から入れるとコーディング規約に反する指摘が大量に出て手がつけられなくなるためです。

Gemfile
...

+ gem 'rubocop-rails'

...
インストール
$ bundle

rubocop設定ファイルを作る

.rubocop.yml
AllCops:
  Exclude:
    - bin/*
    - db/schema.rb

# 日本語コメント許可
AsciiComments:
  Enabled: false

# ダブルクォーテーションに統一
StringLiterals:
  EnforcedStyle: double_quotes

アプリケーションのrootディレクトリ(/home/ec2-user/environment/bbs)に配置してください。
ファイル名の先頭にドット付けるのを忘れずに。

rubocop指摘箇所を直す

$ rubocop -a

実行すると40個前後のエラーを吐きますが、ほぼ全て以下エラーのはず。

C: Style/FrozenStringLiteralComment: Missing frozen string literal comment.

これは

各Rubyファイルの1行目に追記
+ # frozen_string_literal: true
+

これでOKです。
意味するところはRubyだと文字列がデフォルトでミュータブルだけど、意図しない不具合を生んだりするのでfreezeしてイミュータブルにするといいよね
Ruby3ではデフォルトでイミュータブルになることが決まっているよ、それに備えてこの記述を入れておけばファイル中の文字列がイミュータブルになるよ

的な感じです。

数十ファイルを開いて編集保存して…をやるのが面倒であれば、

# Gemfileの先頭に # frozen_string_literal: trueを追記
$ sed -i '1s/^/# frozen_string_literal: true\n\n/' Gemfile

のようにすればファイル先頭に追記ができます。
db/*のように指定するとディレクトリ一括もできます(ちょっと危険)。

元も子もない話をすると、チュートリアルの範囲ならrubocopでこのチェック無効にしちゃってもいいです。
その場合は

.rubocop.yml
...

+ Style/FrozenStringLiteralComment:
+   Enabled: false

と追記してください。
ただし前述の通りRuby3からはデフォルトでイミュータブルになるので、今後新規アプリケーションを作り始めるのであれば入れておくべきでしょう。

あとは以下エラーを潰すだけですね。

C: Style/Documentation: Missing top-level class documentation comment.

app/mailers/application_mailer.rb
app/models/application_record.rb
config/application.rb
の3ファイルで発生しているはずですが、クラスのドキュメンテーションが無い時に出るエラーです。

config/application.rb
 module Bbs
+  #
+  # 設定ファイル
+  #
   class Application < Rails::Application

のようにclass定義の前にコメントを追加しましょう。

全て修正し、

Inspecting 26 files
..........................

26 files inspected, no offenses detected

のようにエラーがなくなればOKです。

Rails 6で認証認可入り掲示板APIを構築する #3 RSpec, FactoryBot導入しpostモデルを作る

連載目次へ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【RSpec】FactoryBotを使ってみよう【Rails】

 Factory Botとは??

  • テストのためのデータを簡単に作成できるライブラリ
  • Active Record を使う

 Factory Botのあれこれ

factory_bot_railsをインストールすると、下記のようなコマンドでfactoryを作成するためのファイルを作ることができます。

bin/rails g factory_bot:model task

少し解説します。

bin/rails gまでは大丈夫でしょう。factory_bot:modelでモデルに明示します。taskのように対象を指定してあげてください。

このコマンドを実行すると、spec/factories/tasks.rbに以下のようなファイルが生成されると思います。

spec/factories/tasks.rb
FactoryBot.define do

  factory :task do

  end
end

雛形ができましたね。この中にデータを定義していきましょう。

spec/factories/tasks.rb
FactoryBot.define do

  factory :task do
    name "お買い物"
    content "夕飯を買いに行く"
  end
end

少ないかもしれませんが、まあ大丈夫でしょう。 テストファイル内でFactoryBot.create(:task)と実行することで、spec/factories/tasks.rbに記述した内容のデータを作成することができます。

 FactoryBotを少しだけ使いこなす。

 オーバーライドさせる

例えば、tasknamenilだった時にエラーを出力させる異常系のテストを書きたい場合は、下記のように書けます。

spec/models/task_spec.rb
# nameがなければ、無効。
it "is invalid without a name" do
  task = FactoryBot.build(:task, name: nil)
  task.valid?
  expect(task.errors[:name]).to include("can't be blank")
end

プロパティを上書きしてnilにすることで、期待通りの形にしています。

 ユニークな値を扱う。

例えばメアドは唯一無二であるはずだし、そのような仕様であり、そのようなテストを書くべきです。しかし、FactoryBotは常に同じ値を参照するので、そのような事象に対応できない場合があります。

この問題をは下記のようにシーケンスを使うことで解決できます。

spec/factories/users.rb
FactoryBot.define do

  factory :user do
    name "山田"
    sequence(:email) { |n| "test#{n}@example.com" }
  end
end

このように定義することによって、test1@example.comtest2@example.comのように設定されます。

とても便利。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsでワクワク個人開発  第5回: CRUD処理の規制と温度/湿度の登録

他ユーザーがCRUD処理できないようにする

 少しだけ涼しくなってきた。朝の開発が心地よくなっている。今のコードでは自分以外のユーザーでも植物のCRUD処理ができてしまうので、それをなくしていこう。plant_controller.rbbefore actionを追加する

app/controllers/plants_controller.rb
class PlantsController < ApplicationController
  before_action :move_to_index, except: [:index, :show]

#----中略----
  def move_to_index
    # 自分以外のユーザーの植物の 編集/削除/新規作成はできない
    if (user_signed_in?)    
      if (params[:user_id].to_i != current_user.id)
        redirect_to action: :index
      end
    elsif
        redirect_to action: :index
    end
  end
#----中略----
end

before_actionはこのコントローラー内のアクションがよびだされたときにはじめに実行される。今回はmove_to_indexというメソッドが実行されるように指定した。move_to_indexではログインしていてかつログインIDと表示したページのuseridが一致しているときだけなにも起こらない。それ以外の場合はindexアクションが実行されるようになっている。
つまりは植物一覧ページへ飛ぶ。

温度湿度の表示

 いよいよこんなかんじになってきた。所有している植物の温度と湿度を登録できるようにする。以前のDB設計の際に考えていたgrowth_recordを追加することにしよう。

rails g model growth_recordをターミナルで実行してmodelをつくる。

growth_reocerdテーブルへ各種カラムを書き込む。

db/migrate/20200821194548_create_growth_records.rb
class CreateGrowthRecords < ActiveRecord::Migration[6.0]
  def change
    create_table :growth_records do |t|
      t.integer :plant_id, null: false
      t.datetime :record_time
      t.float :temp, null: false
      t.float :humid, null: false
      t.timestamps
    end
  end
end

temp(温度)とhumid(湿度)はfloat型で小数点を記録できるようにした。record_timeはまだ実装していないのでnullでも大丈夫なようにした。

次はrails g controller growth_reocordsでコントローラーを作成する。

app/controllers/growth_records_controller.rb
class GrowthRecordsController < ApplicationController
  def index
    @user = User.find(params[:user_id])
    @plants = @user.plants
    @growth_record = @plants.growth_record
  end
end

modelどうしの関連づけをする必要がある。

app/models/growth_record.rb
class GrowthRecord < ApplicationRecord
  belongs_to :plant
end
app/models/plant.rb
class Plant < ApplicationRecord
  belongs_to :user
  has_many :growth_record
end

今回は植物のgrowth_recordのindexページへは飛ばない。植物詳細ページの内容がかわるだけである。だからroutes.rbの記述に変更はない。かわりに植物のコントローラーとviewファイルが変更される。

app/controllers/plants_controller.rb
#---中略---
  def show
    @user = User.find(params[:user_id])
    @plant = @user.plants.find(params[:id])
    @growth_record = @plant.growth_record # ← 追加
  end
#---中略---
app/views/plants/show.html.erb
<h3>植物の名前</h3>
<%= @plant.plant_name %><br>
<h3> 植物の写真 </h3>
<%= @plant.img %><br> 
<%= @plant.img %><br>
<h3> 温度/湿度 </h3>
<!-- 追加↓-->
<% @growth_record.each do |g| %>
  <li>日時:<%= g.record_time %> /  温度:<%= g.temp %>  /湿度:<%= g.humid %>  </li> 
<% end %> 

これでOK。ついでにseedデータも修正しておこう。

db/seed.rb
# ----中略-----
GrowthRecord.create(plant_id:1, record_time: "r22ose", temp: 25.2, humid:50.3)
GrowthRecord.create(plant_id:1, record_time: "r22ose", temp: 24.2, humid:70.3) 
GrowthRecord.create(plant_id:1, record_time: "r22ose", temp: 21.2, humid:60.3) 
GrowthRecord.create(plant_id:1, record_time: "r22ose", temp: 20.2, humid:30.3)  
GrowthRecord.create(plant_id:2, record_time: "ro33se", temp: 25.6, humid:30.3) 

よし。

温度湿度のCRUD処理

 この記事もきりの良いところまで進めてしまおう。温度と湿度のCRUD処理ができるようする。まずはroutes.rbを修正しよう。

config/routes.rb
Rails.application.routes.draw do
  root to: 'users#index'
  devise_for :users

  resources :users do
    resources :plants do
      resources :growth_records, shallow: true # 追加
    end
  end
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end

 shallowというオプションがあることに気づいた。railsガイドの浅いネストにある。
Rails のルーティング - Railsガイド

この方法は、ルーティングの記述を複雑にせず、かつ深いネストを作らないという絶妙なバランスを保っています。:shallowオプションを使うことで、上と同じ内容をさらに簡単に記述できます。

これにより、show/edit/update/destroyアクションはネストの外にいくようになる。ターミナルでrails routesを打ちながら確認しよう。

            user_plant_growth_records GET    /users/:user_id/plants/:plant_id/growth_records(.:format)                                growth_records#index
                                      POST   /users/:user_id/plants/:plant_id/growth_records(.:format)                                growth_records#create
         new_user_plant_growth_record GET    /users/:user_id/plants/:plant_id/growth_records/new(.:format)                            growth_records#new
                   edit_growth_record GET    /growth_records/:id/edit(.:format)                                                       growth_records#edit
                        growth_record GET    /growth_records/:id(.:format)                                                            growth_records#show
                                      PATCH  /growth_records/:id(.:format)                                                            growth_records#update
                                      PUT    /growth_records/:id(.:format)                                                            growth_records#update
                                      DELETE /growth_records/:id(.:format)                                                            growth_records#destroy

コントローラーやviewファイルは植物のCRUD処理の場合とあまり変わらず実装できた。よかった。

おまけ:「植物詳細」へ戻るリンクが難しかった

 個人的に難しかったのがひとつあった。linkのurlである。今まではCRUD処理の後はマイページへ戻る仕様にしていたのでlinkの張り方は迷わなかった。以下のような形。

<%= link_to 'マイページへ', user_path(current_user.id) %>  

 でも、温度湿度のCRUD処理をした後はその植物の詳細ページへ戻るようにしたかった。いろいろ悩んだけど以下のようになった。

app/views/growth_records/create.html.erb
<%= link_to '詳細ページ', user_plant_path(current_user.id,@plant.id) %> 
app/views/growth_records/destroy.html.erbまたはupdate.html.erb
<%= link_to '詳細ページ', user_plant_path(current_user.id,@growth_record.plant_id) %> 

pathの指定で()の中に複数の値を指定できることがわかった。

binding.pryでデバッグするのが便利

 やっとおわった。ちょっと詰まったところはbinding.pryをつかって中の変数を観察するようにした。するとうまくいった。

 単純にエラー文をググるだけでは全然うまくいかない。OKだったときとエラーができるときの差分を自分の中ではっきりと持つことが重要だった。そうして変数をいじったり場合を分けたりしてその差分を徐々に詰めていく…。

 「この場合ではOKだったけどこうしたらNGだった」そんなイメージで。

 一区切りついた。次はrspecのテストを試してみよう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

railsアプリで simple calendar を使ったページにて、undefined local variable or method `block' for

環境

ruby 2.5.1
rails 5.2.4

gem 'simple_calendar', '~> 2.0'

背景

  1. Docker+ CircleCiを導入し、ローカルでCircleCIによるrspecが正しく機能するかテスト
  2. 以前にはなかった箇所でrspecがfalseになる
  3. simple-calendarを使ったページにて、エラーが発生していた
ActionView::Template::Error - undefined local variable or method `block' for #<#<Class:0x00007ffddca4a6a0>:0x00007ffddc568d58>:
  app/views/simple_calendar/_month_calendar.html.erb:25:in `view template'
  app/views/application/_calendar.html.erb:1:in `_app_views_application__calendar_html_erb___1293910406710393037_70364149977560'
  app/views/events/index.html.erb:4:in `_app_views_events_index_html_erb___316186926983959569_70364150058740'

_month_calendar.html.erb
    <% 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?(block) %>
                <% capture_haml(day, sorted_events.fetch(day, []), &block) %>
              <% else %>
                <% block.call day, sorted_events.fetch(day, []) %> #この行にある"block"が定義されていないらしい
              <% end %>
            <% end %>
          <% end %>
        </tr>
      <% end %>

試したこと

1.以下のsimle calenderのviewを生成するコマンドで初期のhtmlを作成

rails g simple_calendar:views

2.カスタマイズしていたhtmlを初期のhtmlに置き換える
3.正常にカレンダーが表示された

結論

HTMLの記述が違っていた。
"block" ではなく、 "passed_block" にしなければならない

_month_calendar.html
#エラーが出たhtml
<% block.call day, sorted_events.fetch(day, []) %>


# 正常に動作するhtml
<% passed_block.call day, sorted_events.fetch(day, []) %>

"passed_"の部分を間違って消してしまったのか???
githubで過去に正常に動いていた時のバージョンを調べても、"block" と記載となっていたのだが、、、
simple calendar側で変更があったのだろうか?

simple calendar 側で view の一部に変更があった

simple calendarのgithubを調べたところ、最新の変更にて"block"から"passed_block" になっていた。
simple calendarのgithub

app/views/simple_calendar/_month_calendar.html.erb
        <tr>
          <% 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?(block) %>
 -              <% capture_haml(day, sorted_events.fetch(day, []), &block) %>
 +             <% if defined?(Haml) && respond_to?(:block_is_haml?) && block_is_haml?(passed_block) %>
 +              <% capture_haml(day, sorted_events.fetch(day, []), &passed_block) %>
              <% else %>
 -              <% block.call day, sorted_events.fetch(day, []) %> # 削除された
 +              <% passed_block.call day, sorted_events.fetch(day, []) %> # 追加された
              <% end %>
            <% end %>
          <% end %>

こういったgem側の変更によって、自分のアプリに影響が及ぶケースは, "impressionist"と言うgemでのエラーで経験していたので、
そんなに詰まることなく、原因究明できました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む