0

이것은 "실제 프로젝트"문제 중 하나입니다. 좋은 해결책을 찾기 위해 애쓰는 중입니다.Rails 4+ : 여러 개의 연관이있는 단일 리소스, 컨트롤러 구성

나는 여러 개의 다른 리소스와 관련된 모델을 가지고 있으며 관련 리소스 각각에서 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 

더 나은 구성과 제어를 위해 내 컨트롤러 이름을 지정했습니다. 내 행동이 <action>.js.erb 형식을 따라야 그래서 내가 대신 페이지를 업데이트 자바 스크립트를 사용하는 경우,

# 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 

나는 또한 빠른 (Turbolinks 포함) 인터페이스를 만들기 위해 AJAX를 많이 사용하고 있습니다 : 그것은 다음과 같은 경로로 날 리드 페이지 새로 고침. 내가 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 리소스와 약간 다른 상호 작용을하는 서로 다른 리소스마다 반복 코드가 많이 있습니다.

몇 가지 다른 리소스로 조작되는 리소스를 구조화하는 데 도움이되는 표준 형식이나 레일스 기능이 있습니까?

어떻게하면이 중복을 줄일 수 있습니까? 그것은 필자가 기능을 추가하거나 변경할 때마다 3 개 이상의 다른 장소로 가서 변경해야하는 복잡한 시스템으로 직접 연결됩니다.

+0

이것이 바로 발표자, 서비스, 관리자 등 (모두 평범한 오래된 루비 개체)을 사용해야하는 이유입니다. – jvillian

+0

@ jvillian : 발표자, 서비스 개체 및 다른 PORO를 사용합니다. 그러나 컨트롤러와 뷰의 중복을 줄이는 데 어떻게 도움이 될지 모르겠습니다. 문제는 실제로 3 개 이상의 장소가 동일한 코드의 90 %를 사용한다는 것이며, 코드를 말라 버리는 방법을 모르지만 여전히 그 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 

같은 보이는 Tasks::CreateService : 추상화 한 후, 당신의 컨트롤러가, 당신은 어디 지점에 도착하면

# 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 

그런 다음, 당신은 같은 것을 할 수 있어야 동일한 방법을 사용한다면, Milestones::TasksControllerProjects::TasksController과 같은 다른 멋진 것들을 모두 시작할 수 있습니다. ApplicationController 이외의 공통 컨트롤러에서 모든 공유 방법을 상속받습니다. 따라서 TasksController을 사용하지 않는다고 가정 해 봅시다. 그런 다음 컨트롤러가 다음과 같이 표시 될 수 있습니다.

이제 서비스에서 한 곳에서만 변경하면됩니다. 물론 서비스를 사용하지 않으려는 경우 컨트롤러 메서드를 항상 무시할 수 있습니다.

+0

공유 "기본"컨트롤러를 사용하여 다른 연결을 사용하는 경로를 따라했습니다. 여러 조회 방법을 정리할 수 있었지만 각각의 조회 방법을 융통성있게 허용했습니다. –