- 投稿日:2020-09-07T23:54:15+09:00
【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 endrails db:migrate最後に、対象モデルに acts_as_paranoid を追記します。
Event.rbclass Event < ApplicationRecord acts_as_paranoid end導入完了!!
step2: ルーティングを設定する
routes.rbRails.application.routes.draw do root "events#index" resources :events do member do delete 'finish' end end endresourcesで生成される七つのアクションの中に終了するアクションは無いのでイベントにネストする形でfinishアクションのルーティングを設定します。
step:3 コントローラーにfinishアクションを定義する
events_controller.rbdef 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
と記述していますが実際データーベースにどの様な形で残っているのでしょうか?
destroy
されたレコードは画像の様にdeleted_atというカラムに論理削除された時間が残ります。step:4 論理削除されたデータをfindメソッドで取ってくる
users_controller.rbdef show @user=User.find(params[:id]) @events = @user.events.only_deleted endユーザーの詳細ページで過去のイベントを表示したいのでユーザーコントローラーに
only_deleted
と記述することで論理削除されたレコードだけを取ってくることができます。最後に、、
paranoiaは他にも色々なメソッドが使えるみたいなので是非、公式ドキュメントを参考にしてください^^
公式リンク(https://github.com/rubysherpas/paranoia)最後まで見て頂きありがとうございました^^
- 投稿日:2020-09-07T23:10:36+09:00
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-x64
,2.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\sqlite3
に2.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.rbRUBY_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
にアクセスして動作確認
- 投稿日:2020-09-07T22:34:38+09:00
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.rbdef 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となります。
ここらへんのことはまだふわっとしているので次の記事でまとめたいと思います。
- 投稿日:2020-09-07T22:00:51+09:00
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"を押下します。
❸そしてgithub desktopの画面に戻ってhistoryを見ていきます。
❹”1(1以上になっている)⬇︎”になっていると思うのでそれをmasterに統合します。
写真では0⬇︎になっていますが、エラーが起きていた際は1⬇︎になっていました。その証拠に隣が1⬆︎になっています。
そうすることで再度、ターミナルで” git push heroku master”してあげるとうまく行きました。そこで、いろいろと思い返しました。
Pay.jpでjavascriptに環境変数を記載して、herokuにpushする際にわざとプログラムを変更して読み込ませるようにしました。変更を加えた際に、ブランチを作ったような気がしました。やはり気のせいではなくブランチを作ってありました。
- 投稿日:2020-09-07T20:02:27+09:00
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のヘルパーメソッドで回避できる。
- 投稿日:2020-09-07T19:20:34+09:00
CircleCI: parallelismを使うときにSimpleCovの結果をまとめ、Artifactsに置く
CircleCIでparallelismを使ってRspecを並列実行すると、SimpleCovの結果がそれぞれのcoverageディレクトリにばらばらに出力されてしまいます。これを1つにまとめ、さらにCircleCI上で結果のHTMLが見えるようにしたい。
参考にしたページ:
- CircleCI で並列実行したRSpecのカバレッジを Coveralls に送る
- Merging test runs under different execution environments
- Storing Build Artifacts - CircleCI
複数のジョブのテスト結果を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.ymltest: 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-*.jsonSimpleCovの結果をまとめるタスク
最近のSimpleCovには、複数のJSONから1つのHTMLを出力する機能ができましたので、これをそのまま使うrakeファイルを書きます。
lib/tasks/coverage_report.rakenamespace :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.ymljobs: 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記事:
- 投稿日:2020-09-07T19:16:35+09:00
railsのハッシュタグ検索の実装
今回は、railsでハッシュタグ機能を実装しました。
https://glodia.jp/blog/3936/
のサイトを参考にさせていただきました。完成図
商品を出品する時にタグをつけると実装方法
1 商品テーブルitemsとタグテーブルtagsは中間テーブルで結びついている
2 ヘルパーに、タグ名をリンクにするための記述をするitems_helper.rbdef 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.rbget '/item/hashtag/:name', to: "items#hashtag"本来はitemコントローラーの中にネストするのがベストだと思います
4 コントローラーの実装
items_controller.rbdef hashtag @tag = Tag.find(params[:tag_id]) @items = @tag.items end2でリンクの中に仕込んだtag_idをparamsで渡し、該当するタグを探す
該当するタグを持つ商品を変数に入れる5 投稿詳細一覧を作る時と同様、eachメソッドを@itemsを使用してビューを実装
完成です!
- 投稿日:2020-09-07T18:53:17+09:00
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/bashRailsの導入
作業ディレクトリへ移動します。
$ 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 nodejsWebpackerをインストールしますが、その前に
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 yarnWebpackerをインストール。
rails webpacker:installRails 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!
おわり
参考文献: 知識0から始めるRails on Docker
- 投稿日:2020-09-07T18:01:51+09:00
preventDefaultはなぜ必要なのか
動作環境
Ruby 2.6.5
Rails 6.0.3.2JavaScriptにおける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の流れを妨害するもの」と捉えております。
- 投稿日:2020-09-07T16:37:16+09:00
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を使用した方がいいとのことでした
- 投稿日:2020-09-07T15:13:30+09:00
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を使用したテストについて学んで行きたい。
- 投稿日:2020-09-07T14:03:07+09:00
enumからセレクトボックスを作る際に、表示する文言を変えたいとき
あまり無いケースだとは思うが、enumからセレクトボックスを作成する際に、文言を変えたい場合の方法をメモしておく。
前提として、gemにenumerize
とreform
を使用している。product_form.rbextend Enumerize enumerize :fluit, in: { apple: 1, orange: 2, peach: 3, strawberry: 4, cherry: 5, }, predicates: { prefix: true } property :fluitja.ymlproduct: 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.rbdef fruit_select_display self.class.fluit.options.map do |key, value| if value == 'apple' || value == 'cherry' [key + '(特別セール中)', value] else [key, value] end end endnew.slim= f.select :fluit, f.object.fruit_select_display, {prompt: '果物を選択'}これでいったん用は足せた。
- 投稿日:2020-09-07T11:02:37+09:00
Railsで現在いるページの判定をする
- 投稿日:2020-09-07T11:02:37+09:00
Ruby on Railsで現在いるページの判定をする
- 投稿日:2020-09-07T09:53:35+09:00
[Rails]モデルの単体テスト実装方法
前提条件
今回は新規ユーザー登録に関するモデルの単体テストを実装していきながら、方法をアウトプットします。ユーザー登録の仕様書は以下のとおり。
ニックネームが必須 メールアドレスは一意である メールアドレスは@とドメインを含む必要がある パスワードが必須 パスワードは7文字以上 パスワードは確認用を含めて2回入力する ユーザー本名が、名字と名前でそれぞれ必須 ユーザー本名は全角で入力させる ユーザー本名のフリガナが、名字と名前でそれぞれ必須 ユーザー本名のフリガナは全角で入力させる 生年月日が必須マイグレーションファイルは以下のとおり。
20200823050614_devise_create_users.rbclass 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.rbclass 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.rbgroup :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 failuresfactory_botを導入
続いてfactory_botを導入します。
Gemをインストール。Gemfile.rbgroup :development, :test do # 省略 gem 'rspec-rails' gem 'factory_bot_rails' endその後、bundle installを実行。
factory_botの実装
次に、specディレクトリ直下に
factories
というディレクトリを作成し,その中に、作成したインスタンスの複数形のファイル名でRubyのファイルを作成します。今回の場合は、users.rb
になります。
users.rb
を以下のように編集。users.rbFactoryBot.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.rbclass 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/ } # ここがフリガナ全角の正規表現 # 省略 endRubyの正規表現は下記サイトを参考にしてください。
https://gist.github.com/nashirox/38323d5b51063ede1d41テストコードの実装
それではいよいよテストコードの実装です!
テスコードを記述するときに心掛ける点は以下のとおり。①各exampleで期待する値は1つ ②期待する結果をはっきりわかりやすく記述 ③起きて欲しいことと起きてほしくないこと両方をテストする ④可読性を考えつつ、適度にDRYにするuser_spec.rbに以下のコードを記述。
user_spec.rbrequire 'rails_helper' describe User do describe '#create' do end end今回はUserモデルの単体テストなので
describe User do
となり、新規ユーザーを作成するのでdescribe '#create' do
としており、2行目のdescribの中にテストコードを記述します。まず、全ての項目の入力が存在すれば登録できることをテストします。
コードとその解説は以下の通り。user_spec.rbrequire '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.rbrequire '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.rbrequire '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.rbrequire '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.rbrequire '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.rbrequire '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.rbrequire '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すると下記画像のように処理が行われればテストが成功しています。
最後に
モデルの単体テストは基本文法と、エラーを発生させる処理と発生させない処理を把握し、それぞれのメソッドを理解することができれば新たに機能が追加された場合でもコードを記述できると思います。
何か気になる点がございましたらコメント欄にてお申し付けください!
最後までご覧いただきありがとうございました!
- 投稿日:2020-09-07T09:39:05+09:00
byebugの導入から使い方まで
Railsの勉強の際にデバッグツールは使っていますか?
デバッグツールとは、プログラミングにつきもののデバッグ作業をサポートしてくれるツールのことで、ステップ実行といって一行ごとにプログラムを実行してその時に宣言されている変数の中身をみながら動作を確認する事が出来ます。それではRailsの開発に使っているbyebugについて紹介していきます。
まずはbyebugのgemをインストールして、デバックしたいコードに「byebug」というメソッドを仕込んでおくと、プログラムがそこを通過した時に止まり、デバッグモードが開始、対話形式でコマンドを打ち込んでおくと自由にメソッドを実行したり変数の値を確認したり出来ます。
インストール方法
インストールはGemfaileに
gem 'byebug'を追記するだけです。 (railsをインストールしている場合はデフォルトでインストールされています)
追記したら
bundle installでgemをインストールしてきます。
byebugの使い方
まずbyebugを開始するには、デバッグをしたいところにbyebugというメソッドを書いて上げます。
(今回の場所は例となります。)今回はstatic_pages/homeページの部分にbyebugを挿入してみます。
byebugを挟み込んだら、実際にメソッドを叩いてみます。
このように処理が止まり入力プロンプトが出てきます。
ここでnextを入力してEnterを押すと次の行に移動しました。ここでhelpメソッドを調べる為に入力してEnterを押すと
helpの中身を確認する事が出来ます。(変数の中身も見れます)
実際のデバッグ作業ではこのように変数の値やメソッドを実行してみて、期待通りの動きをしているかを確認していきます。よく使うコマンド一覧
next 一行進む continue 次のブレイクポイントに進む step メソッドの内部にステップインする list ソースコードを表示する up ソースコードの上を表示する down ソースコードの下を表示する他にも使ってみたいコマンドがあればhelpを入力すると表示されるので確認してみて下さい♪
ここまで覚えてしまえば、あとは実際に書いてみて慣れるのが一番早いと思います。
まとめ
byebugの紹介をしましたが、いかがでしたでしょうか?
byebugではそれぞれの変数の型も見れるのでブラックボックス化しがちなプログラミングの細かいところを見れるようになり、開発スピードの高速化と一石二鳥です。慣れればとても使えるツールだと思うのでぜひ使ってみてください♪
駆け出しエンジニアなので間違っている部分やわかりにくい点などあるかと思います。
その場合はコメントなど入れていただけるとこれからの成長につながりますのでよろしければお願いします。
【LGTM】を押していただけると飛び跳ねて喜びます。
Twitteの方でも呟いたりしておりますのでもしよろしければ遊びにきてください♪
- 投稿日:2020-09-07T08:46:54+09:00
Rails 6で認証認可入り掲示板APIを構築する #2 gitとrubocop導入
←Rails 6で認証認可入り掲示板APIを構築する #1 環境構築
Githubに上げる
前回の続きから。
とりあえず動くところまでいったのでGithubにpushしましょう。New repositoryを選択。
その後適当な名前を付けて作成。
他の人からソースコードを見られたくなければPrivateに。問題なければPublicに。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 rsaEnterキー連打でキーペアを作ります。
$ 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' ...インストール$ bundlerubocop設定ファイルを作る
.rubocop.ymlAllCops: 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.rbmodule Bbs + # + # 設定ファイル + # class Application < Rails::Application
のようにclass定義の前にコメントを追加しましょう。
全て修正し、
Inspecting 26 files .......................... 26 files inspected, no offenses detectedのようにエラーがなくなればOKです。
→Rails 6で認証認可入り掲示板APIを構築する #3 RSpec, FactoryBot導入しpostモデルを作る
【連載目次へ】
- 投稿日:2020-09-07T06:07:56+09:00
【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.rbFactoryBot.define do factory :task do end end雛形ができましたね。この中にデータを定義していきましょう。
spec/factories/tasks.rbFactoryBot.define do factory :task do name "お買い物" content "夕飯を買いに行く" end end少ないかもしれませんが、まあ大丈夫でしょう。 テストファイル内で
FactoryBot.create(:task)
と実行することで、spec/factories/tasks.rb
に記述した内容のデータを作成することができます。FactoryBotを少しだけ使いこなす。
オーバーライドさせる
例えば、
task
のname
がnil
だった時にエラーを出力させる異常系のテストを書きたい場合は、下記のように書けます。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.rbFactoryBot.define do factory :user do name "山田" sequence(:email) { |n| "test#{n}@example.com" } end endこのように定義することによって、
test1@example.com
、test2@example.com
のように設定されます。とても便利。
- 投稿日:2020-09-07T05:25:56+09:00
Railsでワクワク個人開発 第5回: CRUD処理の規制と温度/湿度の登録
他ユーザーがCRUD処理できないようにする
少しだけ涼しくなってきた。朝の開発が心地よくなっている。今のコードでは自分以外のユーザーでも植物のCRUD処理ができてしまうので、それをなくしていこう。
plant_controller.rb
へbefore action
を追加するapp/controllers/plants_controller.rbclass 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 #----中略---- endbefore_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.rbclass 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 endtemp(温度)とhumid(湿度)はfloat型で小数点を記録できるようにした。record_timeはまだ実装していないのでnullでも大丈夫なようにした。
次は
rails g controller growth_reocords
でコントローラーを作成する。app/controllers/growth_records_controller.rbclass GrowthRecordsController < ApplicationController def index @user = User.find(params[:user_id]) @plants = @user.plants @growth_record = @plants.growth_record end endmodelどうしの関連づけをする必要がある。
app/models/growth_record.rbclass GrowthRecord < ApplicationRecord belongs_to :plant endapp/models/plant.rbclass 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.rbRails.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のテストを試してみよう。
- 投稿日:2020-09-07T01:28:45+09:00
railsアプリで simple calendar を使ったページにて、undefined local variable or method `block' for
環境
ruby 2.5.1 rails 5.2.4 gem 'simple_calendar', '~> 2.0'背景
- Docker+ CircleCiを導入し、ローカルでCircleCIによるrspecが正しく機能するかテスト
- 以前にはなかった箇所でrspecがfalseになる
- 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:views2.カスタマイズしていた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のgithubapp/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でのエラーで経験していたので、
そんなに詰まることなく、原因究明できました。