将来を見据えたウェブ・アプリケーションの構築:The Codestのエキスパート・チームによる洞察
The Codestが、最先端技術を駆使してスケーラブルでインタラクティブなウェブアプリケーションを作成し、あらゆるプラットフォームでシームレスなユーザー体験を提供することにどのように秀でているかをご覧ください。The Codestの専門知識がどのようにデジタルトランスフォーメーションとビジネス...
仕事で、オーバーロードされたモデルやコントローラ内の膨大な数の呼び出しに何度も遭遇している可能性はかなり高いでしょう。この記事では、Rails環境での知識に基づいて、この問題の簡単な解決策を提案します。
railsアプリケーションの非常に重要な側面は、冗長な依存関係の数を最小限に抑えることです。そのため、Rails環境全体が最近、サービスオブジェクトのアプローチとPORO(Pure Old Ruby 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では、すべてのビジネス・ロジックとデータベース・ロジックが一箇所にまとまっているため、モデル内にすべてのビジネス・ロジックとデータベース・ロジックを配置することが非常に簡単です。これは、小規模なアプリケーションでは非常に有効です。プロジェクトの複雑さが増すにつれて、ロジックを他の場所に設定していきます。同じクエリオブジェクトで、メンバーのクエリを特定の問題にグループ化することができます。
このおかげで、コードの継承が容易になり、またアヒルの型付けのおかげで、他のモデルでもこれらの解決策を使うことができる。この解決策の欠点は、コードの量が多くなり、責任が細分化されることである。しかし、このような課題に挑戦するかどうかは、私たち次第であり、太ったモデルにどれだけ悩まされるかは私たち次第である。