サーチ…


備考

これは簡単なことのようですが、クラスで気球が始まると、時間をかけて整理していただき感謝しています。

モデルクラス

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

モデルは、通常、

  • 関係を設定する
  • データの検証
  • スコープとメソッドを介してデータへのアクセスを提供する
  • データの永続性に関する処理を実行します。

最高レベルでは、モデルはドメイン概念を記述し、永続性を管理します。

サービスクラス

コントローラは、アプリケーションのエントリポイントです。しかし、唯一可能なエントリーポイントではありません。私は自分のロジックを以下からアクセスできるようにしたいと考えています:

  • レイクタスク
  • バックグラウンドジョブ
  • コンソール
  • テスト

コントローラーにロジックを投げれば、これらの場所からアクセスすることはできません。そこで、「スキニーコントローラ、ファットモデル」アプローチを試して、そのロジックをモデルに移してみましょう。しかし、どちら?特定のロジックにUserCartProductモデルが含まれている場合、どこにそれを置く必要がありますか?

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.rbInvite::Createを定義しInvite::Create
  • サービスは動詞で始まります(サービスで終わらない): ApproveTransactionSendTestNewsletterImportUsersFromCsv
  • サービスはcallメソッドに応答しcall 。別の動詞を使用すると少し冗長になります: ApproveTransaction.approve()はうまく読みません。また、 callメソッドは、 lambdaprocs 、およびmethodオブジェクトの事実上のメソッドです。

利点

サービスオブジェクトは私のアプリケーションが何をするかを示します

サービスディレクトリを見れば、私のアプリケーションが何をするかを見ることができます: ApproveTransactionCancelTransactionBlockAccountSendTransactionApprovalReminder ...

サービスオブジェクトをすばやく見て、ビジネスロジックがどのようなものかを知っています。私はコントローラ、 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オブジェクトを使用した電子商取引の購入)
  • このアクションは、外部サービス(ソーシャルネットワークへの投稿など)
  • このアクションは、基礎となるモデルの中核的な関心事ではありません(例えば、一定期間後に古いデータを掃除するなど)。
  • アクションを実行するには複数の方法があります(たとえば、アクセストークンまたはパスワードによる認証)。

ソース

Adam Niedzielski Blog

Brew House Blog

コード気候のブログ



Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow