Help us understand the problem. What is going on with this article?

Railsでの開発を快適にするための工夫集

目的

  • Rails案件(だけの話ではないだろうけど)には、「このメソッドを変えると、どこに影響が出るんだろう」とか「このクラスはどこで作られたり、更新されてるんだっけ」という疑問が頻繁に起こる。
  • なので、なるべくその疑問がスピーディに解消できるような工夫をまとめておく。

工夫集

ActiveRecordのcreateupdateは「決まった形」から生やす

  • 特定のActiveRecordのcreateupdateと言った、DB処理があちこちに散らばっている場合には、検索しやすいように特定の形に限定しておくことで、楽に影響範囲を調査できる。
  • 具体的には、呼び出し元をAdminUserとする場合には、以下の形に限定する。
    • AdminUser(Camelケース)
    • admin_user(Snakeケース)
  • 加えて、DBメソッドも以下に限定する
    • new
    • save, save!
    • create, create!
    • update, update!
    • destroy, destroy!
    • find_or_create_by, find_or_create_by!
    • find_or_initialize_by
  • こうすれば、パターンとしてはせいぜい(2 x 7 = 14通り)になるので(あれ?結構大変?)、思わぬ見過ごしが減って、影響範囲がすぐにわかるようになる。
  • あと、newで呼び出す場合は必ず以下のどちらかの形になる
@admin_user = AdminUser.new(...)
admin_user = AdminUser.new(...)

アンチパターン

  • 「コードを短くしたい」という名目でau.createとかにしてしまうと、発見しづらくなる
  • has_many関連のモデルをUser.dogs.createとかで作成するのも、上のパターンから外れるのでNG

DB処理が走る箇所にはユニットテストを仕込む

  • これをしておくだけでも、見落としをだいぶん防げる。
  • 基本全てのメソッドを手動で試し尽くすことはできないので、崩れて欲しくないロジックなどにはテストを必ず挟むこと

DBスキーマの修正とFactory、DBシードの修正をセットにする

  • 個人的な意見だが、「プロジェクトの開発環境の健康度」と「プロジェクトのDBシードの管理具合」の間には正の相関があると思っている。
  • 一応「DBシード」とは、「開発環境のDBに入れるデフォルトデータ」のことである。
  • そして「良いDBシード」とは、「アプリのビュー表示ロジックの、ありとある可能性を網羅できるデータ」とする。
  • これがしっかり管理されていると、コードの修正によって問題が発生した時に、すぐさま何処のビューに「エラー」として表示されるため、バグの発見が早まるのだ。
  • なので、「DBに新しいフィールドが追加・更新・削除される」と言った場合には、必ずセットでFactoryとSeedを修正することをセットにする。

コントローラーアクションへのアクセス権限ロジックはPunditに集約する

  • ログインユーザーの状態(プランの違いだったり、登録情報の有無だったり)によって、「コントローラーの特定のアクション」をさせたくないとする。
  • かといって、その条件文をコントローラー内に書くと、コントローラーが「重く」なってしまう。
  • そんな時にはpunditというGemが役に立つ。
  • これを使えば以下のように、権限周りの条件文をapp/policies以下のディレクトリで一元管理することができる。(細かい使い方は簡単なので、リポジトリを参照)
# app/policies/article_policy.rb
# ログインユーザーの「記事オブジェクト」に関する行動範囲
class ArticlePolicy < ApplicationPolicy
  # 詳細ページへは誰でもいける
  def show?
    true
  end

  # newページへはcreateができる権限でないと行けない
  def new?
    create?
  end

  # ログインユーザーがpennameを入力してないと作成できない。
  def create?
    user.penname.present?
  end
end

決まり切った処理はServiceクラスでパッケージ化

  • 複数のテーブルが関連するDB処理の場合、順番が大事なケースがある。
  • その場合、Serviceクラスで処理を全てパッケージ化すると、管理がとても楽になる
  • サービスクラスの鉄則として、クラス内で直接モデルを呼び出さない。必要なモデルは全て引数から入れる
  • いわゆる「ダックタイピング」な疎結合のクラスを作ることで、モデルの呼び出しを全てコントローラー内に収めることができる。
  • また小ネタ的な使い方だが、サービスクラス内で処理されるモデルを全部attr_readerに登録しておくことで、「ああこのサービスクラスではこのモデルを処理してるのか」が一発でわかる。
  • あとは「なんのためのサービスクラスか」をコメントしておくと、よりGOOD。
# app/services/sample_service.rb

# UserとDogを同時に保存するサービスクラス
class SampleService
  attr_reader :user, :dog

  def initialize(user, dog)
    @user = user
    @dog = dog
  end

  # ActiveRecord::RecordInvalidをthrowする可能性があるため
  def call!
    ActiveRecord::Base.transaction do
      user.save!
      dog.save!
      # これはアウト
      # Dog.create!(user: user)
    end
  end
end

ビューで「データをそのまま表示するメソッド」と「ステータスによってデータを加工・表示するメソッド」は分ける

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away