0

これは私が良い解決策を見つけるのに苦労している "現実世界のプロジェクト"の問題の1つです。Rails 4+:複数のアソシエーションを持つ単一のリソース、コントローラ組織

私はいくつかの異なるリソースに関連付けられたtaskモデルを持っています。これらの関連リソースのそれぞれからCRUD能力を許可する必要があります。

たとえば、projectには多くのtasksがあり、projectの文脈でtasksを更新する必要があります。

また、それぞれprojectは、多くがmilestonesであり、各milestoneは、多くともtasksを持つことができます。

taskmilestoneに関連付けられている場合と関連付けられていない場合があります。

class Project < ApplicationRecord 
    has_many :milestones 
    has_many :tasks 
end 

class Milestone < ApplicationRecord 
    belongs_to :project # required 
    has_many :tasks 
end 

class Task < ApplicationRecord 
    belongs_to :project # required 
    belongs_to :milestone # optional 
end 

私は、より良い構成と制御のためにコントローラに名前を付けました。私の行動は、私はページを更新する代わりにするためにJavaScriptを使用<action>.js.erbフォーマットに従ってくださいので、私はまた、インタフェースが高速(Turbolinks付き)にするためにAJAXの多くを使用しています

# routes.rb 
resources :projects do 
    namespace :projects do 
    resources :milestones # app/controllers/projects/milestones_controller.rb 
    resources :tasks # app/controllers/projects/tasks_controller.rb 
    end 
end 

resources :milestones do 
    namespace :milestones do 
    resources :tasks # app/controllers/milestones/tasks_controller.rb 
    end 
end 

:それは、次のようなルートで私をリードページが更新されます。私はtaskフォームのためのダイアログ/ポップアップインターフェイスも使用しているので、ページ全体のリフレッシュだけを行うことはできません。

これは「うまくいく」とはいえ、重複したコードが多い状況に終わります。

# projects/tasks_controller.rb 
def new 
    @project = Project.find(params[:project_id]) 
    @task = @project.tasks.new 
end 

def create 
    @project = Project.find(params[:project_id]) 
    @task = @project.tasks.new(task_params) 

    if @task.save 
    # create.js.erb 
    else 
    render js: "alert('error');" # example... 
    end 
end 

# app/views/projects/tasks/create.js.erb 
$("#tasks_for_<%= dom_id(@project) %>").append("<%=j render(partial: 'projects/tasks/task') %>"); 

# milestones/tasks_controller.rb 
def new 
    @milestone = Milestone.find(params[:milestone_id]) 
    @task = @milestone.tasks.new 
end 

def create 
    @milestone = Milestone.find(params[:milestone_id]) 
    @task = @milestone.tasks.new(task_params) 

    if @task.save 
    # create.js.erb 
    else 
    render js: "alert('error');" # example... 
    end 
end 

# app/views/milestones/tasks/create.js.erb 
$("#tasks_for_<%= dom_id(@milestone) %>").append("<%=j render(partial: 'milestones/tasks/task') %>"); 

実際のシステムのコードでは、ほんの一部のサンプルコードであり、さらに重複しています。お分かりのように、tasksリソースとは少し違った、さまざまなリソースに繰り返しコードがたくさんあります。

他のいくつかのリソースによって操作されるリソースを構造化するのに役立つ標準的なフォーマットまたはRails機能はありますか?

どうすればこの重複を減らすことができますか?それは、機能を追加または変更するたびに3つ以上の異なる場所に移動して変更する必要がある複雑なシステムに直接つながります。

+0

これはプレゼンター、サービス、マネージャーなど(すべての普通の古いルビーオブジェクト)を使用する必要がある理由です。 – jvillian

+0

@ jvillian:私はプレゼンター、サービスオブジェクト、および他のPOROを使用します。しかし、私はそれらが上記のコントローラとビューの重複を減らすのにどのように役立つだろうか分かりません。問題は実際には3つ以上の場所で同じコードの90%を使用していることです。コードをDRYする方法はまだわかりませんが、その10%の違いがまだあります。 –

答えて

0

のは、あなたがこのようなものになりますServiceBase持っていると仮定しましょう:その後、

# services/service_base.rb 
class ServiceBase 

    attr_accessor :args, 
       :controller 

    class << self 

    def call(args={}) 
     new(args).call 
    end 

    end # Class Methods 

    #====================================================================== 
    # Instance Methods 
    #====================================================================== 

    def initialize(args) 
     @args = args 
    end 

    private 

    def params 
     controller.params 
    end 

    def assign_args 
     args.each do |k,v| 
     class_eval do 
      attr_accessor k 
     end 
     send("#{k}=",v) 
     end 
    end 

end 

そして、このようになりますTasks::NewService:あなたのコントローラで、

# services/tasks/new_service.rb 
class Tasks::NewService < ServiceBase 

    def call 
     assign_args 
     @haser = haser_klass.find(haser_id) 
     @haser.tasks.new 
    end 

    private 

    def haser_klass 
     haser_base.constantize 
    end 

    def haser_instance_name 
     haser_base.downcase 
    end 

    def haser_base 
     controller.class.name.split("::")[0].singularize 
    end 

    def haser_id 
     params["#{haser_instance_name}_id".to_sym] 
    end 

end 

その後に、あなたができるはずです

# milestones/tasks_controller.rb 
Milestones::TasksController < ApplicationController 

    def new 
    @task = Tasks::NewService.call(controller: self) 
    end 

end 

# projects/tasks_controller.rb 
Projects::TasksController < ApplicationController 

    def new 
    @task = Tasks::NewService.call(controller: self) 
    end 

end 

もしあなたがあなたは抽象化した後、あなたのコントローラが持っている、ポイントを取得した場合

# milestones/tasks_controller.rb 
Milestones::TasksController < ApplicationController 

    def new 
    @task = Tasks::NewService.call(controller: self) 
    end 

    def create 
    Tasks::CreateService.call(controller: self) 
    end 

end 

# projects/tasks_controller.rb 
Projects::TasksController < ApplicationController 

    def new 
    @task = Tasks::NewService.call(controller: self) 
    end 

    def new 
    Tasks::CreateService.call(controller: self) 
    end 

end 

# services/tasks/create_service.rb 
class Tasks::CreateService < ServiceBase 

    delegate :render, 
      to: :controller 

    def call 
     assign_args 
     @haser = haser_klass.find(haser_id) 
     @task = @haser.tasks.new(task_params) 
     if @task.save 
     # create.js.erb 
     else 
     render js: "alert('error');" # example... 
     end  
    end 

    private 

    def task_params 
     send("#{haser_instance_name}_params") 
    end 

    def milestone_params 
     params.require(:milestone).permit(:foo) 
    end 

end 

次に、あなたのコントローラでは、あなたのような何かを行うことができるはず:のようなものが見えますTasks::CreateServiceMilestones::TasksControllerProjects::TasksControllerの両方が、すべての共有メソッドを持つApplicationController以外の共通コントローラから継承しているような、他の面白いものをやり始めることができます。つまり、TasksControllerを使用していないとします。コントローラは次のようになります。

これで、サービス内の1箇所だけを変更する必要があります。もちろん、サービスを使用したくない場合は、コントローラメソッドをオーバーライドできます。

+0

私は共有 "ベース"コントローラを使用するルートを行った後、異なるアソシエーションがそれを継承します。いくつかのルックアップメソッドをクリーンアップすることができましたが、それぞれのルックアップメソッドを柔軟にすることができました。 –

関連する問題