RailsベースのRESTプロバイダー:悪夢のようなビュー

ブラウザベースのMVCフレームワークの開発により、RESTプロバイダーにとって便利なフレームワークのコンテキストでRailsが頻繁に言及されるようになりました。 また、この目的のためにRailsを使用しています。 ただし、ビューという非常に大きな問題があります。 応答のJSON構造を記述するビュー。



一見、すべてが正常です。 複雑な場合には、 .to_json



またはRABLのみが必要です。 しかし、状況は制御不能になります。 そして、より良い生活を求めてJSONビルダーを検索する無限のサイクルがあります。



問題



銀行サービスを例に取りましょう。 30のモデルで構成されています。 各モデルはCRUDリソース(それぞれ3〜4個の拡張メソッド)で表されます。 各モデルには10〜12個のフィールドがあり、これらは通常長い行です。 そして、もちろん、それらはすべて接続されています。 最大4-5個のbelongs_to



レベル。



実生活では、JSON応答は単なるモデル構造の直接のダンプではないことを覚えておくことが重要です。 常に条件(回答に含まれるべき属性は?別の属性に依存)とカスタムメソッドに遭遇します。



表現の問題は、RESTサービスのクライアントが、そのような各モデルおよびこのRESTリソースの_メソッドごとに固有のモデルフィールドのセットを必要とすることです。 また、ネストされたエンティティについても忘れないでください。





各モデルに4〜5セットのフィールドがあると想像してください。 そして、これはほんの始まりに過ぎません。 その後、モデルには別のモデルが含まれます。 また、親は、関係を完全に説明する3つの小さなフィールドのみを見たいと考えています。 そして、この同じモデルには、他に2つのフィールドが必要な別の親が含まれます。 これは約10種類のセットです。 そして、そのような各セットには、モデルがそれ自体に投資するものに対する追加の条件がまだあるかもしれません。



痛み



私たちが始めたソリューションはRABLです。 最初は非常に効果的に見えますが、実際には複雑な表現にはまったく適していません。 実際には、RABLは.to_json



からこれまでのところ行っていません。 多くの異なるビルダーを試し、最終的にはJbuilder gemに落ち着きました。これにより、構文ノイズを回避しながら、非常に簡単でシンプルなコードを作成できます。



しかし、それは助けにはなりませんでした。 コードを複製しないように表現で何をしますか? パーサルを使用します。 すぐに、これにより、各モデルに10〜15個のパーシャルが作成されました。 これに30モデルを掛けると、 app/views



450個のファイルが得られapp/views



。 この束を維持することは単に不可能です。



発表者パターン



このような問題を解決するためのよく知られたアプローチは、Presenterパターンです。 ビューは単なるRubyコードであるため、論理的な最初のステップは、クラスにラップすることでした。



 # example taken from http://quickleft.com/blog/presenters-as-a-solution-to-asjson-woes-in-rails-apis class Api::V1::ResourcePresenter attr_reader :resource def initialize( resource ) @resource = resource end def as_json( include_root = false ) data_hash = { :attr1 => @resource.attr1, :attr2 => @resource.attr2 } data_hash = { :resource => data_hash } if include_root data_hash end end
      
      







これにより、ファイルの数を削減し、同様のフィールドセットを入力パラメーターを使用して1つのメソッドにグループ化することができました。



素晴らしい。 モデルフィールドのセットを記述するデコレータの比率は1対1になりました。 しかし今、別の問題が発生しています。このコードはRailsに期待されるものとは異なります。



最良の結果により、gem Draperを実現できます。 これにより、上記のコードを次のように変換できます。



 # app/decorators/article_decorator.rb class ArticleDecorator < ApplicationDecorator decorates :article def the_very_important_fields_set( include_root = false ) data_hash = { :attr1 => att1, :attr2 => attr2 } data_hash = { :resource => data_hash } if include_root data_hash end end
      
      







しかし、問題は終わりませんでした。現在、DRYの明らかな違反が表面化しています。 多数のフィールドがある場合、キーに同じ行( :foo



およびself.foo



値)を含む巨大なハッシュに固執します。



Draperは共通のApplicationクラスをサポートしているため、これは小さなバインディングメソッドで非常に簡単に解決できます。 ただし、私たちの目標は、Jbuilderでの作業を改善することです。 また、Jbuilderには、この問題を解決するメソッドがすでにあることに注意してください。 ハッシュを操作する必要はありません。デコレータで直接Jbuilderを使用して、一連の文字列から応答を収集できます。



この記事の執筆時点では、Jbuilderは生成時に生のJSON文字列を挿入することを許可していません。 ただし、望ましい結果を達成するのに役立つ別のアプローチがあります。 優れた分岐点があります(プル要求は作成者によってすでに部分的に確認されており、この機能はまもなくJbuilder自体に該当します)。



このフォークを使用して、次のようにコードを変更できます。



 # app/decorators/article_decorator.rb class ArticleDecorator < ApplicationDecorator decorates :article def the_very_important_fields_set( include_root = false ) data = Jbuilder.encode do |j| j.(self, :attr1, :attr2) end data = { :resource => data } if include_root end def another_set Jbuilder.encode do |j| j.(self, :attr1, :attr2, :attr3) j.cards card.basic_fields(:include_transactions) end end end
      
      







これは最良の例ではないかもしれませんが、Jbuilderをこの方法で使用すると、大きなデコレータが大幅に簡素化されることが実際にわかっています。



その結果、次の構造になります。







そのような戦略は少し冗長に見えるかもしれません。 しかし、これは大規模なデータレイヤーでは非常にうまく機能します。 この戦略により、RESTプロバイダーは正確になり(特定のメソッドが各要求に必要とするフィールドのセットを正確に与えます)、重複を避け、サポートの単純さを保ちます。



PSセキュリティについて



この戦略は、異なる役割に異なるフィールドを分散する問題も解決するようです。 そして、本当にそうです。 ただし、これは通常、実行する価値がありません-ロジックの重複につながる可能性があります。 データの配布を許可するだけでなく、変更も必要です。



この戦略にたどり着くまでに、すでに優れたHeimdallr gemがあり、この問題をより良く解決することができました。 しかし、これは完全に別の記事のトピックです:)。



All Articles