20210916のRubyに関する記事は9件です。

[Rails]Gemを使ってモーダル作成

はじめに 本記事では、Gemを使ったモーダルの作成方法を記述します。 参考動画 ブックマークしているサイトがもろ見えになるので、上は切りました。 「決して」やましいものはありません。 流れ gemのインストールです。 gem 'data-confirm-modal' からの bundle install からの application.js // = require data-confirm-modal コメントアウトでOK <%= link_to food_path(food.id), method: :delete, data: { confirm: '本当に削除しますか?削除すると投稿の復元はできません', cancel: 'やめる', commit: '削除する'}, title: '削除確認' do %> <i class="fas fa-trash-alt"></i> <% end %> dataは{}、titleは{}には入れないようにそれぞれ注意ですね。 検索ボタン押したら検索フォームがブワッと出てくるような おしゃれなモーダルを作成する場合は、CSSとjQueryを使ったモーダルでいいと多いますが、 今回のような削除は、このGem使った方が、 え?ほんとに削除するの?感が出るので、私はこっちが好きです。 適材適所ってやつですかね。 難しいと思ったら思いの外簡単にできたので、覚えておきたいところです。 以上です。 終わりに 色々なGemがあるんですね。 もっといろんなGemを知ってアプリケーションを良くする武器を揃えたいです。 以下参考サイトです。 Ruby on Railsで削除ボタンを押したときに確認ダイアログを表示させる方法 data: {confirm: }の確認ダイアログをいい感じにする【Ruby on Rails】 明日も頑張ります!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

知っておきたいincludesの正体【Rails】

rails 6.0.2 ruby 2.7.1 includesとは N+1問題を起こさないように、関連テーブルのデータをキャッシュしてくれるメソッド。 しかし、関連テーブルのデータをキャッシュするといっても 一体どのようにして、データを取得してきているのだろうか。 実はincludesは、モデルの関連など見て以下のどちらかの最適とrailsが判断したメソッドを実行している。 (このrailsの判断が正しいとは限らない。*後述) preload eager_load 関連ごとにSQLを発行してキャッシュ。  left_joinを用いて、キャッシュ。 preload、eager_loadがどの様なSQLを発行してるか見ていく。 以下の様なモデルがあるとする。 user.rb class User < ApplicationRecord belong_to :country #国 has_many :posts #投稿 end preload preloadは、関連ごとにSQLを発行してキャッシュする。 いったいどういうことかというと、以下のようなrubyコードでは User.all.preload(:posts,:country) 以下のようなsqlが発行される。  --user.allのsql select * from users; --postsのキャッシュのsql in句は、user.id select * from posts where user_id in (1,2,3,4,5,6.....); --countryのキャッシュのsql in句は、user.county_id select * from countries where id in (1,2,3,4,5,6.....); eager_load left_joinを用いて、キャッシュ。 いったいどういうことかというと、以下のようなrubyコードでは User.all.eager_load(:posts,:country) 以下のようなsqlが発行される。  --user.allのsql兼、postsのキャッシュ兼、countryのキャッシュ select * from users u left join posts p on p.user_id = u.id left join countries c c.id = u.countries ; 以上の様にsqlが発行される訳だが、 railsのincludesがpreload,eager_loadの使い分けを完璧にできる訳ではない。 例えば、以下のように複数のテーブルをキャッシュしたい時、 countryだけ、preloadで他は全てeager_loadみたいなことは出来ない。 全部preload or 全部eager_load のどちらかである。  User.all.includes(posts: [:comments],:country,:profies) preloadでキャッシャしたらまずいところ eager_loadでキャッシュしたらまずいところがそれぞれあり、 railsでも使い分けをしている訳だが、 このようなことが起きると、意図せぬところでパフォーマンスの低下が起きてしまう。 したがって、各メリットデメリットを理解しておく必要がある。 メリットデメリット preload メリット 1.メモリの圧迫を防げる。  例えば、中間テーブルを持つN対Nの関係のテーブルをeaager_loadを使ってキャッシュした場合。  その実行結果のレコード数は単純に取得したかったデータ量よりもはるかに多くなってしまい、メモリを圧迫してしまうこ とになる。   デメリット  ① IN句が大きくなりすぎる場合がある。  例えば、例で用いたrubyコードのUser.allが1万件あった場合、in句が膨大な長さになり、  以下の様なことが起きる。  ・ ネットワークI/Oを圧迫する。  ・ rdbmsによってin句に指定できる数には限界がある、Oracleは、1000個らしい。 参考 https://oreno-it.info/archives/816 ② 関連先テーブルを使って絞り込みなどができない。 別のsqlでキャッシュをしてるので、関連テーブルのカラムをwhereで使うことはできない。 つまり以下の様なrubyコードはエラーになる。 irb(main)> User.preload(:country).where(countries: {name: '日本'}) ActiveRecord::StatementInvalid (PG::UndefinedTable: ERROR: missing FROM-clause entry for table "members") LINE 1: SELECT "users".* FROM "users" WHERE "members"."name" = $1 LI... eager_load メリット ① 関連先テーブルを使って絞り込みなどが出来る。 以下が実行可能。 irb(main)> User.eager_load(:country).where(countries: {name: '日本'}) ② 一対一,N対一の関連テーブルを取得するのに良い。  ・一対一,N対一の関連の場合、結合の結果のレコードが増えるわけではないので、   結合によるメモリの圧迫は少ない。  ・sqlの発行回数が少ない。(通信回数、rdbmsの構文解析などの時間が減る。) デメリット ① 関連がN対ー,N対Nの場合のパフォーマンス left_joinなので無駄なデータを沢山とってきてしまい、パフォーマンスは下がる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】同名のパスから異なるviewを表示する方法

rails初学者によるポートフォリオ作成中の学びのアウトプットです。 実現したい事 クイズアプリを作成中に複数のモードを全てquestionモデルとanswerモデルで賄いたい(ジャンルAのクイズ集とジャンルBのクイズ集を実装する)。下記の二つのページのようにnew_answer_pathからアクセスした時に異なるジャンルの問題が表示されるようにしたい。 (モードA) ジャンルAの問題を表示 回答欄(answerモデルに保存される) (モードB) ジャンルBの問題を表示 回答欄 実装の流れ ①questionモデルにmode_numカラム(integer)を追加して問題のジャンルを番号で識別するようにする。 ②new_answer_path(mode_num: <識別番号>)とし、newアクションにmode_numを渡すようにする。 ③newアクションで渡された識別番号からquestionモデルを検索するよう実装する。 実際のコード (②のステップから) 問題ジャンルAはquestion.mode_numを1に設定。 問題ジャンルBはquestion.mode_numを2に設定。 home.html.erb <%= link_to "問題を解く", new_answer_path(mode_num: 1) %> . . . <%= link_to "問題を解く", new_answer_path(mode_num: 2) %> answer_controller.rb def new @questions = Question.where(mode_num: params[:mode_num]) @question = @questions.find(@questions.pluck(:id).sample ) @answer = @question.answers.new end @questions = Question.where(mode_num: params[:mode_num])で受け取ったmode_numから該当するジャンルのquestionを配列で取得。 @question = @questions.find(@questions.pluck(:id).sample )で 取得した配列の中からランダムで一つのquestionを取得してそれをviewに表示する。 new.html.erb <%= @question.content %> <%= form_with(model: @answer, local: true) do |f| %> <%= f.text_area :content %> <%= f.submit "次の問題へ進む" %> <% end %> これでhome.html.erbの上のリンクと下のリンクからそれぞれアクセスした際、 <%= @question.content %>に違う内容が表示される。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】同名のパスのviewで表示するインスタンスを使い分ける

rails初学者によるポートフォリオ作成中の学びのアウトプットです。 実現したい事 クイズアプリを作成中に複数のモードを全てquestionモデルとanswerモデルで賄いたい(ジャンルAのクイズ集とジャンルBのクイズ集を実装する)。下記の二つのページのようにnew_answer_pathからアクセスした時に異なるジャンルの問題が表示されるようにしたい。 (モードA) ジャンルAの問題を表示 回答欄(answerモデルに保存される) (モードB) ジャンルBの問題を表示 回答欄 実装の流れ ①questionモデルにmode_numカラム(integer)を追加して問題のジャンルを番号で識別するようにする。 ②new_answer_path(mode_num: <識別番号>)とし、newアクションにmode_numを渡すようにする。 ③newアクションで渡された識別番号からquestionモデルを検索するよう実装する。 実際のコード (②のステップから) 問題ジャンルAはquestion.mode_numを1に設定。 問題ジャンルBはquestion.mode_numを2に設定。 home.html.erb <%= link_to "問題を解く", new_answer_path(mode_num: 1) %> . . . <%= link_to "問題を解く", new_answer_path(mode_num: 2) %> answer_controller.rb def new @questions = Question.where(mode_num: params[:mode_num]) @question = @questions.find(@questions.pluck(:id).sample ) @answer = @question.answers.new end @questions = Question.where(mode_num: params[:mode_num])で受け取ったmode_numから該当するジャンルのquestionを配列で取得。 @question = @questions.find(@questions.pluck(:id).sample )で 取得した配列の中からランダムで一つのquestionを取得してそれをviewに表示する。 new.html.erb <%= @question.content %> <%= form_with(model: @answer, local: true) do |f| %> <%= f.text_area :content %> <%= f.submit "次の問題へ進む" %> <% end %> これでhome.html.erbの上のリンクと下のリンクからそれぞれアクセスした際、 <%= @question.content %>に違う内容が表示される。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails の Custom Validator を RSpec で柔軟かつ簡単にテストする方法

はじめに カスタムバリデータのテストを RSpec で記述するにあたり、いくつかの技術記事を参考にしたが、どれも一部情報が欠けているものばかりだった。 そこで、カスタムバリデータのテストの書き方について、個人的につまずいた部分を解消したものをまとめておく。 目標 以下のようなカスタムバリデータがあり、そのテストコードを RSpec で記述できるようにする。 app/validators/inclusion_in_array_validator.rb # frozen_string_literal: true # 配列の中身に特定の値が含まれている、または、特定の値の範囲内かを検証するカスタムバリデーション class InclusionInArrayValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) return if value.all? { |v| options[:in].include?(v) } record.errors.add(attribute, options[:message] || :inclusion_in_array) end end ちなみに、使い方としては、以下のようなものを想定している。 validates :prefecture, inclusion_in_array: { in: (1..47) } 都道府県を複数選択し、[1, 13, 27, 47] のような配列が送信されたとき、各々の数値が都道府県 ID の範囲内に含まれるか検証するカスタムバリデータである。 CustomValidatorHelper の作成 カスタムバリデータを単体でテストするためには、ダミーモデルを生成し、それを呼び出す。 いくつかのカスタムバリデータを実装している場合は、それぞれに全く同じダミーモデル生成の処理を書くのは DRY ではない。 そこで、CustomValidatorHelper というヘルパーを作り、そこにダミーモデルを生成する処理を書く。 spec/support/helper/custom_validator_helper.rb というファイルを生成し、以下のコードを書く。 spec/support/helper/custom_validator_helper.rb # frozen_string_literal: true # カスタムバリデータを簡単にテストできるようにするためのモジュール module CustomValidatorHelper def build_validator_mock(attribute: nil, record: nil, validator: nil, options: nil) record ||= :record attribute ||= :attribute validator ||= self.described_class.to_s.underscore.gsub(/_validator\Z/, '').to_sym options ||= true Struct.new(attribute, record, keyword_init: true) do include ActiveModel::Validations def self.name 'DummyModel' end validates attribute, validator => options end end end RSpec.configure do |config| config.include CustomValidatorHelper, type: :model end CustomValidatorHelper をロード CustomValidatorHelper を用意しただけでは、各 spec ファイルで読み込んでくれない。そのため、全 spec 内で CustomValidatorHelper が自動的に読み込まれるようにする。 spec/rails_helper.rb に以下のコードを追加する。 spec/rails_helper.rb RSpec.configure do |config| Dir[Rails.root.join('spec/support/config/*.rb')].each { |f| require f } Dir[Rails.root.join('spec/support/helper/*.rb')].each { |f| require f } end RSpec.configure do |config| ... end はすでに書かれていると思うので、そのブロック内の任意の箇所 (末尾など) に中身を記述すれば良い。 使い方 目標 の項で提示した InclusionInArrayValidator のテストを以下に示す。 spec/validators/inclusion_in_array_validator_spec.rb # frozen_string_literal: true require 'rails_helper' RSpec.describe InclusionInArrayValidator, type: :model do describe '#validate_each' do # valid? または invalid? を呼び出さないとエラーメッセージが取得できないので before do mock.valid? end let(:mock) { build_validator_mock(options: options).new(attribute: value) } # 例として都道府県を想定 (1 〜 47 まで) context '正常な場合' do context '範囲内の整数を複数指定した場合' do let(:value) { [1, 13, 27, 47] } let(:options) { { in: (1..47) } } specify 'バリデーションを通過すること' do expect(mock).to be_valid end end end context '異常な場合' do context '0 を指定した場合' do let(:value) { [0] } let(:options) { { in: (1..47) } } specify 'バリデーションを通過しないこと' do expect(mock).to be_invalid end specify 'エラーメッセージが表示されること' do expect(mock.errors.added?(:attribute, :inclusion_in_array)).to be_truthy end end context '負の数を指定した場合' do let(:value) { [-5] } let(:options) { { in: (1..47) } } specify 'バリデーションを通過しないこと' do expect(mock).to be_invalid end specify 'エラーメッセージが表示されること' do expect(mock.errors.added?(:attribute, :inclusion_in_array)).to be_truthy end end end end end 注目すべき点は以下の 1 行だけ。 let(:mock) { build_validator_mock(options: options).new(attribute: value) } 説明のわかりやすさのため、options と value に適当な値を入れたものを以下に示す。 let(:mock) { build_validator_mock(options: { in: (1..47) }).new(attribute: [1, 13, 27, 47]) } 先ほど作成した CustomValidatorHelper の build_validator_mock メソッドを呼び出している。 このメソッドを呼び出すことにより、アプリケーション内で以下のようにバリデータを呼び出したことになる。 validates :attribute, inclusion_in_array: { in: (1..47) } attribute や inclusion_in_array がどこから出てきたのか不思議に思うかもしれないが、これは build_validator_mock メソッド内で暗黙的に値を設定しているためである。 InclusionInArrayValidator のテストを行いたいため、validates に渡す第 2 引数のキーの名前は必然的に inclusion_in_array となる。それを build_validator_mock メソッドで暗黙的に行っている。 これらを明示的に指定したい場合は、以下のようにする。 let(:mock) do build_validator_mock(attribute: :prefecture, validator: :inclusion_in_array, options: { in: (1..47) }). new(prefecture: [1, 13, 27, 47]) end attribute の値を :prefecture に変えたので、attribute: [1, 13, 27, 47] の部分が prefecture: [1, 13, 27, 47] になったことに注意すること。 すると、以下のようにバリデータを呼び出したことになる。 validates :prefecture, inclusion_in_array: { in: (1..47) } 基本的に、カスタムバリデータ単体のテストを書くときは、属性名 (DB に紐付いている場合はカラム名のこと) は何でも良いはず1なので、通常は省略して構わない。 また、呼び出すバリデータの名称に関しても、RSpec.describe InclusionInArrayValidator, type: :model do ... end のように書いたら、ふつうは inclusion_in_array で呼び出すはずなので、こちらも省略して構わない。 options を省略した場合 なお、options: { in: (1..47) } の部分を省略して、 let(:mock) { build_validator_mock.new(attribute: [1, 13, 27, 47]) } のように書いた場合、以下のようにバリデータを呼び出したことになる。 validates :prefecture, inclusion_in_array: true options を省略すると、代わりに true が入る。ここに指定する値として、よく true が来ることが多い印象なので、省略時は true が入るように CustomValidatorHelper で実装している。 他の属性 (カラム) と依存関係のあるカスタムバリデータをテストしたい場合 今までの説明 (都道府県 ID) のように、1 つの属性単体で完結する場合はこれで十分だろう。しかし、他の属性と依存関係がある場合はどのように書けば良いだろうか。 たとえば、サブカテゴリという属性があり、それがメインカテゴリと依存関係にあった場合のことを考える。 サブカテゴリ ID はメインカテゴリ ID と関係があり、ユーザから送信されるサブカテゴリ ID は、同じくユーザから送信されるメインカテゴリ ID に属しているものでなければならないとする。 もう少し具体的に説明するため、以下のリスト (以下、※ 1 とする) を参照してほしい。 ゲーム (ID: 1) アクション (ID: 1) パズル (ID: 2) RPG (ID: 3) アニメ (ID: 2) 日常系 (ID: 4) アドベンチャー (ID: 5) ほのぼの (ID: 6) ... (ID: 3) ... (ID: 7) ... (ID: 8) ... (ID: 9) それぞれ、ネストされていないのがメインカテゴリを表し、ネストされているのがサブカテゴリを表している。 ユーザからはメインカテゴリ ID とサブカテゴリ ID が送られてくるが、サブカテゴリ ID が、メインカテゴリ ID に属している必要がある。 たとえば、メインカテゴリで「ゲーム」(ID: 1) を選択しているのに、サブカテゴリで「ほのぼの」(ID: 6) を選択していたら弾くようなバリデータを想定している。 これを、以下のように実装したとする。 app/validators/sub_category_dependency_validator.rb # frozen_string_literal: true # 指定されたサブカテゴリの ID が正しいかどうかをチェックするカスタムバリデーション # # 送信されたすべてのサブカテゴリ ID からメインカテゴリ ID を調べた際に # それらがすべて、送信されたメインカテゴリ ID に含まれていれば OK # そうでなければメインカテゴリとサブカテゴリに不整合が起きているので弾く # class SubCategoryDependencyValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) return if (SubCategory.find(value).pluck(:main_category_id) - record.main_category).empty? record.errors.add(attribute, options[:message] || :sub_category_dependency) end end 送信されたサブカテゴリに対してバリデーションをかけるカスタムバリデータの中で、送信されたメインカテゴリを参照している。record.main_category の部分が該当する。 その際、メインカテゴリをテスト内でどのように指定するか、ということである。 その場合は、build_validator_mock を以下のように呼び出せば良い。 let(:mock) do build_validator_mock(attribute: :sub_category, record: :main_category). new(sub_category: [3, 6], main_category: [1, 2]) end build_validator_mock を呼び出す際の引数として、record: :main_category というものを追加する。 そして、attribute と record に指定したそれぞれのキーを使って、new(sub_category: [3, 6], main_category: [1, 2]) のようにインスタンスを生成する。 これにより、バリデータ内で、value には、attribute として指定した sub_category の値 ([3, 6]) が入り、record.main_category には、record として指定した main_category の値 ([1, 2]) が入ることになる。 ちなみに、上記のサブカテゴリ ID は、※ 1 のリストによれば、メインカテゴリ ID に属しているので、正常系となる。 new(sub_category: [3, 6], main_category: [1, 2]) の部分を、たとえば new(sub_category: [6], main_category: [1]) のようにした場合、メインカテゴリ「ゲーム」と、メインカテゴリ「ゲーム」に属さない (メインカテゴリ「アニメ」に属する) サブカテゴリ「ほのぼの」が指定されているため、異常系となる。 参考サイト カスタムバリデータのテストの書き方について 【Rails】まだValidatorのテストで消耗してるの? Rails の Custom Validator をテストする RailsでCustom validatorをテストする RSpec 用のモジュールの置き場所と読み込み方について RSpecコトハジメ ~初期設定マニュアル~ Struct の文法について Class: Struct (Ruby 2.7.0) 一応、エラーメッセージ内に属性名が含まれるので、エラーメッセージの文字列が正しいかどうかをテストする際には関係してくるかもしれないが、そもそもエラーメッセージの文言をそのままテストで書くのはあまり良い作法ではないと個人的には思う。 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Ruby] AtCoder過去問 B - Chocolate

はじめに AtCoderの過去問B問題をRubyで解いてみました。 よろしくお願いします。 問題はこちらから確認してください↓ B - Chocolate まずは入力を受け取ります。 aは配列として受け取るようにしています。 答えはチョコの数なので変数chocoに0を代入しておきます。 n = gets.to_i d, x = gets.split.map(&:to_i) a = readlines.map(&:to_i) choco = 0 まずは法則を考えます。 私は割り算でこの問題を考えました。 ①まず、dをそれぞれの日数で割った時あまりなく、ちょうどで割り切れた場合はチョコを割った答えがチョコを食べた数です。 →この法則は入力例2の一人目や入力例3の最後のひと(6の人)に当てはめて計算してみたらわかります。 ②もし、dをそれぞれの日数で割った時、あまりがある場合は割った答え+1がチョコを食べた数です。 ③もし入力例1の3人目の人のように10の人はdを超えているので最初の1日目しかチョコを食べることができません。(10日後に食べる予定ですが、その日はこないため) 上記を踏まえてeach文でそれぞれの合宿参加者ごとに、条件分岐し、chocoに追加していきます。 n = gets.to_i d, x = gets.split.map(&:to_i) a = readlines.map(&:to_i) choco = 0 a.each do |i| if d % i == 0 choco += d / i elsif i <= d choco += d / i + 1 else choco += 1 end end そして最後に余ったxをchocoに追加したら、準備されていたチョコの数が出てきます。 n = gets.to_i d, x = gets.split.map(&:to_i) a = readlines.map(&:to_i) choco = 0 a.each do |i| if d % i == 0 choco += d / i elsif i <= d choco += d / i + 1 else choco += 1 end end puts choco += x
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ActiveStorage と variant と それから N+1

概要と対策手順 Active Storageを使ってポートフォリオを作成 作成後のレビューでActive StorageでN+1を確認する 原因はActive Storageのvariantだった 現環境では対処できないのと発覚。 variantをやめてCSSでの制御に変更し解決。 ※ポートフォリオの詳細に関してはこちらに投稿してあります。 実行環境 Ruby ( 2.7.2 ) Ruby on Rails ( 6.1.4.1 ) PostgreSQL Active Storage と N+1 対象になったのはこちら(記事の投稿者を示すユーザーアイコン) 当時のコントローラーの処理は下記 @articles = Article.published.includes(:tag_maps, :tags, :keeps, user: { avatar_attachment: :blob }).order(created_at: :desc).page(params[:page]).per(PER_PAGE) avatar取得時のN+1には下記を参考に対応しているつもりだった。 参考: Active StorageのN+1問題に対処する 原因は variant だった ローカルに接続した時のログを眺めていると ActiveStorage::VariantRecord Load が記事の表示件数分弱表示されていた。 アイコンの画像を場所ごとにサイズを分けて表示したかったのでapplication_helper.rbに下記の処理をしていた。 application_helper.rb def user_icon(user, size) case size when "mypage" user.avatar.variant(resize: "72x72").processed when "profile" user.avatar.variant(resize: "64x64").processed when "list" user.avatar.variant(resize: "25x25").processed end end このバリアント画像をN+1しているらしい。 現環境では対処できない 自分で調べてみたが座礁したので、相談をし調査継続 週刊RailsウォッチさんとRuby on Rails APIに辿り着き、下記を見て対処方法とvariantが原因のN+1だと気づく。 現在のActive Storageではvariantトラッキングで添付ファイルごとにvariantが存在するかどうかをチェックするクエリが走る。通常のRailsのN+1防止策(includes)ではこれを防止できない。 このプルリクはwith_all_variant_recordsメソッドを追加し、かつincludesがActive Storageの添付ファイルで期待どおり動作するようになる。 解決の糸口は見えてきたので、try&error で挑戦するも一向に解決できず。 scope :with_all_variant_records, -> { includes(blob: :variant_records) }という記述をみた際に先ほど参考にしたActive StorageのN+1問題に対処する内で出てきたscope :"with_attached_#{name}", -> { includes("#{name}_attachment": :blob) }を参考にしたがずっとundefind methods with_all_variant_recordsと表示される(今思うと感の良い人なら気付きそうなログですね) どうやら自分の使用しているRailsのverではwith_all_variant_recordsが実装されていないという事を教えてもらいました。(githubの英語訳を見てた時にRails7に対応する的な事が書いてあったのはこの事かと後で合点がいく) 確かに github の main にはscope :with_all_variant_records, -> { includes(blob: :variant_records) }が記載され 6.1.4.1の方には記載がなかった。(通りでundefind methodsな訳ですね) CSS制御に変更し 件の根本的な原因と現状は分かりましたが、現状の改善はできていません。 今回はvariantをやめてcssで調整する方向に転換しました。 変更手順 application_helper.rbで使用しているvariant(resize: "サイズ").processedの記述を削除 CSSで画像調整する記述を用意 CSSでの画像調整 今回はSCSSを利用 html.erb内でvariantを使用していた部分のクラス名を変更 同じ記述が増えるので@mixinと@includeを使用する @mixin icon_size($size: 25px) { border-radius: 50%; width: $size; height: $size; } /* アイコンサイズ */ .list-user-icon { @include icon_size; } .profile-user-icon { @include icon_size(64px); } .mypage-user-icon { @include icon_size(72px); } スッキリ!! 解決 これでログを見てもActiveStorage::VariantRecord Load が減りました。 無事解決!! 今回の件で解決方法を正規ルートに拘りすぎず、違う形でも解決できる方法も一緒に考えることができるようになる力も必要だなと感じました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【RSpec】結合テストを実施した際に出たchromedriverに関するエラーの解消方法

対象者 chromedriverに関するエラーが発生している方 RSpe結合テストを実施中の方 JavaScriptのテストが不要な方 GoogleChromeを使用中の方 目的 エラーを解消してRSpecテストを実行できるようにする 実際の手順と実例 1.前提 RSpecテスト実装 Ruby 2.6.3 Rails 5.2.5 Gemfileは下記の通り Gemfile. : : group :test do # Adds support for Capybara system testing and selenium driver gem 'capybara', '>= 2.15' gem 'selenium-webdriver' # Easy installation and use of chromedriver to run system tests with Chrome gem 'webdrivers' gem 'rspec-rails' gem 'factory_bot_rails' gem 'faker' end : : 2.現状(エラー内容) 下記のようなエラーが出ています。 調べてたところによると、chromedriver-helperというgemがないことが問題みたいです。 しかし、chromedriver-helperのサポートはgemは2019年3月31日に終了しています。 webdriversというgemへ移行してくださいとのことですが、 下記の記事を参考にさせて頂いた際にうまく行かなかったので、 渋々chromedriver-helperをアプリケーションにインストールすることにしました。 3.Chromedriverをインストールして解決する方法 Chromedriverのインストール rails_helper.rbの修正 上記2点を実行していきます。 1. Chromedriverのインストール まず手始めにGoogleChromeをupdateしていきます。 Terminal. $ sudo yum -y update $ sudo yum -y install google-chrome-stable $ sudo yum -y install libX11 GConf2 fontconfig libOSMesa google-noto-cjk-fonts $ google-chrome --version その後下記のコマンドでChromedriverをインストールしていきます。 Terminal. sudo yum -y install unzip curl -O -L https://chromedriver.storage.googleapis.com/85.0.4183.38/chromedriver_linux64.zip unzip chromedriver_linux64.zip sudo mv chromedriver /usr/local/bin rm chromedriver_linux64.zip chromedriver -v 最後のコマンドを実行するとChromedriverのバージョンが表示されます。バージョンは下記のサイトで確認できます。 ここまで実行したら再度、テストを実行します。 実行後は更に下記のようなエラーがでました。 Terminal. Selenium::WebDriver::Error::UnknownError: unknown error: Chrome failed to start: exited abnormally. (unknown error: DevToolsActivePort file doesn't exist) (The process started from chrome location /usr/bin/google-chrome is no longer running, so ChromeDriver is assuming that Chrome has crashed.) 2. rails_helper.rbの修正 rails_helper.rb RSpec.configure do |config| : : : config.include FactoryBot::Syntax::Methods config.before(:each) do |example| if example.metadata[:type] == :system if example.metadata[:js] driven_by :selenium, using: :headless_chrome, screen_size: [1400, 1400] else driven_by :rack_test end end end # Capybara.javascript_driver = :selenium end 上記を追加したらRSpecテストが正常に動作しました。下記の記事を参考にさせて頂いております。 参考にさせて頂いた記事 Selenium::WebDriver::Error::UnknownError:の対処法(seleniumではなくrackを指定してテストを走らせる) サポートが終了したchromedriver-helperからwebdrivers gemに移行する手順 WSL2 環境で RSpec (Capybara) で「Webdrivers::BrowserNotFound: Failed to find Chrome binary.」エラーが出るとき ChromeDriver - WebDriver for Chrome - Downloads
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[RSpec]コメント機能の単体テスト

はじめに 本記事では、コメント機能の単体テストのコードや説明を記述します。 前回いいね機能について投稿しているので、こちらも参照ください。 前提 コメント機能の実装ずみ 私は、投稿に対するコメントを実装しています。 コード 早速ですが、コードを記述します。 初めの方のsleep 0.2は必要ありません。 自宅のWi-Fiがとんでもないぐらい遅いので記述しています。一応記事を添付します。 spec/models/comment_spec.rb require 'rails_helper' RSpec.describe Comment, type: :model do before do user = FactoryBot.create(:user) food = FactoryBot.create(:food) @comment = FactoryBot.build(:comment, user_id: user.id, food_id: food.id) sleep 0.2 end describe 'コメント機能' do context 'コメントを保存できる場合' do it "コメント文を入力済みであれば保存できる" do expect(@comment).to be_valid end end context 'コメントを保存できない場合' do it "コメントが空では投稿できない" do @comment.text = '' @comment.valid? expect(@comment.errors.full_messages).to include "Text can't be blank" end it "ユーザーがログインしていなければコメントできない" do @comment.user_id = nil @comment.valid? expect(@comment.errors.full_messages).to include "User must exist" end it "投稿したものがなければコメントできない" do @comment.food_id = nil @comment.valid? expect(@comment.errors.full_messages).to include "Food must exist" end end end end textはコメントするテキストのことです。ちょっと命名下手くそすぎたかな。。。 comment.rb FactoryBot.define do factory :comment do text {'aaaa'} association :user association :food end end 私の中でテストする項目は以下の4つだと思っています。 - コメント文を入力済みであれば保存できる - コメントが空では投稿できない - ユーザーがログインしていなければコメントできない - 投稿したものがなければコメントできない 下2つのテストは、 it "ユーザーがログインしていなければコメントできない" do @comment.user_id = nil @comment.valid? expect(@comment.errors.full_messages).to include "User must exist" end it "投稿したものがなければコメントできない" do @comment.food_id = nil @comment.valid? expect(@comment.errors.full_messages).to include "Food must exist" end FactoryBotのところで、 以下を記述します。 association :user association :food さらに、以下を記述することでテストすることが可能になりました。 spec/models/comment_spec.rb before do user = FactoryBot.create(:user) food = FactoryBot.create(:food) @comment = FactoryBot.build(:comment, user_id: user.id, food_id: food.id) sleep 0.2 end 以上です。 いかがでしょうか。 どうにかテストできない場合は、空欄ではコメント送信できないようバリデーションできているかも確認してみます。 追記 @github0013@github さんからご意見いただき修正しました。ありがとうございます。 comment.rb FactoryBot.define do factory :comment do text {'aaaa'} user food end end spec/rails_helper.rb RSpec.configure do |config| config.include FactoryBot::Syntax::Methods ←一番下の行に記述しました。 end spec/models/comment_spec.rb before do user = create(:user) food = create(:food) @comment = build(:comment, user_id: user.id, food_id: food.id) sleep 0.2 end spec/rails_helper.rb # Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f } ↑コメントアウトする また、一段と賢くなりました。 終わりに テストは奥が深く、やればやるほど、また考えれば考えるほどどんどん浮かんできます。 作成されたアプリケーションもテストされている項目が多ければその分、使っていてバグが起こりにくいアプリケーションに仕上がるので、ここは注意できればなと考えました。 以下、参考サイトです。 コメント機能 モデル単体テスト 実装 明日も頑張ります!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む