Ruby on Rails
クラス構成
サーチ…
備考
これは簡単なことのようですが、クラスで気球が始まると、時間をかけて整理していただき感謝しています。
モデルクラス
class Post < ActiveRecord::Base
belongs_to :user
has_many :comments
validates :user, presence: true
validates :title, presence: true, length: { in: 6..40 }
scope :topic, -> (topic) { joins(:topics).where(topic: topic) }
before_save :update_slug
after_create :send_welcome_email
def publish!
update(published_at: Time.now, published: true)
end
def self.find_by_slug(slug)
find_by(slug: slug)
end
private
def update_slug
self.slug = title.join('-')
end
def send_welcome_email
WelcomeMailer.welcome(self).deliver_now
end
end
モデルは、通常、
- 関係を設定する
- データの検証
- スコープとメソッドを介してデータへのアクセスを提供する
- データの永続性に関する処理を実行します。
最高レベルでは、モデルはドメイン概念を記述し、永続性を管理します。
サービスクラス
コントローラは、アプリケーションのエントリポイントです。しかし、唯一可能なエントリーポイントではありません。私は自分のロジックを以下からアクセスできるようにしたいと考えています:
- レイクタスク
- バックグラウンドジョブ
- コンソール
- テスト
コントローラーにロジックを投げれば、これらの場所からアクセスすることはできません。そこで、「スキニーコントローラ、ファットモデル」アプローチを試して、そのロジックをモデルに移してみましょう。しかし、どちら?特定のロジックにUser
、 Cart
、 Product
モデルが含まれている場合、どこにそれを置く必要がありますか?
ActiveRecord::Base
から継承したクラスは、すでに多くの責任を負っています。これは、クエリインターフェイス、関連付けと検証を処理します。モデルにさらに多くのコードを追加すると、何百ものパブリックメソッドがすぐに維持されなくなります。
サービスは通常のRubyオブジェクトです。そのクラスは特定のクラスから継承する必要はありません。その名前は、たとえば、動詞句であるCreateUserAccount
のではなくUserCreation
またはUserCreationService
。これはapp / servicesディレクトリにあります。このディレクトリは自分で作成する必要がありますが、Railsは内部でクラスを自動読み込みします。
サービスオブジェクトは1つのことを行います
サービスオブジェクト(別名メソッドオブジェクト)は、1つのアクションを実行します。そのアクションを実行するビジネスロジックを保持します。次に例を示します。
# app/services/accept_invite.rb
class AcceptInvite
def self.call(invite, user)
invite.accept!(user)
UserMailer.invite_accepted(invite).deliver
end
end
私が従う3つの慣例は次のとおりです。
サービスは、 app/services directory
ます。私はビジネスロジック重いドメインのためにサブディレクトリを使用することをお勧めします。例えば:
-
app/services/invite/accept.rb
ファイルはInvite::Accept
を定義し、app/services/invite/create.rb
はInvite::Create
を定義しInvite::Create
- サービスは動詞で始まります(サービスで終わらない):
ApproveTransaction
、SendTestNewsletter
、ImportUsersFromCsv
- サービスは
call
メソッドに応答しcall
。別の動詞を使用すると少し冗長になります:ApproveTransaction.approve()
はうまく読みません。また、call
メソッドは、lambda
、procs
、およびmethodオブジェクトの事実上のメソッドです。
利点
サービスオブジェクトは私のアプリケーションが何をするかを示します
サービスディレクトリを見れば、私のアプリケーションが何をするかを見ることができます: ApproveTransaction
、 CancelTransaction
、 BlockAccount
、 SendTransactionApprovalReminder
...
サービスオブジェクトをすばやく見て、ビジネスロジックがどのようなものかを知っています。私はコントローラ、 ActiveRecord
モデルのコールバック、オブザーバを経由して "トランザクションの承認"に関係することを理解する必要はありません。
クリーンアップモデルとコントローラ
コントローラはリクエスト(params、session、cookies)を引数に変換し、サービスに渡し、サービス応答に従ってリダイレクトまたはレンダリングします。
class InviteController < ApplicationController
def accept
invite = Invite.find_by_token!(params[:token])
if AcceptInvite.call(invite, current_user)
redirect_to invite.item, notice: "Welcome!"
else
redirect_to '/', alert: "Oopsy!"
end
end
end
モデルはアソシエーション、スコープ、検証、永続性のみを扱います。
class Invite < ActiveRecord::Base
def accept!(user, time=Time.now)
update_attributes!(
accepted_by_user_id: user.id,
accepted_at: time
)
end
end
これにより、モデルとコントローラーのテストと保守が非常に簡単になりました。
サービスクラスを使用する場合
アクションが次の1つ以上の条件を満たす場合、サービスオブジェクトに到達します。
- 行為は複雑である(例えば、会計期間の終わりに本を閉じる)
- このアクションは、複数のモデルにまたがっています(たとえば、Order、CreditCard、およびCustomerオブジェクトを使用した電子商取引の購入)
- このアクションは、外部サービス(ソーシャルネットワークへの投稿など)
- このアクションは、基礎となるモデルの中核的な関心事ではありません(例えば、一定期間後に古いデータを掃除するなど)。
- アクションを実行するには複数の方法があります(たとえば、アクセストークンまたはパスワードによる認証)。
ソース