製品の品質を落とさずに開発チームを拡大する方法
開発チームの規模を拡大中ですか?製品の品質を犠牲にすることなく成長する方法を学びましょう。このガイドでは、スケールする時期、チーム構成、採用、リーダーシップ、ツールなどの兆候に加え、The Codestがどのように...
仕事で、オーバーロードされたモデルやコントローラ内の膨大な数の呼び出しに何度も遭遇している可能性はかなり高いでしょう。この記事では、Rails環境での知識に基づいて、この問題の簡単な解決策を提案します。
railsアプリケーションの非常に重要な側面は、冗長な依存関係の数を最小限に抑えることです。そのため、Rails環境全体が最近、サービスオブジェクトのアプローチとPORO(Pure Old ルビー Object)メソッドを使用します。このようなソリューションの使用方法については、以下を参照してください。 これ.この記事では、ステップ・バイ・ステップでコンセプトを解き、問題に適応させていく。
仮想的なアプリケーションでは、複雑なトランザクションシステムを扱っている。各トランザクションを表す我々のモデルは、データを取得するのに役立つスコープのセットを持っています。これは1つの場所で見つけることができるため、素晴らしい仕事の円滑化です。しかし、これは長くは続かない。アプリケーションの開発とともに プロジェクト はますます複雑になっている。スコープはもはや単純な "WHERE "参照ではなくなり、データが不足し、リレーションシップをロードし始める。しばらくすると、それはミラーの複雑なシステムを思い起こさせる。さらに悪いことに、複数行のラムダをどうやればいいのかわからない!
以下に、すでに拡張されたアプリケーション・モデルを示す。決済システムのトランザクションが格納されています。下の例でわかるように
クラス Transaction { where(visible: true) }.
scope(:active, lambda do
結合(<<-SQL
LEFT OUTER JOIN source ON transactions.source_id = source.id
AND source.accepted_at IS NOT NULL
SQL
end)
終了
モデルもそうですが、プロジェクトの規模が大きくなるにつれて、コントローラーも大きくなっていきます。下の例を見てみよう:
class TransactionsController < ApplicationController
def index
@transactions = Transaction.for_publishers
.active
.visible
.joins("LEFT JOIN withdrawal_items ON withdrawal_items.transaction_id = transactions.id")
.joins("LEFT JOIN withdrawals ON withdrawals.id = withdrawal_items.withdrawal_id OR
(withdrawals.id = source.resource_id AND source.resource_type = 'Withdrawal')")
.order(:created_at)
.page(params[:page])
.per(params[:page])
トランザクション = apply_filters(@transactions)
終了
終了
ここでは、何行もの連鎖メソッドと、多くの場所で実行したくない追加の結合が並んでいるのがわかる。添付されたデータは後でapply_filtersメソッドで使われ、GETパラメータに基づいて適切なデータフィルタリングを追加する。もちろん、これらの参照のいくつかをスコープに移すことはできるが、実際に解決しようとしているのはこの問題ではないだろうか?
私たちが抱えている問題はすでに分かっているのだから、これを解決しなければならない。冒頭のリファレンスに基づき、ここではPOROアプローチを使用する。この正確なケースでは、このアプローチはクエリーオブジェクトと呼ばれ、サービスオブジェクトのコンセプトを発展させたものである。
プロジェクトのappsディレクトリにservicesという新しいディレクトリを作りましょう。そこに トランザクションクエリ.
クラス TransactionsQuery
終了
次のステップとして、オブジェクトのデフォルト・コール・パスを作成するイニシャライザーを作成する必要があります。
クラス TransactionsQuery
def initialize(scope = Transaction.all)
スコープ = スコープ
終了
終わり
このおかげで、リレーションシップをアクティブレコードから施設に転送することができます。これで、提示されたコントローラでのみ必要とされる、すべてのスコープをクラスに転送することができます。
クラス TransactionsQuery
def initialize(scope = Transaction.all)
スコープ = スコープ
終了
プライベート
def active(scope)
scope.joins(<<-SQL
LEFT OUTER JOIN source ON transactions.source_id = source.id
AND source.accepted_at IS NOT NULL
SQL
終了
def visible(scope)
scope.where(visible: true)
終了
def for_publishers(scope)
scope.select("transactions.*")
.joins(:account)
.where("accounts.owner_type = 'Publisher'")
.joins("JOIN publishers ON owner_id = publishers.id")
終了
終了
最も重要な部分、つまりデータを1つの文字列に集め、インターフェイスをパブリックにすることがまだできていない。すべてをくっつけるメソッドの名前は「コール」とする。
本当に重要なのは、@scopeインスタンス変数を使うことだ。
クラス TransactionsQuery
...
def call
可視(@スコープ)
.then(&method(:active))
.then(&method(:for_publishers))
.order(:created_at)
終了
非公開
...
終了
クラス全体は次のように発表している:
クラス TransactionsQuery
def initialize(scope = Transaction.all)
スコープ = スコープ
終了
def call
visible(@scope)
.then(&method(:active))
.then(&method(:for_publishers))
.order(:created_at)
終了
プライベート
def active(scope)
scope.joins(<<-SQL
LEFT OUTER JOIN source ON transactions.source_id = source.id
AND source.accepted_at IS NOT NULL
SQL
終了
def visible(scope)
scope.where(visible: true)
終了
def for_publishers(scope)
scope.select("transactions.*")
.joins(:account)
.where("accounts.owner_type = 'Publisher'")
.joins("JOIN publishers ON owner_id = publishers.id")
終了
終了
クリーンアップ後、モデルの見た目は確実に軽くなった。ここでは、データの検証と他のモデルとの関係だけに集中する。
クラス Transaction < ActiveRecord::Base
所属 :口座
has_one :引き出し項目
終了
コントローラはすでに私たちの解決策を実装しています。追加のクエリはすべて別のクラスに移動しました。しかし、モデルになかった呼び出しは未解決のままです。いくつかの変更を経て、インデックスアクションは次のようになりました:
class TransactionsController < ApplicationController
def index
@transactions = TransactionsQuery.new
.call
.joins("LEFT JOIN withdrawal_items ON withdrawal_items.accounting_event_id = transactions.id")
.joins("LEFT JOIN withdrawals ON withdrawals.id = withdrawal_items.withdrawal_id OR
(withdrawals.id = source.resource_id AND source.resource_type = 'Withdrawal')")
.order(:created_at)
.page(params[:page])
.per(params[:page])
トランザクション = apply_filters(@transactions)
終了
終了
グッドプラクティスと慣例を実施する場合、与えられた問題の類似した発生をすべて置き換えることが良いアイデアかもしれません。したがって、SQLクエリをインデックス・アクションから別のクエリ・オブジェクトに移動します。これを TransactionsFilterableQuery クラスを用意する。私たちが準備する授業スタイルは、以下の授業で紹介するスタイルに似ています。 トランザクションクエリ.その一環として コード と呼ばれる複数行の文字列を使用して、より直感的に大規模な SQL クエリを記録できるようになります。 ヘレドック 解決策は以下にある:
クラス TransactionsFilterableQuery
def initialize(scope = Transaction.all)
スコープ = スコープ
終了
def call
withdrawal(@scope).then(&method(:withdrawal_items))
終了
プライベート
def withdrawal(scope)
scope.joins(<<-SQL
LEFT JOIN withdrawals ON withdrawals.id = withdrawal_items.withdrawal_id OR
(withdrawals.id = source.resource_id AND source.resource_type = 'Withdrawal')
SQL
終了
def withdrawal_items(scope)
scope.joins(<<-SQL
LEFT JOIN withdrawal_items ON withdrawal_items.accounting_event_id = transactions.id
SQL
終了
終了
コントローラーに変更があった場合は、クエリー・オブジェクトを追加することで行数を減らす。重要なのは、ページ分割を担当する部分以外はすべて切り離すことだ。
class TransactionsController < ApplicationController
def index
TransactionsFilterableQuery.new(scope).call.then do |scope|.
TransactionsFilterableQuery.new(scope).call
end.page(params[:page]).per(params[:page])
トランザクション = apply_filters(@transactions)
end
エンド
クエリオブジェクトはSQLクエリを書くアプローチを大きく変える。ActiveRecordでは、すべてのビジネス・ロジックとデータベース・ロジックが一箇所にまとまっているため、モデル内にすべてのビジネス・ロジックとデータベース・ロジックを配置することが非常に簡単です。これは、小規模なアプリケーションでは非常に有効です。プロジェクトの複雑さが増すにつれて、ロジックを他の場所に設定していきます。同じクエリオブジェクトで、メンバーのクエリを特定の問題にグループ化することができます。
Thanks to this, we have an easy possibility of the code’s later inheritance and because of duck typing, you can also use these solutions in other models. The disadvantage of this solution is a larger amount of code and fragmentation of responsibility. However, whether we want to take up such a challenge or not, depends on us and how badly we are disturbed by fat models.