- 投稿日:2022-01-31T23:53:27+09:00
「Everyday Rails - RSpecによるRailsテスト入門」のセットアップで詰まったことと対処法まとめ
「Everyday Rails - RSpecによるRailsテスト入門」 職場の先輩におすすめしていただいたので、買ってみました。 そこのセットアップでつまずいたことと対処法を自分なりにまとめてみました。 「Everyday Rails - RSpecによるRailsテスト入門」のセットアップの手順 # ソースコードのダウンロード git clone git@github.com:JunichiIto/everydayrails-rspec-jp-2022.git # ディレクトリの移動 cd everydayrails-rspec-jp-2022 # 使用する Ruby バージョンを指定(本書では3.1.0を推奨。下記コマンドは rbenv を使用する場合) rbenv local 3.1.0 # gem のインストール bundle install # データベースのセットアップ等 bin/setup # JavaScript パッケージのインストール yarn install # foreman gem のインストール gem install foreman # サーバーの起動 bin/dev 「Everyday Rails - RSpecによるRailsテスト入門」 にこのようなパートがあると思います。27p目(2022/1/31時点)。 git clone時のエラー # ソースコードのダウンロード git clone git@github.com:JunichiIto/everydayrails-rspec-jp-2022.git で、 git@github.com: Permission denied (publickey) のようなエラーが起こると思います。 以下の記事をまんまやったらできました。 ssh等のよけいな設定がめんどくさければ下記のコマンドでもクローンできるかと思います。 git clone https://github.com/JunichiIto/everydayrails-rspec-jp-2022.git bin/setup時のエラー bin/setup を実行すると ActiveSupport::MessageEncryptor::InvalidMessage (ActiveSupport::MessageEncryptor::InvalidMessage) というエラーがでるかもしれません。 このエラーはどんなエラーかという 詳しいことの説明は、チェリー本や、Everyday Rails - RSpecの著書である伊藤淳一さんの下記の記事におまかせしたいと思います。 https://qiita.com/jnchito/items/a73bc2838bfab5240675 bin/setup時のエラーの解決 credentials.yml.encを削除 万が一master.keyがあった場合それも削除 sudo EDITOR="vi" bin/rails credentials:edit を実行、このときmaster.keyが作られたことを確認。 sudo chmod 764 config/master.key を実行。権限系のコマンドは自分もよくわかっていないので自己責任でお願いします。 sudo bin/setup を実行したらデータベースが作られると思います。 ついでにいうと、 # サーバーの起動 bin/dev も # サーバーの起動 sudo bin/dev とやると立ち上がりました。 いちいちsudoコマンドを実行しなくてもいい方法があるかもしれませんが、一応この方法でセットアップが完了しました。 環境 macOS Monterey 12.1 2022/1/31日にbrew updateを実行 ruby 3.1.0p0 Rails 7.0.1
- 投稿日:2022-01-31T22:50:40+09:00
Elasticsearch::Modelのresultsとrecordsメソッドの違い
elasticsearchをなんとなく触っていたら、勘違いしてハマったので記事として残します。 resultsとrecordsメソッドの違い 結論から書くとresultsメソッドはelasticsearchのドキュメントを返しますが、recordsメソッドはデータベースから取得したモデルインスタンスのコレクションを返します。 response = Article.search 'sample' response.results.map { |r| r._source.title } # => ["sample article 1", "sample article 2"] response.records.to_a # Article Load (0.3ms) SELECT "articles".* FROM "articles" WHERE "articles"."id" IN (1, 2) # => [#<Article id: 1, title: "sample article 1">, #<Article id: 2, title: "sample article 2">] 仮に<Article id: 1, title: "sample article 1">のレコードは削除したが、elasticsearchのドキュメントは削除していない状態だと、 response = Article.search 'sample' response.results.map { |r| r._source.title } # => ["sample article 1", "sample article 2"] response.records.to_a # Article Load (0.3ms) SELECT "articles".* FROM "articles" WHERE "articles"."id" IN (1, 2) # => [#<Article id: 2, title: "sample article 2">] recordsメソッドではデータベースから取得した情報を返すので削除されたレコードは結果に返ってきませんが、 resultsメソッドだとelasticsearchのドキュメントを削除していないため削除されたレコードの結果も返ってきてしまいます。 ちなみに二つのメソッドの使い分けですが、基本的にはresultsメソッドを使ってモデルのメソッドにアクセスしたいときのみrecordsメソッドを使うとよさそうです。 参考
- 投稿日:2022-01-31T18:54:24+09:00
let と let! の違い(遅延評価)
はじめに request specを書いているときに、エラーぶつかりました。その原因がletとlet!の違いによるものでした。 当時は違いを理解できていなかったため、エラーにかかって向き合う時間ができてよかったと思っています。 今回得た学びをまとめます。 開発環境 ruby '3.0.0' Rails 6.0.3.4 エラー原因 コントローラのテストをrequest specで実行する際、以下のようなソースで、indexアクションを検証していました。 request.rb RSpec.describe "Gadjets", type: :request do let(:user) {FactoryBot.create(:user, :a)} let(:category) {FactoryBot.create(:category)} let(:gadjet) {FactoryBot.create(:gadjet, user_id: user.id, category_id: category.id)} describe "Action test" do context "index" do it "ログインユーザの場合。リクエストが成功すること" do sign_in user get gadjets_path expect(response.status).to eq(200) end it "ログインユーザの場合。gadjet_titleが表示されること" do sign_in user get gadjets_path expect(response.body).to include "#{gadjet.gadjet_title}" end it "ログインしていないユーザの場合、302レスポンスであること" do get gadjets_path expect(response.status).to eq(302) end end index.html.erb <% if @gadjets.present? %> <%# ログインしているユーザが投稿したgadjetがあるかを判定するフラグ%> <% @gadjets.each do |gadjet| %> **省略** <p class="card-text"><%= gadjet.gadjet_title.truncate(15) %></p **省略** <% end %> <% else %> <%= current_user.user_name %>が投稿したgadjetはありません <% end %> @gadjetsが存在すれば「gadjet_title」を表示し、存在しなければ「(ユーザ名)が投稿したgadjetはありません」と表示するものです。 ここのテストを実施した際に、結果は失敗でした。 it "ログインユーザの場合。gadjet_titleが表示されること" do sign_in user get gadjets_path expect(response.body).to include "#{gadjet.gadjet_title}" end エラー Failed examples: rspec ./spec/requests/gadjets_request.rb:17 # Gadjets Action test index ログインユーザの場合。gadjet_titleが表示されること <div class="col-md-4"> satoshiが投稿したgadjetはありません </div> どうやら”if @gadjets.present?”でelse(存在しない)を通っているみたいです。 しかし、request.rbにてletで:gadjetを定義し、インスタンスを作成しているのだが。。。 原因は遅延評価による特性のためでした。 let と let! の違い(遅延評価) エラー解決まで letで定義(遅延評価)すると、必要なときだけのみ評価されます。(letで定義した変数が参照されたタイミングで評価される)。 つまり必要ないときまで処理しないので余計な処理をせず結果的に、パフォーマンスが上がる。 それに比べlet!で定義するとテストを行う前(before doと同じタイミング)に評価されます。 また"it"ごとに変数が初期化されます。つまり無駄な初期化の時間が生まれる可能性があるため、無闇に使うのは控えたい。。 今回のケースで言えば、request.rbで "sign_in user"の記載でlet(:user) {FactoryBot.create(:user, :a)}が評価されることになります。 ":user"は参照されたため、評価されたのですが、肝心の":gadjet"は、参照されていないため、評価されていないことになります。 it "ログインユーザの場合。gadjet_titleが表示されること" do sign_in user #userを参照している get gadjets_path #index画面に遷移した時点で、":gadjetを参照していないため、”if @gadjets.present?”がelseになる expect(response.body).to include "#{gadjet.gadjet_title}" end そのため、let を let! に変更しテスト実行と同時に評価するよう変更したところ成功しました。 request.rb RSpec.describe "Gadjets", type: :request do let(:user) {FactoryBot.create(:user, :a)} let(:category) {FactoryBot.create(:category)} let!(:gadjet) {FactoryBot.create(:gadjet, user_id: user.id, category_id: category.id)} #!を追加 describe "Action test" do context "index" do it "ログインユーザの場合。リクエストが成功すること" do sign_in user get gadjets_path expect(response.status).to eq(200) end it "ログインユーザの場合。gadjet_titleが表示されること" do sign_in user get gadjets_path expect(response.body).to include "#{gadjet.gadjet_title}" end it "ログインしていないユーザの場合、302レスポンスであること" do get gadjets_path expect(response.status).to eq(302) end end 終わりに 個人的に、うやむやになっていた部分が今回エラーにかかったことで、理解が深まりました。 初学者にとって、遅延評価はつまづきやすいポイントだと思います。 少しでも参考になれば幸いです。
- 投稿日:2022-01-31T17:22:51+09:00
【Capybara,webdrivers】統合テスト(System Spec)とは
統合テスト(System Spec)とは 統合テストは、アプリケーション全体が一つのシステムとして期待通りに動くか否かを検証するテストのこと。 統合テストにおいては、Capybaraというrubyのライブラリを使用。Capybaraを使うことで、実際のアプリケーションの使われ方をコードで表すことができる。(fill_inやclick_buttonなど) webdriversはChromeDriverを簡単に導入してくれるgemである。アプリケーション全体が一つのシステムとして期待通りに動くか否かを検証する場合、実際にブラウザでテストがそうのように動いているのか目視できた方が分かりやすいので、Capybaraとwebdriversはセットで入れることが多い。 Capybaraとwebdriversの導入を記載していく。 gemの導入 Gemfile. group :test, :development do # (省略) gem 'capybara' gem 'webdrivers' end bundle install後、spec/rails_helper.rbに下記の一文を追記。 spec/rails_helper.rb require 'capybara/rspec' #追記 Capybara.configure do |config| ---省略--- end webdriversの設定 spec/supportディレクトリ下にcapybara.rbを作成し、下記を追記。 spec/support/capybara.rb RSpec.configure do |config| config.before(:each, type: :system) do driven_by :selenium, using: :headless_chrome #←ブラウザの表示無 #driven_by :selenium_chrome ←ブラウザの表示有 end end chromeについての設定を記入している。ここでheadless_chromeと記入されている一文を表記すればブラウザの表示を無しにできる。 逆に、コメントアウトされている方を記載すると、ブラウザの表示有になる。 注意 テストファイルを作成した際、おそらくデフォルトで下記のようになっている。 example_spec.rb require 'rails_helper' RSpec.describe 'Tasks', type: :system do before do driven_by(:rack_test) end pending "add some scenarios #{__FILE__}" #↑この1文はRSpecの書き方を解説している。削除してOK end driven_byで実行時のブラウザを指定しているので、この記載を削除。(spec/support/capybara.rbでブラウザの指定を一元管理しているため) 【Capybara】要素を操作するメソッド メソッド 動作 visit ページへアクセスする(GETリクエスト) check チェックボックスをチェックする uncheck チェックボックスのチェックを外す fill_in テキストフォームに入力する select セレクトボックスを選択する choose ラジオボタンを選択する attach_file ファイルセレクタにファイルを設定する click クリックを実行する click_on ボタンorリンクをクリックする(click_button/click_link) accept_alert アラートのボタンをクリックする 実用例 tasks_spec.rb equire 'rails_helper' RSpec.describe 'Tasks', type: :system do let(:user) { create(:user) } let(:task) { create(:task) } describe 'タスク削除' do let!(:task) { create(:task, user: user) } it 'タスクの削除が成功する' do visit tasks_path click_on "Destroy" expect(page.accept_confirm).to eq 'Are you sure?' expect(page).to have_content "Task was successfully destroyed" expect(current_path).to eq tasks_path expect(page).not_to have_content task.title end end end end 参考記事 【Rails】Capybaraを使った統合テスト(導入〜簡単なテスト実行まで) 【Rspec】統合テスト(System Spec)について Rspecの設定(SystemSpecの導入、実行時にブラウザ表示、非表示の切り替え設定) #5
- 投稿日:2022-01-31T15:50:34+09:00
VMWare Fusionでguest(windows)からhost(mac)の開発環境(localhost:3000)にアクセス
環境 Host: Mac Big Sur 11.5.2 VM: VMWare Fusion 12.1.2 Guest: Windows 10 x64 やりたいこと お客さんの環境(Windows)だけで、デザインが崩れたので修正したい Guest(Windows)からHost(Mac)の開発環境(localhost:3000)にアクセスして直接操作 手順 1 VMでWindowsを立ち上げる 2 Macのターミナルでコマンド実行 $ ifconfig 3 上記から該当するものを探す en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500 options=400<CHANNEL_IO> ether 3c:22:fb:13:df:4f inet6 fe80::1c84:d089:5dae:5860%en0 prefixlen 64 secured scopeid 0x6 inet 192.168.128.56 netmask 0xffffff00 broadcast 192.168.128.255 nd6 options=201<PERFORMNUD,DAD> media: autoselect status: active 4 自分のは192.168.128.56 5 Windowsでchromeを立ち上げて、192.168.128.56:3000にアクセス 動作は遅いけどとりあえずこれで確認できた
- 投稿日:2022-01-31T15:36:31+09:00
Rspecで特定のテストだけ実行する
特定のテストを実行 特定のテストを実行には2つあり、1つは実行したいテストのファイル名と行数を指定する方法。 ターミナル. $ rspec spec/models/user_spec.rb:8 2つ目はテストコードitの前にfをつけるだけでそのテストのみ実行される方法。 task_spec.rb require 'rails_helper' RSpec.describe Task, type: :model do describe 'validaton' do fit 'タスクを問題なく作成' do. #fitとなっているこのテストだけ実行される。 task = build(:task) expect(task).to be_valid expect(task.errors).to be_empty end it 'タイトル空欄は無効' do task = build(:task,title: "") expect(task).to be_invalid expect(task.errors[:title]).to eq ["can't be blank"] end end end ターミナルで入力するよりも、fitと入力したほうが簡単なので、これが使えるように設定していく。 spec_helperを編集 spec_helper.rb RSpec.configure do |config| config.filter_run_when_matching :focus #↑コメントアウトされているはずなので解除 end これで、fdescribe, fcontext, fit のように使うことができる。 参考記事 RSpecで特定のテストを実行する方法
- 投稿日:2022-01-31T10:29:24+09:00
ヒアドキュメントについて
rubyシルバーを勉強していくなかで、hashについてちゃんとわかっていなかった今回は解説させていただこうとおもいます。 ヒアドキュメントとは 複数行にわたる長い文章の文字列を扱い場合に便利な機能のことです。 言われてもピンとこないとおもいますので、実際にコードを見ていきましょう ヒアドキュメントを使わないときと、使うときで比較してみていきましょう puts "ヒアドキュメントを使わない場合はこのようにクオーテーションで囲い、\n改行したい箇所に\\nを書くことで改行できる。\nちなみにバックスラッシュを二つ使うことで改行(\\n)をエスケープできる" 上の書き方は、ヒアドキュメントを使わない書き方です。これでも、コード上は何も悪いわけではないのですが、なんか見た目悪くないですか? 可読性が落ちるんですね。 では次に、ヒアドキュメントを使う書き方を見ていきましょう。 text = <<EOS ヒアドキュメントを使わない場合はこのようにクオーテーションで囲い、 改行したい箇所に\\nを書くことで改行できる。 ちなみにバックスラッシュを二つ使うことで改行(\\n)をエスケープできる EOS puts text => #ヒアドキュメントを使わない場合はこのようにクオーテーションで囲い、 改行したい箇所に\nを書くことで改行できる。 ちなみにバックスラッシュを二つ使うことで改行(\n)をエスケープできる ヒアドキュメントを使うと、かなり見やすくなりましたね! ヒアドキュメントの使い方 今回では識別子に、EOSが使われていますが、EOSじゃなくてもいいんです。 慣習的に、EOS(End Of String), EOF(End Of File), EOL(End Of Line)が使われます。 でも、識別子が一致していれば、名前は何でもいいんです。 そのため、以下のような書き方でもOKです text = <<HOGE ヒアドキュメントを使わない場合はこのようにクオーテーションで囲い、 改行したい箇所に\\nを書くことで改行できる。 ちなみにバックスラッシュを二つ使うことで改行(\\n)をエスケープできる HOGE puts text => #ヒアドキュメントを使わない場合はこのようにクオーテーションで囲い、 改行したい箇所に\nを書くことで改行できる。 ちなみにバックスラッシュを二つ使うことで改行(\n)をエスケープできる インデントをつける場合 インデントをつけるときは、識別子の前に「-」をつけることでインデントがつくんです。 data = <<-EOF hoge fuga hogehoge EOF => " hoge\n fuga\n hogehoge\n" puts data # hoge fuga hogehoge インデントをつけない場合 インデントをつけないときは、識別子の前に「~」をつけることでインデントを排除できるんです data = <<~EOF hoge fuga hogehoge EOF => "hoge\nfuga\nhogehoge\n" 実際に問題を解いてみましょう ※選択されている回答は間違っています 今回の問題では、識別子の前に「-」をついています。つまりインデントが適用されるんですね。 よって、この問題の正解は " Hello,\n Ruby\n" が正解です 以上です。 何か間違いがございましたら、ご教示いただけますと幸いです。
- 投稿日:2022-01-31T10:26:58+09:00
RailsでJavaScriptファイルからのパスの通し方
実現したいこと RailsのJavaScriptファイル内に記載した、画像・音楽ファイルへのパスを正常に読み込ませたい✅ 開発環境 Ruby 3.0.2 Rails 6.1.4.4 結論 パスの指定を以下のように、asset_pathを使用する。 ※ JavaScriptの記載は、viewファイルのscriptタグ内に記載するか、hghg.js.erbのように、Rubyの埋め込みが可能な状態にする必要があります⚠️ 任意のファイル var ASSETS = { sound: { music: "<%= asset_path("energy.mp3") %>", ring: "<%= asset_path("tamborine.mp3") %>", }, json: { beatmap: "<%= asset_path("notes.json") %>" }, image: { 'bg': "<%= asset_path("club.jpg") %>" } }; manifest.jsにassets内の読み込むフォルダを指定する。 app/assets/config/manifest.js //= link_tree ../images //= link_tree ../music //= link_tree ../json これでOK!! エラー表示 間違ったパスの指定をしていた際のエラーも掲載しておきます? 下記コードのように、assets内の任意のファイルを相対パスで指定してもRouting Error(No route matches [GET] ... /assets...)になります。 任意のファイル var ASSETS = { sound: { music: "../../assets/music/energy.mp3", ring: "../../assets/music/tamborine.mp3", }, json: { beatmap: "../../assets/json/notes.json" }, image: { 'bg': "../../assets/images/club.jpg" } }; 最後に このエラーで数日詰まり、解決記事も他になかったので本記事を執筆しました✏️ 本記事が多くの悩める方に届けば幸いです!!!
- 投稿日:2022-01-31T00:54:44+09:00
ハッシュに関する解説
rubyシルバーを勉強していくなかで、hashについてちゃんとわかっていなかった今回は解説させていただこうとおもいます。 ハッシュって? ハッシュとは、配列と似ていますが、keyというものと、valueというものがある配列です。 具体的に、コードを見ていきましょう! hash = {'Satou' => 19, 'Hashimoto' => 35, 'Yamada' => 23} こういうふうな書き方をします。 keyというものが、Satou, Hashimoto, Yamadaです。 valueというものが、19, 35, 23です。 このハッシュのメリットが、keyを目印にして、valueを取得することができるんです。 これと似たものに配列というものがあります。 配列は、0,1,2のような数字で値を取得するような書き方をするんですね。 ハッシュの方が直感的でいいですね。 ハッシュの値の取り方 先ほどのコードを書いてみましょう hash = {'Satou' => 19, 'Hashimoto' => 35, 'Yamada' => 23} #keyを全部取得して、それを配列で返す。 hash.keys => ["Satou", "Hashimoto", "Yamada"] #valueを全部取得して、それを配列で返す。 hash.values => [19, 35, 23] #あるvalueに対応した、keyを取得 hash.key(19) => "Satou" ##あるkeyに対応した、valueを取得し、それを配列で返す。 hash.values_at('Satou') => [19] hash.['Satou'] => 19 そのほかの値の取得方法もありますので、気になる方は、こちらを参考にしてください! シンボルについて シンボルとは、「:」という書き方です。 では公式ドキュメントになんて書いてあるんでしょうか? Rubyの内部実装では、メソッド名や変数名、定数名、クラス名などの`名前'を整数で管理しています。これは名前を直接文字列として処理するよりも速度面で有利だからです。そしてその整数をRubyのコード上で表現したものがシンボルです。 シンボルは、ソース上では文字列のように見え、内部では整数として扱われる、両者を仲立ちするような存在です。 名前を管理するという役割上、シンボルと文字列は一対一に対応します。また、文字列と違い、immutable (変更不可)であり、同値ならば必ず同一です。 公式ドキュメントより つまり、見た目上は、文字列でも、中身は数字なんですね。 これの何がいいのか?僕個人的には、一番のメリットは処理速度が速いということだとおもいます。 他にメリットがありますので、それを知りたい方はこちらを参照してみてください。 実際のコードを見ていきましょう #書き方➀ hash = {:'Satou' => 19, :'Hashimoto' => 35, :'Yamada' => 23} puts hash[:'Satou'] # 19 #書き方➁ hash = {'Satou': 19, 'Hashimoto': 35, 'Yamada': 23} puts hash[:'Satou'] # 19 #値の取り方は、上のコードと同じようにとれる 実際に問題を解いてみよう この問題では、 klassという変数に、Classクラスから新しいインスタンスを作成して、そのオブジェクトを代入します。 そして、そのkeyであるオブジェクトに、valueの100を作ります。つまり、ハッシュをつくります。 3行目では、上のコードでも書いたような書き方で書かれていますね。 つまり、keyに紐づいた、valueを取得しています。 このコードを実行すると、取得できる値は、 klass = Class.new hash = {klass => 100} puts hash[klass] # 100 100が出てくるような選択肢を選べばいいんですね。 選択肢を見ていきましょう 選択肢1 klass = Class.new hash = {klass: 100} puts hash[klass] 1行目までは同じですね。 2行目もハッシュの書き方➁の書き方です。 3行目をよく見てみると、値の取り方が違いますね。もし、100を取りたかったら、 puts hash[:klass] と書くべきですね。 選択肢1の値の取り方をすると、nilになるんです。 選択肢2 klass = Class.new hash = {} hash.store(klass, 100) puts hash[klass] 2行目で、空のハッシュを作ります。 3行目でstoreメソッドを使ってます。storeとは英語で「格納する」という意味です。 つまり、hashにklassをkeyに、valueを100としたhashを作ります。コードで書くなら、 {#<Class:0x00000000086b8660>=>100} そして4行目で、keyに対応したvalueを取得してます。この書き方は、「ハッシュの値の取り方」の最後に書いてあります。 このコードを実行すると、100が取得できるので、1つ目の正解です 選択肢3 ここまで読んでいただいた方であれば、選択肢3はnilになることが分かるとおもいます。 選択肢1の解説を読んでいただければわかるとおもいます。 選択肢4 ここまで読んでいただいた方であれば、選択肢4は100になることが分かるとおもいます。 2行目のHashメソッドは、新しいハッシュを作成してくれるというメソッドです。 以上です。 何か間違いがございましたら、ご教示いただけますと幸いです。 【参考文献】