サーチ…


繰り返さないでください(ドライ)

きれいなコードを維持するために、RailsはDRYの原則に従います。

可能な限り、可能な限り多くのコードを再利用します。複数の場所で同じコードを複製するのではなく、部分的なコードを使用します。これにより、 エラーが減少し、コードがきれいに保たれ、 一度コードを記述してから再利用するという原則が適用されます。また、同じコードの複数の部分を更新するよりも、ある場所でコードを更新する方が簡単で効率的です。したがって、コードをモジュール化して堅牢にします。

あなたのモデルにコードを書いているのでまた、コントローラーでコードを書いているだけなので、Fat Model、Skinny ControllerはDRYです。

# Post model
scope :unpublished, ->(timestamp = Time.now) { where('published_at IS NULL OR published_at > ?', timestamp) } 


# Any controller
def index
    ....
    @unpublished_posts = Post.unpublished
    ....
end

def others
    ...
    @unpublished_posts = Post.unpublished
    ...
end

これはまた、内部メソッドが隠され、APIのやり方でパラメータを渡すことによって変更が達成されるAPI駆動構造につながります。

コンベンションオーバーコンフィグレーション

Railsでは、データベースのコントローラ、ビュー、モデルを見ています。

設定の必要性を減らすために、Railsはルールを実装してアプリケーションの操作を容易にします。独自のルールを定義しても構いませんが、最初はRailsが提供する規約に従うことをお勧めします。

これらの規約は、開発をスピードアップし、コードを簡潔かつ読みやすくして、アプリケーション内での簡単なナビゲーションを可能にします。

大会では初心者の入場障壁も低くなります。初心者が知る必要がないだけでなく、無知で恩恵を受けることができるように、Railsにはたくさんの慣習があります。なぜすべてが正しいのかを知らずにすばらしいアプリケーションを作成することは可能です。

例えば

主キーid持つordersというデータベーステーブルがある場合、一致するモデルはorderと呼ばれ、すべてのロジックを処理するコントローラはorders_controllerという名前にorders_controllerます。ビューは異なるアクションで分割されます。コントローラーにnewアクションとeditアクションがある場合は、 newビューとeditビューもあります。

例えば

アプリケーションを作成するには、単にrails new app_name実行しrails new app_name 。これにより、Railsアプリケーションのインフラと基盤を構成する約70のファイルとフォルダが生成されます。

それは以下を含む:

  • モデル(データベース層)、コントローラ、およびビューを保持するフォルダ
  • アプリケーションの単体テストを保持するフォルダ
  • JavascriptやCSSファイルなどのWebアセットを保持するフォルダ
  • HTTP 400レスポンスのデフォルトファイル(ファイルが見つからない)
  • 他の多く

脂肪モデル、スキニーコントローラ

「脂肪モデル、スキニーコントローラ」は、MVCのMとC部分が理想的に一緒に働く方法を指します。つまり、レスポンスに関係のないロジックは、理想的にはテスト可能な方法でモデルに入るべきです。一方、「スキニー」コントローラは、ビューとモデルの間の単なる素晴らしいインタフェースです。

実際には、これにはさまざまなタイプのリファクタリングが必要ですが、すべてが1つのアイデアになります。コントローラーではなく、モデルの応答ではないロジックを移動することで、再利用を促進するだけでなく可能であれば、要求のコンテキスト外でコードをテストすることも可能にしました。

簡単な例を見てみましょう。次のようなコードがあるとします。

def index
  @published_posts = Post.where('published_at <= ?', Time.now)
  @unpublished_posts = Post.where('published_at IS NULL OR published_at > ?', Time.now)
end

次のように変更することができます:

def index
  @published_posts = Post.published
  @unpublished_posts = Post.unpublished
end

次に、ロジックをポストモデルに移動すると、次のようになります。

scope :published, ->(timestamp = Time.now) { where('published_at <= ?', timestamp) }
scope :unpublished, ->(timestamp = Time.now) { where('published_at IS NULL OR published_at > ?', timestamp) }

default_scopeに注意してください

ActiveRecordにはdefault_scope含まれており、デフォルトでモデルを自動的にスコープします。

class Post
  default_scope ->{ where(published: true).order(created_at: :desc) }
end

上記のコードは、モデルのクエリを実行すると既に公開されている投稿を処理します。

Post.all # will only list published posts 

そのスコープは、無害に見えるものの、複数の隠された副作用があり、あなたが望まないかもしれません。

default_scopeorder

default_scopeorderを宣言したので、 Post呼び出しorderはデフォルトを上書きするのではなく、追加のオーダーとして追加されます。

Post.order(updated_at: :desc)
SELECT "posts".* FROM "posts" WHERE "posts"."published" = 't' ORDER BY "posts"."created_at" DESC, "posts"."updated_at" DESC

これはおそらくあなたが望む動作ではありません。最初にスコープからorderを除外することでこれを無効にすることができます

Post.except(:order).order(updated_at: :desc)
SELECT "posts".* FROM "posts" WHERE "posts"."published" = 't' ORDER BY "posts"."updated_at" DESC

default_scopeとモデルの初期化

他のActiveRecord::Relation同様に、 default_scopeは、初期化されたモデルのデフォルト状態を変更します。

上記の例では、 Postはデフォルトでwhere(published: true)設定されているので、 Post新しいモデルにも設定されています。

Post.new # => <Post published: true>

unscoped

default_scopeunscoped最初に呼び出すことで名目上クリアすることができますが、これには副作用もあります。たとえば、STIモデルを考えてみましょう。

class Post < Document
  default_scope ->{ where(published: true).order(created_at: :desc) }
end

デフォルトでは、 Postに対する照会はScopeされ、 'Post'を含む列がtypeます。しかし、 unscopedは独自のdefault_scopeと一緒にこれをクリアするので、 unscopedを使用する場合はこれも考慮に入れる必要があります。

Post.unscoped.where(type: 'Post').order(updated_at: :desc)

unscopedおよびモデル関連

PostUser関係

class Post < ApplicationRecord
  belongs_to :user
  default_scope ->{ where(published: true).order(created_at: :desc) }
end

class User < ApplicationRecord
  has_many :posts
end

個々のUser取得すると、関連する投稿を見ることができます。

user = User.find(1)
user.posts
SELECT "posts".* FROM "posts" WHERE "posts"."published" = 't' AND "posts"."user_id" = ? ORDER BY "posts"."created_at" DESC [["user_id", 1]]

しかし、あなたはposts関係からdefault_scopeをクリアしたいので、 unscopedを使う

user.posts.unscoped
SELECT "posts".* FROM "posts"

これにより、 default_scopeと同様にuser_id条件が消去されuser_id

default_scope使用例

そのすべてにかかわらず、 default_scopeを使用することが正当な理由があります。

複数のサブドメインが同じアプリケーションから配信されるが、分離されたデータがあるマルチテナントシステムを考えてみましょう。この分離を実現する1つの方法はdefault_scopeです。他の場合の短所はここでは不利になります。

class ApplicationRecord < ActiveRecord::Base
  def self.inherited(subclass)
    super

    return unless subclass.superclass == self
    return unless subclass.column_names.include? 'tenant_id'

    subclass.class_eval do
      default_scope ->{ where(tenant_id: Tenant.current_id) }
    end
  end
end

あなたがする必要があるのは、リクエストの早い段階でTenant.current_idを設定することだけです。 tenant_idを含むテーブルは、追加のコードなしで自動的にスコープになります。レコードをインスタンス化すると、作成されたテナントIDが自動的に継承されます。

このユースケースの重要な点は、スコープが要求ごとに1回設定され、変更されないことです。ここでスコープをunscoped必要がある唯一のケースは、要求スコープ外で実行されるバックグラウンドワーカーなどの特別なケースです。

あなたはそれを必要としない(YAGNI)

あなたが機能について「YAGNI」(あなたはそれを必要としないだろう)と言うことができるなら、あなたはそれを実装しないほうがよいでしょう。シンプルさに焦点を当てることで、多くの開発時間を節約できます。とにかくこのような機能を実装すると、問題が発生する可能性があります。

問題

Overengineering

製品がそれよりも複雑であれば、それは設計されています。通常、これらの「まだ使用されていない」機能は、意図された方法で使用されることはなく、使用されるとリファクタリングする必要があります。時期尚早の最適化、特にパフォーマンスの最適化は、しばしば設計上の決定につながり、将来的に間違っていることが判明します。

コードブロッティング

Code Bloatは不要な複雑なコードを意味します。これは、例えば、抽象化、冗長性、または設計パターンの誤った適用によって生じ得る。コードベースは、理解しにくく、混乱し、維持するのに費用がかかります。

フィーチャークリープ

フィーチャークリープとは、製品のコア機能を超える新しい機能を追加し、不必要に複雑な製品につながることを指します。

長い開発時間

必要な機能を開発するために使用できる時間は、不要な機能を開発するために費やされます。製品の配送に時間がかかります。

ソリューション

キス - それは簡単、愚かなままに

KISSによると、シンプルに設計されていれば、ほとんどのシステムが最適です。シンプリシティは、複雑さを軽減するための主要な設計目標です。これは、例えば、「単一責任原則」に従うことによって達成することができます。

YAGNI - あなたはそれを必要としない

少ないほうがいいですね。すべての機能を考えてください。本当に必要ですか?もしあなたがYAGNIであると思うなら、それを離れてください。それが必要なときに開発する方が良いです。

継続的リファクタリング

製品は着実に改善されています。リファクタリングでは、製品がベストプラクティスに従って実行されていることを確認し、パッチ作業に堕落しないようにすることができます。

ドメインオブジェクト(ファットモデルなし)

「Fat Model、Skinny Controller」は非常に良い第一歩ですが、コードベースが成長し始めるとスケールが上がらなくなります。

モデルの単体責任について考えてみましょう。モデルの単一の責任は何ですか?それはビジネスロジックを保持するのですか?それは非応答関連の論理を保持するのですか?

いいえ、その責任は、パーシスタンス層とその抽象概念を処理することです。

ビジネスロジックは、レスポンスに関連しないロジックや非パーシスタンス関連のロジックと同様に、ドメインオブジェクトに含める必要があります。

ドメインオブジェクトは、問題のドメイン内で1つの責任しか持たないように設計されたクラスです。あなたのクラスで、彼らが解決した問題を「 悲しみの建築 」にしましょう。

実際には、あなたはスキニーのモデル、スキニーのビューとスキニーコントローラに向かって努力する必要があります。ソリューションのアーキテクチャは、選択しているフレームワークの影響を受けてはいけません。

例えば

ストライプ経由でお客様に15%の固定手数料を請求するマーケットプレイスだとします。固定15%手数料を請求すると、注文額に応じて手数料が変わることを意味します。ストライプは2.9%+ 30¢です。

手数料としてお支払いいただくamount*0.15 - (amount*0.029 + 0.30)です。

このロジックをモデルに書き込まないでください:

# app/models/order.rb
class Order < ActiveRecord::Base
  SERVICE_COMMISSION = 0.15
  STRIPE_PERCENTAGE_COMMISSION = 0.029
  STRIPE_FIXED_COMMISSION = 0.30

  ...

  def commission
    amount*SERVICE_COMMISSION - stripe_commission  
  end

  private

  def stripe_commission
    amount*STRIPE_PERCENTAGE_COMMISSION + STRIPE_FIXED_COMMISSION
  end
end

新しい支払い方法と統合するとすぐに、このモデル内でこの機能を拡張することはできません。

また、より多くのビジネスロジックを統合し始めると、 Orderオブジェクトは結束を失い始めます。

永続的な注文の責任から完全に抽象化された手数料の計算で、ドメインオブジェクトを好む:

# app/models/order.rb
class Order < ActiveRecord::Base
  ...
  # No reference to commission calculation
end

# lib/commission.rb
class Commission
  SERVICE_COMMISSION = 0.15

  def self.calculate(payment_method, model)
    model.amount*SERVICE_COMMISSION - payment_commission(payment_method, model)  
  end

  private

  def self.payment_commission(payment_method, model)
    # There are better ways to implement a static registry,
    # this is only for illustration purposes.
    Object.const_get("#{payment_method}Commission").calculate(model)
  end
end

# lib/stripe_commission.rb
class StripeCommission
  STRIPE_PERCENTAGE_COMMISSION = 0.029
  STRIPE_FIXED_COMMISSION = 0.30

  def self.calculate(model)
    model.amount*STRIPE_PERCENTAGE_COMMISSION
      + STRIPE_PERCENTAGE_COMMISSION
  end
end

# app/controllers/orders_controller.rb
class OrdersController < ApplicationController
  def create
    @order = Order.new(order_params)
    @order.commission = Commission.calculate("Stripe", @order)
    ...
  end
end

ドメインオブジェクトを使用すると、次のようなアーキテクチャ上の利点があります。

  • ロジックを使用してオブジェクトをインスタンス化するための治具やファクトリは必要ないため、単体テストは非常に簡単です。
  • メッセージamountを受け入れるすべてのもので動作します。
  • それぞれのドメインオブジェクトを小さく保ち、明確な責任を明確にし、より高い結束性を維持します。
  • 追加ではなく、変更による新しい支払い方法で簡単に拡張できます
  • 各Ruby on Railsアプリケーションで常に増加するUserオブジェクトを持つ傾向をなくします。

私は個人的にlibにドメインオブジェクトを置くのが好きです。その場合は、 autoload_pathsに追加することを忘れないでください:

# config/application.rb
config.autoload_paths << Rails.root.join('lib')

また、コマンド/クエリのパターンに従って、より行動指向のドメインオブジェクトを作成することもできます。そのような場合、これらのオブジェクトをapp/commandsに入れるのは、すべてのappサブディレクトリが自動ロードパスに自動的に追加されるため、より良い場所になる可能性があります。



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