Ruby on Railsのプロジェクトの複雑さの管理。 パート1

この一連の記事では、Ruby on Railsの開発経験のほとんどを集めます。 これらの手法により、複雑さを制御し、プロジェクトのメンテナンスを容易にすることができます。 それらのほとんどは私によって発明されたものではありません。可能であれば、ソースを示します。

RoRプロジェクトの主な問題は、原則として、すべてのロジックをモデル、コントローラー、および表現に適合させようとすることです。 つまり コードは、モデル(ActiveRecord :: Base)、コントローラー、ヘルパー、およびテンプレートでのみ見つかります。 このアプローチは悲しい結果につながります。コードが混乱し、機能の作成に時間がかかり、リグレッションが現れ、開発者のモチベーションが消えます。 例として、 redmineのソースを見ることができます。

この状況から抜け出す方法はかなり明白です。 レール上のルビーではなく、レール上のルビーを使用してプロジェクトを行います。 外観:MVCとRailsから離れるのではなく、Model、View、Controllerを修正するだけです。 まず、モデルの概念を拡張します。 モデルは単なるORM派生クラスではありません。 モデルは、アプリケーションのすべてのビジネスロジックです。 モデルには、モデル、サービス、ポリシー、リポジトリ、フォーム、およびその他の要素が含まれます。これらについてはさらに説明します。 プレゼンテーションも拡大します。 ビューは、テンプレート、プレゼンター、ヘルパー、フォームビルダーです。 コントローラーは、リクエスト処理に関連するすべてのものです:コントローラー、レスポンダー。

これらの手法に加えて、SOLID、ルビースタイルガイド、レールの規則、ルビーオブジェクトモデル、ルビーメタプログラミング、および基本パターンの知識が役立ちます。

ヘルパー


最も簡単なアドバイスは、ヘルパーを使用することです。 それらを使用すると、頻繁な操作を説明するのに便利です。

module ApplicationHelper def menu_item(model, action, name, url, link_options = {}) return unless policy(model).send "#{action}?" content_tag :li do link_to name, url, link_options end end end # _nav.haml = menu_item current_user, :show, t(:show_profile), user_path(current_user) = menu_item current_user, :edit, t(:edit_profile), edit_user_path(current_user) 


menu_itemヘルパーは、ポリシーに応じてメニュー項目を表示します。 このヘルパーを展開すると、アクティブなメニュー項目が強調表示されます。

 module ApplicationHelper def han(model, attribute) model.to_s.classify.constantize.human_attribute_name(attribute) end def show_attribute(model, attribute) value = model.send(attribute) return if value.blank? [ content_tag(:dt, han(model.model_name, attribute)), content_tag(:dd, value) ].join.html_safe end end # show.haml = show_attribute user_presenter, :name = show_attribute user_presenter, :role_text = show_attribute user_presenter, :profile_image 


show_attributeヘルパーは、属性の名前とその値(存在する場合)を出力します。

フォームテンプレート


 = simple_form_for @user, builder: PunditFormBuilder do |f| = f.input :name = f.input :contacts, as: :big_textarea # some other inputs = f.button :submit 


gem simple_formを使用してフォームをレンダリングしています。 このgemは、フォームを表示するすべての作業を引き受けます。 非標準のデザインフォームの場合、このgemは機能しませんが、標準フォームには完全に適合します。

フォームを作成するとき、必要なものだけを指定します。フィールドのリストとそのタイプです。 ラベル、プレースホルダー、送信のテキストは自動的に置換されます-翻訳ファイルに正しいキーを入力するだけです:

 ru: attributes: created_at:  activerecord: attributes: user: name:  helpers: submit: create:  


次に、入力について詳しく説明します。
たとえば、すべてのテキストフォームには少なくとも10行が含まれている必要があります。

 class BigTextareaInput < SimpleForm::Inputs::TextInput def input_html_options { rows: 10 } end end 


これは非常に単純な例であり、入力はさらに複雑になる可能性があります。 たとえば、モデルをどの状態に転送できるかを選択します(gem state_machines)。

SimpleFormでは、フォームビルダーを接続することもできます。

 class PunditFormBuilder < SimpleForm::FormBuilder def input(attribute_name, options = {}, &block) return unless show_attribute? attribute_name super(attribute_name, options, &block) end def show_attribute?(attr_name) # some code end end = simple_form_for @user, builder: PunditFormBuilder do |f| 


PunditFormBuilderは、現在のアプリケーションユーザーがアクセスできるフィールドのみを表示します。 これについては、ACLの章で詳しく説明します。

シリアライザー


次に、より具体的なタスク、つまりhttp json apiの設計を見てみましょう。 最も簡単な方法は次のとおりです。


これらの方法はすべて、単独責任の原則とMVCパターンと矛盾します。 モデルとコントローラーは表示を処理する必要はありません-これはビューの義務です。

2つのソリューションがあります。


 class CommentSerializer < ActiveModel::Serializer attributes :name, :body belongs_to :post end 


つまり プレゼンテーションは、テンプレートとヘルパーだけでなく、シリアライザーなど、データのプレゼンテーションに関係する他のオブジェクトでもあります。

発表者


そのため、発表者の使用というスムーズなアプローチに取り組みました。 レールでは、ヘルパーを補完するものとして使用されます。

gem drapperは混乱を招きました。その開発者はプレゼンターデコレーターと呼ばれていました。 これらのパターンは似ていますが、大きな違いがあります。デコレータはインターフェースを変更しません。 このgemには多くの問題があります(問題のリストを見ることができます)。

プレゼンターを実装するためのシンプルでエレガントで理解可能な方法を見つけました。 以下に、実装について説明します。

 # app/presenters/base_presenter.rb class BasePresenter < Delegator attr_reader :model, :h alias_method :__getobj__, :model def initialize(model, view_context) @model = model @h = view_context end def inspect "#<#{self.class} model: #{model.inspect}>" end end 


プレゼンターは、モデルをラップし、メソッドをモデルに委任するオブジェクトです。 オブジェクトは、別のデコレータであっても、モデルになることができます。 基本クラスDelegatorは標準ライブラリに含まれています。

モデルに加えて、プレゼンターにはview_contextが含まれています。これは便宜上「h」と呼ばれます。
これは、ヘルパーとビューで自己利用可能です。 したがって、すべてのヘルパーをプレゼンターで使用できます。

 # app/presenters/task_presenter.rb class TaskPresenter < BasePresenter def to_link h.link_to model.to_s, model end def description h.markdown model.description end #   def users model.users.map { |user| h.present user } end end 


 # app/helpers/application_helper.rb def present(model) return if model.blank? klass = "#{model.class}Presenter".constantize presenter = klass.new(model, self) yield(presenter) if block_given? presenter end 


現在のヘルパーは、現在のオブジェクトをブロックに渡すか、結果として渡します。
ブロックを介した転送は、テンプレートで使用すると便利です。

 # app/views/web/tasks/index.haml - @tasks.each do |task| %tr - present task do |task_presenter| %td= task_presenter.id %td= task_presenter.to_link %td= task_presenter.project 


非常に複雑な表示ロジックがあり、ヘルパーが役に立たない場合は、同様のアプローチを使用できます。 または、表示するオブジェクトがありません。 たとえば、複雑なメニューやイベントスケジュールを表示します。

 class MenuRenderer attr_reader :h def initialize(view_context) @h = view_context end def render some_hard_logic end private def some_hard_logic h.link_to '', '' end end 


このパートでは、プレゼンテーションロジックを整理する方法を検討しました。 次に、コントローラーのロジックを整理する方法を示します。 以下で-モデルについて説明します。 すなわち:フォームオブジェクト、サービス、ACL、クエリオブジェクト、さまざまなリポジトリとの相互作用。

Source: https://habr.com/ru/post/J266761/


All Articles