RailsでのOOデザイン:どこに置くか


244

私は本当にRailsを楽しんでいます(一般的にRESTを使用していませんが)。Rubyが非常にオブジェクト指向であることを楽しんでいます。それでも、巨大なActiveRecordサブクラスと巨大なコントローラーを作成する傾向は非常に自然です(リソースごとにコントローラーを使用する場合でも)。より深いオブジェクトワールドを作成する場合、クラス(およびモジュール)をどこに配置しますか?ビュー(ヘルパー自体?)、コントローラー、モデルについて質問しています。

Libは大丈夫です。開発環境再読み込みするためのいくつかの解決策を見つけましたが、これを行うためのより良い方法があるかどうか知りたいのですが。クラスが大きくなりすぎるのを本当に心配しています。また、エンジンはどうですか、どのように適合しますか?

回答:


384

RailsはMVCの観点から構造を提供するため、提供されているモデル、ビュー、コントローラーのコンテナーのみを使用するのが自然です。初心者(および一部の中間プログラマー)の典型的なイディオムは、アプリ内のすべてのロジックをモデル(データベースクラス)、コントローラー、またはビューに詰め込むことです。

ある時点で、誰かが「ファットモデル、スキニーコントローラー」パラダイムを指摘し、中間の開発者がコントローラーからすべてを急いで切り出し、モデルに投入します。これは、アプリケーションロジックの新しいゴミ箱になり始めます。

実際、細いコントローラーは良いアイデアですが、当然のことですが、モデルにすべてを入れることは、実際には最良の計画ではありません。

Rubyでは、よりモジュール化するための優れたオプションがいくつかあります。かなり一般的な答えはlib、メソッドのグループを保持するモジュール(通常はに隠しておく)を使用し、そのモジュールを適切なクラスに含めることです。これは、複数のクラスで再利用したい機能のカテゴリがあり、機能がまだ概念的にクラスに関連付けられている場合に役立ちます。

モジュールをクラスに含めると、メソッドはクラスのインスタンスメソッドになるので、大量のメソッドを含むクラスになりますが、それらは複数のファイルにうまく整理されています。

このソリューションは、場合によってはうまく機能します。他の場合ではモデル、ビュー、またはコントローラーではないクラスをコード内で使用することを検討する必要があります。

それを考える良い方法は、「単一責任の原則」です。これは、クラスが単一(または少数)の事柄に対して責任を持つべきであると述べています。モデルは、アプリケーションからデータベースへのデータの永続化を担当します。コントローラーは、要求を受信して​​実行可能な応答を返す責任があります。

これらのボックスにうまく収まらない概念(持続性、要求/応答管理)がある場合、問題のアイデアをどのようモデル化するかについて考えたくなるでしょう。非モデルクラスをapp / classesまたはその他の場所に保存し、次のようにしてそのディレクトリをロードパスに追加できます。

config.load_paths << File.join(Rails.root, "app", "classes")

パッセンジャーまたはJRubyを使用している場合は、パスを熱心なロードパスに追加することもできます。

config.eager_load_paths << File.join(Rails.root, "app", "classes")

要点は、いったんRailsでこの質問をするポイントに到達したら、Rubyチョップを強化し、Railsがデフォルトで提供するMVCクラスだけではないクラスのモデリングを開始するときが来たということです。

更新:この回答はRails 2.x以降に適用されます。


ドー。非モデル用に別のディレクトリを追加することは私には起こりませんでした。片付けが近づいてくるのを感じることができます...
マイクウッドハウス

イェフダ、ありがとう。すばらしい答えです。それは私が継承するアプリ(および私が作成するもの)で私が見ているものとまったく同じです。コントローラー、モデル、ビュー、およびコントローラーとビューに自動的に提供されるヘルパーのすべて。その後、libからミックスインが来ますが、実際のOOモデリングを実行する試みはありません。しかし、あなたは正しい:「アプリ/クラス、または他のどこでも」。ただ...私は欠けているいくつかの標準的な答えはありますかどうかを確認したかった
ダンRosenstark

33
最近のバージョンでは、config.autoload_pathsのデフォルトはappの下のすべてのディレクトリです。したがって、上記のようにconfig.load_pathsを変更する必要はありません。(まだ)eager_load_pathsについてはよくわかりません。詳しく調べる必要があります。誰か知っていますか?
Shyam Habarakada 2012年

中級者に対する積極的なパッシブ:P
セバスチャンパッテン

8
Railsがこの「classes」フォルダーを同梱して「単一責任の原則」を奨励し、開発者がデータベースに対応していないオブジェクトを作成できるようにするとよいでしょう。Rails 4の「懸念事項」の実装(Simoneの回答を参照)は、モデル間でロジックを共有するためのモジュールの実装を担当しているようです。ただし、このようなツールは、データベースでサポートされていない単純なRubyクラス用には作成されていません。Railsが非常に独断的であることを考えると、このようなフォルダーを含めない背後にある思考プロセスに興味がありますか?
ライアンフランシス

62

更新:懸念事項の使用がRails 4の新しいデフォルトとして確認されました

それは本当にモジュール自体の性質に依存します。私は通常、コントローラー/モデル拡張をアプリ内の/ concernsフォルダーに配置します。

# concerns/authentication.rb
module Authentication
  ...
end    

# controllers/application_controller.rb
class ApplicationController
  include Authentication
end



# concerns/configurable.rb
module Configurable
  ...
end    

class Model 
  include Indexable
end 

# controllers/foo_controller.rb
class FooController < ApplicationController
  include Indexable
end

# controllers/bar_controller.rb
class BarController < ApplicationController
  include Indexable
end

/ libは、汎用ライブラリの場合に推奨される選択肢です。私は常にlibにプロジェクト名前空間を持ち、ここにすべてのアプリケーション固有のライブラリを配置します。

/lib/myapp.rb
module MyApp
  VERSION = ...
end

/lib/myapp/CacheKey.rb
/lib/myapp/somecustomlib.rb

Ruby / Railsコア拡張機能は、通常、ライブラリがRailsブーストラップで一度だけロードされるように、構成初期化子で行われます。

/config/initializer/config.rb
/config/initializer/core_ext/string.rb
/config/initializer/core_ext/array.rb

再利用可能なコードフラグメントの場合、他のプロジェクトで再利用できるように(マイクロ)プラグインを作成することがよくあります。

ヘルパーファイルは通常、ヘルパーメソッドを保持し、オブジェクトがヘルパー(たとえば、フォームビルダー)によって使用されることを目的とする場合はクラスも保持します。

これは本当に一般的な概要です。よりカスタマイズされた提案を得たい場合は、具体的な例について詳しく説明してください。:)


奇妙なこと。このrequire_dependency RAILS_ROOT + "/ lib / my_module"をlibディレクトリ以外のもので動作させることはできません。それは間違いなく実行され、ファイルが見つからない場合は文句を言いますが、再ロードはしません。
Dan Rosenstark、2009

Rubyのrequireは一度だけロードします。無条件に何かをロードしたい場合は、loadを使用します。
チャック

また、アプリインスタンスの存続期間中にファイルを2回ロードすることは、私にはかなり珍しいようです。進行中にコードを生成していますか?
チャック

なぜrequireではなくrequire_dependencyを使用するのですか?また、命名規則に従っている場合は、requireを使用する必要がないことにも注意してください。lib / my_moduleにMyModuleを作成する場合、以前のrequireなしでMyModuleを呼び出すことができます(requireを使用する方が高速で読みやすい場合もある)。/ lib内のファイルは、ブートストラップで一度だけロードされることにも注意してください。
Simone Carletti、2009

1
懸念の使用が懸念される
bbozo

10

...巨大なActiveRecordサブクラスと巨大なコントローラーを作成する傾向は非常に自然です...

「巨大」は気になる言葉です... ;-)

コントローラはどのように巨大になりますか?それはあなたが見るべきものです:理想的には、コントローラーは薄いべきです。経験則を薄い空気から選ぶ場合、コントローラーのメソッド(アクション)ごとに、たとえば5行または6行を超えるコードが定期的にある場合、コントローラーがおそらく太すぎることをお勧めします。ヘルパー関数またはフィルターに移動する可能性のある重複はありますか?モデルにプッシュダウンできるビジネスロジックはありますか?

モデルはどのようにして巨大になりますか?各クラスの責任の数を減らす方法を検討すべきですか?ミックスインに抽出できる一般的な動作はありますか?または、ヘルパークラスに委任できる機能の領域は?

編集:少し拡大しようとしていますが、ひどく歪めないでください...

ヘルパー:住んでapp/helpersおり、主にビューを単純化するために使用されます。それらは、コントローラー固有(そのコントローラーのすべてのビューでも使用可能)または一般的に使用可能(module ApplicationHelperapplication_helper.rb内)のいずれかです。

フィルター:複数のアクションで同じコード行を使用しているとします(かなり頻繁に、オブジェクトを使用して取得するparams[:id]など)。そのような複製は、最初に別のメソッドに抽象化し、次に、などのクラス定義でフィルターを宣言することにより、アクションから完全に抽象化できますbefore_filter :get_objectActionController Railsガイドのセクション6を参照してください。宣言型プログラミングを友だちにしましょう。

モデルのリファクタリングは、もう少し宗教的なことです。ボブおじさんの弟子たちは、たとえば、SOLIDの 5つの戒めに従うことを勧めます。JoelとJeff 、より「より実用的な」アプローチを推奨するかもしれませんが、その後は少し調整されているようです。属性の明確に定義されたサブセットを操作するクラス内の1つ以上のメソッドを見つけることは、ActiveRecord派生モデルからリファクタリングされる可能性のあるクラスを特定するための1つの方法です。

ところで、RailsモデルはActiveRecord :: Baseのサブクラスである必要はありません。または別の言い方をすれば、モデルはテーブルの類似物である必要はありません。さらに良いことに、app/modelsRailsの規則に従ってファイルに名前を付ける限り(クラス名で#underscoreを呼び出して、Railsが何を探すかを見つける)、Railsはrequiresを必要とせずにそれを見つけます。


すべての点で正しい、マイク、そしてあなたの懸念に感謝...私は巨大なコントローラーにいくつかのメソッドがあったプロジェクトを継承しました。私はこれらをより小さなメソッドに分解しましたが、コントローラー自体はまだ「太っている」のです。だから私が探しているのは、ものをオフロードするための私のオプションのすべてです。あなたの答えは、「ヘルパー関数」、「フィルター」、「モデル」、「ミックスイン」、「ヘルパークラス」です。では、これらをどこに置けばいいのでしょうか。開発環境で自動ロードされるクラス階層を整理できますか?
Dan Rosenstark

1

以下は、「シンコントローラー」の哲学から生まれたと思われる脂肪モデルのリファクタリングに関する優れたブログ投稿です。

http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/

基本的なメッセージは「Fat ModelsからMixinsを抽出しないでください」です。代わりにサービスクラスを使用してください。作成者は7つのパターンを提供しています

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.