RailsにBootstrap3およびBootstrap4を導入する方法
gemを入れることですぐにできる。Bootstrap3か4を使うかで利用するgemが異なる。
Bootstrap3:bootstrap-sass gem
Bootstrap4:bootstrap gem
Bootstrap3を導入する
Gemfile
gem 'bootstrap-sass', '~> 3.3.7' gem 'sass-rails', '>= 3.2'
app/assets/stylesheets/application.scss
application.scss に、あるいは独自のscssファイルを作成する場合は app/assets/stylesheets/custom.scss などとファイル作成し、先頭に次の2行を追加する。
@import "bootstrap-sprockets"; @import "bootstrap";
JavaScriptを必要とする機能を有効化する
app/assets/javascripts/application.js
//= require rails-ujs //= require jquery # 追加 //= require bootstrap # 追加 //= require turbolinks //= require_tree .
参照
RSpecを学ぶ時、書く時に参考になる記事一覧
随時更新中。
「everyday Rails RSpecによるRailsテスト入門」を読了後に読むモノ。
初めてRSpecを学ぶ場合は、何も考えずに書籍の購入をやりこむのがオススメ。
【アンチパターン】Arrange、Act、Assert(AAA)を意識できていないRSpecのコード例とその対処法
Arrange、Act、Assert(AAA)を意識したRSpecの書き方を学べる。
RSpecが複雑になってきた場合に、下記を示してくれる。
- どこに何を書けば良いか
- どうすれば可読性が上がるか
個人的に、記事中で紹介されている「テストコードを書き始める前に大枠だけ固め」る技は、テストに限らず通常の実装時にも、「とりあえずロジックだけ日本語で書き出す」みたいに応用できるのでオススメ。AAAはJavaの書籍「実践JUnit」とかでも出てくるので、懐かしい。
Railscasts - How I Test
Better Specs
画面右上で日本語選択できます。
www.betterspecs.org
【初心者向け】レビュワーをイライラさせるRSpec集と解決方法
可読性、保守性の高いRSpecについて、メドピアのエンジニアの方がポイントを書いてくださっている。
特に、「5. テスト対象が同じ、複数のテストケースで、subjectが使われていない」の内容は、subjectが効果を発揮するのはどういう場面かが、具体例をもとに示されており分かりやすい。
簡易的なサンプルコードではsubjectの有用性がイマイチ理解できなかったが、記事内で挙げられている具体例ではsubjectでまとめることにより、同一の内容をテストしているということが明確になり分かりやすい。
長いメソッド名、メソッドチェーン連発、引数が微妙に違うとか、一目見た時に同じ処理だと思ってたけどよくよく見てみると違うじゃん!という保守性の悪いコードを何回か見たことがあるので、subjectを使うことにより同一処理が明確になるのは嬉しいし、何より文字数減ってスッキリしている。
なお5.以外の内容を読む前から当たり前と感じない場合は、リーダブルコードや「Arrange、Act、Assert(AAA)を意識できていないRSpecのコード例とその対処法」を読むのがオススメかもです。
FcatocyBot Getting Started
factory_bot/GETTING_STARTED.md at master · thoughtbot/factory_bot · GitHub
RSpec - コントローラスペックでは何をテストすべきか
前提
Rails5からは request spec で記述することが推奨され、公式から controller spec を書くことは非推奨とされている。
テストすべき内容
- Webリクエストが成功したか
- 正しいページにリダイレクトされたか
- ユーザー認証が成功したか
- レスポンスのテンプレートに正しいオブジェクトが保存されたか
- ビューに表示されたメッセージは適切か
出典:「Rails テスティングガイド」における「7 コントローラの機能テスト」。
具体的なテストコード
下記、および書籍 Everyday Rails の第5章 コントローラスペックを参照。
RailsのController Spec | 酒と涙とRubyとRailsと
コントローラスペックからリクエストスペックへの移行方法
@t2kojima さん*1がqiitaの記事でまとめてくださっている。
Rails5でコントローラのテストをController specからRequest specに移行する - Qiita
【パーシャル】インスタンス変数の直接参照ではなく、localsで値を渡す
なぜlocalsで渡す必要があるのか
インスタンス変数を直接使うと、Viewと特定のControllerとの依存が強まり、別の箇所で再利用しにくくなるため。
悪い例
Controller (良い例と同じ)
class UsersController < ApplicationController def show @user = User.find(params[:id]) end end
View
# herder内で直接@userを参照 <%= render 'header' %>
良い例
View
# herder内では変数userを参照 <%= render 'header', user: @user %>
上記により、将来的に下記のような再利用ができる
<%= render 'header', user: @owner %>
参照
Rails Best Practice
上記と似たようなコードが記載
Rails Best Practices - Replace instance variable with local variable
Qiita - Rails Best Practices の警告をちゃんと考える
Rails Best Practiceを日本語で解説
Rails Best Practices の警告をちゃんと考える - Qiita
Railsガイド レイアウトとレンダリング 3.4.4 ローカル変数を渡す
localsの使い方を解説
レイアウトとレンダリング | Rails ガイド
JavaScriptのファイルを読み込む位置はどこにすべきか?
回答
bodyタグの終端、すなわち </body> の直前。
理由
- 表示速度を速くするため
javascriptファイルを読み込んでいる間は、HTMLファイルを読み込まない。そのため、ページのレンダリングを行わせてからjavascriptファイルを読み込むことで表示速度を早くする。その結果、SEO対策になるなどのメリットもある。
- 可読性を上げるため
CSSファイルをheadタグに、JSファイルをbodyタグの終端にまとめることで可読性を上げる。
参考
【各章まとめ】Everyday Rails - RSpecによるRailsテスト入門 - 第8章 スペックをDRYに保つ
復習用まとめ
本章で学ぶこと
- ワークフローをサポートモジュールに切り出す
- テスト内でインスタンス変数を再利用するかわりにletを使う
- shared_contextに共通のセットアップを移動する
- カスタムマッチャの作成
- テストを集約して、複数のスペックを一つにする
サポートモジュールに、ログインのフローを切り出す
複数のスペックでの何回も書かれている処理を共通処理として切り出す。例えば、ログイン処理など。ポイントは、メソッド名称を見ただけで何をしているのか一目でわかるように切り出すこと。
切り出し方
spec/supportディレクトリ以下に、login_support.rbを作成
module LoginSupport def sign_in_as(user) visit root_path click_link "Sign in" fill_in "Email", with: user.email fill_in "Password", with: user.password click_button "Log in" end end RSpec.configure do|config| config.include LoginSupport end
呼び出し方
上記のようにmoduleに、「config.include XxxxxSupport」と記述するか、呼び出すスペック内にてinclude
RSpec.feature "Projects" ,type::feature do include LoginSupport # ←使うスペックでincludeする scenario "user creates a new project" do # ... end end
Device gemを使っている場合のログインフローの切り出し方
特に上記のように画面操作をシュミレートしなくても、フィーチャーテストにおいてDeviceのヘルパーをつかうことで、ログイン状態にすることができる。
まずは、rails_helper.rbに次の一行を追加
RSpec.configure do |config| # ... config.include Devise::Test::IntegrationHelpers, type: :feature end
RSpec.feature "Projects" ,type::feature do scenario "user creates a new project" do user = FactoryBot.create(:user) sign_in user # セッションを作成する visit root_path # セッション作成後に遷移させることを忘れずに # ... end end
letで遅延読み込み
なぜletを使うか?
beforeブロックでテストデータを用意する場合、下記のデメリットがある。
- テスト実行のたびに毎回実行され、不要データが作成されテスト速度が遅くなる可能性がある。
- 要件が増えるにつれ可読性が悪くなる。
letの使い方
RSpec.describe Note, type: :model do let(:user) { FactoryBot.create(:user) } let(:project) { FactoryBot.create(:project, owner: user) } # このテストではletが呼び出される it "is valid with a user, project, and message" do note = Note.new( message: "This is a sample note.", user: user, project: project, ) expect(note).to be_valid end # このテストではletが呼び出されず、無駄なデータが作成されない it "is invalid without a message" do note = Note.new(message: nil) note.valid? expect(note.errors[:message]).to include("can't be blank") end end
let!を使う必要がある場合
テスト内で明示的にletの値が呼び出されない場合、例えば検索のテスト等でテスト実行前にデータを用意しておく必要がある場合など、let!を使うことでテスト前にlet内部のコードが呼び出される仕組みとなっている。
# これより上のコードは略 describe "search message for a term" do let!(:note1) do FactoryBot.create( :note, project: project, user: user, message: "This is the first note") end let!(:note2) do FactoryBot.create( :note, project: project, user: user, message: "This is the second note") end let!(:note3) do FactoryBot.create( :note, project: project, user: user, message: "First, preheat the oven.") end context "when a match is found" do it "returns notes that match the search term" do expect(Note.search("first")).to include(note1, note3) end end # letとした場合、このテストではデータが全く作成されなくなってしまう # let!にすることで、テスト実行前にデータが作成された状態になる context "when no match is found" do it "returns an empty collection" do expect(Note.search("message")).to be_empty expect(Note.count).to eq 3 end end end
shared_context
テストにおける条件(=context)、セットアップを共有する。毎回同じセットアップコードを書いているならば、shard_contextで共通化できないかを考えてみる。
共通化の方法
spec/support/contexts ディレクトリ以下に、project_setup.rb などと切り出す
RSpec.shared_context "project setup" do let(:user) { FactoryBot.create(:user) } let(:project) { FactoryBot.create(:project, owner: user) } let(:task) { project.tasks.create!(name: "Test task") } end
使う側の実装
RSpec.describe TasksController, type: :controller do include_context "project setup" # ←この一行を加えるだけでセットアップ完了。 describe "#show" do it "responds with JSON formatted output" do sign_in user get :show, format: :json, params: { project_id: project.id, id: task.id } expect(response.content_type).to eq "application/json" end end . . end
カスタムマッチャ
読みにくいエクスペクテーションを分かりやすいカスタムマッチャとして切り出す。処理をメソッドでまとめ、保守性の高い名前を付けることとやってることは同じ。
RSpec の重要な信条のひとつは、人間にとっての読みやすさです。
spec/support/matchers以下にファイルを作成
spec/support/matchers/content_type.rb
RSpec::Matchers.define :have_content_type do |expected| match do |actual| content_types = { html: "text/html", json: "application/json", } actual.content_type == content_types[expected.to_sym] end end
...一旦ここまで。