みなさん、こんばんは!まどぎわです(・∀・)
最近、ソースコードレビューを受ける機会があったので、指摘頂いた内容を復習も兼ねて備忘目的で下記に整理してみました。
また私が作ったRailsアプリケーション環境関連は下記の通りです。
category | version |
---|---|
ruby | 2.4.3 |
rails | 5.1.4 |
mysql | 14.14 |
また、viewのテンプレートエンジンにはhaml
を使用しています。
レビュー内容まとめ
Model
migrationとmodelのvalidationは合わせる
modelで必須とした項目は、migrationファイルでもnull: false
として、DBでもNOTNULL制約を設定するとDBでも必須チェックを行うため安心です(・∀・)
# model validates :name, presence: true
# migration # before t.string :name # after t.string :name, null: false
validationの検証の実行条件はifオプションを活用する
validationメソッドには、エラーとなる条件のみの記載し、ifオプション側にvalidationメソッドの実行条件を記載することで、validationメソッドが汎用的になり、可読性も上がりますφ(..)
# before validate :birthday_cannot_be_in_the_future def birthday_cannot_be_in_the_future if birthday.present? && birthday.future? errors.add(:birthday, "can not specify your future date as your birth date.") end end # after validate :birthday_cannot_be_in_the_future, if: -> { birthday.present? } def birthday_cannot_be_in_the_future if birthday.future? errors.add(:birthday, "can not specify your future date as your birth date.") end end
Model内でしか活用しないメソッドはprivateにする
インスタンス生成後に呼ばれないメソッド等はprivate
に定義した方が、不用意に呼ばれたりすることがなくなり良いです(・∀・)
# before validate :birthday_cannot_be_in_the_future, if: -> { birthday.present? } def birthday_cannot_be_in_the_future if birthday.future? errors.add(:birthday, "can not specify your future date as your birth date.") end end # after validate :birthday_cannot_be_in_the_future, if: -> { birthday.present? } private def birthday_cannot_be_in_the_future if birthday.future? errors.add(:birthday, "can not specify your future date as your birth date.") end end
Controller
インスタンスの生成処理がシンプルな場合はbeforeアクションに記載せず各アクションに記述する
scaffoldで生成されるコードでは、before_actionでインスタンス変数を設定するようなコードになっていますが、find
してインスタンス変数を設定するぐらいであれば、各アクションに記述した方が可読性が良くなることが多いようですφ(..)
# before before_action :set_user, only: [:show, :edit, :update, :destroy] def show end private def set_user @user = User.find(params[:id]) end # after def show @user = User.find(params[:id]) end
Controllerのbefore_actionにおける インスタンス変数セットについて
あればfind無ければnewする場合は、find_or_initializeメソッドを使用する
特定の値で検索し、無ければその値でインスタンス変数を生成する場合は、find_or_initialize
メソッドを使うとスッキリかけますφ(..)
# before def set_profile @profile = Profile.find_by(user_id: params[:id]) @profile ||= Profile.new end # after def set_profile @profile = Profile.find_or_initialize_by(user_id: params[:id]) end
※findする条件とインスタンスを生成する際の値が違う場合は、検索条件を引数にwhere
を呼び出し、インスタンス生成時のプロパティを引数にfirst_or_initialize
を呼び出すと良いです。
# before def set_profile_user @profile = Profile.find_by(id: params[:id]) @profile ||= Profile.new(profile_params) @user = User.find(@profile.user_id) end #after def set_profile_user @profile = Profile.where(id: params[:id]) .first_or_initialize(profile_params) @user = User.find(@profile.user_id) end
first_or_initializeとfind_or_initialize_by - Qiita
View
省略出来る{}は削除する
シンプルなことですが、意外とやりがちだと思うので、注意したいですねφ(..)
- # before = render 'form',{ user: @user, next_action: 'create' } - # after = render 'form', user: @user, next_action: 'create'
機能単位にpartialを切りだす
viewファイルは機能単位にpartial
を切り出してあげた方が、application.html.erb
は特にモリモリになりがちなので良いです。
また、ファイルが分割されることにより、可読性があがるだけでなくファイル競合の防止等のメリットもあるかと思いました(・∀・)
- # before %body %header - unless current_user = link_to "Login", login_path = link_to "Signup", signup_path - else = link_to "Profile", current_user = link_to "Logout", logout_path, method: :delete - # after %body %header = render 'layouts/navbar'
TestCode
登録成否は、change matcherを使用する
change matcher
だけでなく、色々なmatcherを使って綺麗なテストコードを書けるように意識したいです。
# before let(:user_before_cnt){ User.count } context "User#create時にエラーが発生した場合" do before do post signup_path, params: invalid_user_param end it "ユーザーが登録されないこと" do expect(User.count).to eq user_before_cnt end end [使えるRSpec入門・その2「使用頻度の高いマッチャを使いこなす」 - Qiita](https://qiita.com/jnchito/items/2e79a1abe7cd8214caa5) # after context "User#create時にエラーが発生した場合" do it "ユーザーが登録されないこと" do expect { post signup_path, params: invalid_user_param }.to_not change(User, :count) end end
メソッド実行結果のtrue/false判定はbe_methodnameを使用する
メソッドの返り値のtrue/false
を検証する場合は、eq
ではなくbe_methodname
を使用した方が綺麗にかけますφ(..)
# before it "ログイン状態となること" do post signup_path, params: valid_user_param expect(!!current_user).to eq true end # after it "ログイン状態となること" do post signup_path, params: valid_user_param expect(current_user).not_to be_blank end
インスタンス変数ではなく、letを使う
letは、遅延評価(使用される時に変数が生成される)ため、実行負荷の軽減が見込めるようですφ(..)
他にもメリットがいくつかあるようです、下に参考のリンクを記載しておきました!
# before @user = FactoryBot.create(:valid_user) # after let!(:user){ FactoryBot.create(:valid_user) }
RSpecのletを使うのはどんなときか?(翻訳) - Qiita
factoryの共通部分はtraitでまとめる
trait
を使うとfactory(テストデータ)の共通部分をまとめることが出来ます。
でもなんとなくfactory
を入れ子にするのは、まだ少し違和感がある・・・(._.)
# before FactoryBot.define do factory :user do name 'Example User' email "User@example.com" password 'foobar' password_confirmation 'foobar' end factory :valid_user_with_career, class: User do name 'Example User' email 'User_with_career@example.com' password 'foobar' password_confirmation 'foobar' after :create do |user| create(:vallid_career, user: user) end end end # after FactoryBot.define do factory :user_difine do trait :valid do name 'Example User' email "User@example.com" password 'foobar' password_confirmation 'foobar' end trait :with_profile do after :create do |user| create(:profile, user: user) end end factory :valid_user, class: User, traits: [:valid] factory :valid_user_with_profile, class: User, traits: [:valid,:with_profile] end end
FactoryGirlのtransientとtraitを活用する - Qiita
1度しか使わないようなデータは、factoryに定義せずテストコードで生成する
1度しか使わないのもは、factoryに書くよりもテストコードに直接書いた方が、factoryがスッキリし、テストコード上に意図が現れるのでわかりやすいですね(・∀・)
# before FactoryBot.define do factory :no_name_user, class: User do name '' email 'user@example.com' password 'foobar' password_confirmation 'foobar' end end RSpec.describe User, type: :model do describe 'Userのモデリングに関するテスト' do it 'nameが必須項目となっていること' do user = FactoryBot.build(:no_name_user) user.name = '' expect(user.valid?).to eq false end end end # after RSpec.describe User, type: :model do describe 'Userのモデリングに関するテスト' do let(:user){ FactoryBot.build(:user) } it 'nameが必須項目となっていること' do user = FactoryBot.build(:no_name_user) user.name = '' expect(user.valid?).to eq false end end end
おわりに
個人開発だけだと自分が書いているコードが良い感じなのかが良くわからないので、ソースコードレビューは、本当に学びが多いですね・・・!
これからもレビュー内容を定期的に整理して、きちんと身につけられるようにしたいですφ(..)