纠正Rails在另一个控制器中使用另一个控制器操作的方式 - 没有嵌套属性的子表单

时间:2016-04-01 14:03:02

标签: ruby-on-rails ruby rails-activerecord nested-forms

我正在写一个应用程序的一部分,这是“要做的工作”,而工作对他们有记录或评论。

我永远不确定使用嵌套资源视图的正确方法是什么 - 我不认为nested_attributes_for在这里有效 - 我可能错了。

我想要做的是拥有作业渲染的主要#show动作,并在脚下渲染has_many模型的#index(显示当前注释),然后渲染has_many模型的#new,当我提交它通过AJAX发送,并将新评论添加到备注/评论面板的底部。

我不认为nested_attributes_for就在这里,因为我实际上并没有在父控制器上提交任何内容。

class JobsController < ApplicationController
end

class JobNotesController < ApplicationController
end

class Job < ActiveRecord::Base
  has_many :job_notes
end

class JobNote < ActiveRecord::Base
  belongs_to :job
end

在我看来,从JobsController #show中呈现JobNotesController的#new动作更有意义,这样我就可以有效地将某人发送到原始URL:/ jobs / 7 / jobs_notes / 34

在我看来,这种方式会更加干燥。

我可能过于复杂了。

更多信息

看起来我的问题有点模糊 - 问题是什么被认为是最好的方法....

我已经写了许多不同的标题/详细信息类型的变体,我不确定的是这样做的最佳实践并且效率很高。

这种情况下的标题是要完成的工作。它有自己的#edit方法和视图,使屏幕更易于使用。

当您查看工作时,在其详细信息下面,我想包括有关工作的讨论 - 如果您愿意,请注意。

我总是希望显示一个用于添加新笔记的表单,但我还想显示附加到此作业的所有其他笔记。

我可以创建所有相关的逻辑/处理程序,以便仅返回最后N个音符并进一步读取,以便下一个音符在正确的时间顺序位置而不是反转所有音符。这很容易。

它涉及在父控制器/视图的上下文中执行属于另一个控制器的表单。

我正沿着这个助手的路线前进。

def new_job_note
  nc = JobNotesController.new
  nc.params = { :job_id = @job.id }
  nc.dispatch(:new, request)
  return c.response.body
end

然后在我看来我可以打电话

<%= new_job_note %>

这实际上有效 - 只要我为没有周围标签的JobNotesController设置布局。

这感觉不是正确的做法。

3 个答案:

答案 0 :(得分:0)

使用partials可能更好。您可以对job_notes使用partial,将其拥有的作业作为本地(或使用实例变量)传递给它。另一个部分用于创建新笔记,再次传递拥有的工作来渲染它。

您仍然可以使用AJAX更新记事列表。

答案 1 :(得分:0)

我不确定你想做什么,但从技术上讲,你的应用程序总是只呈现一个动作(而不是索引+显示等...)。如果您想渲染块而不重复自己,请在视图中使用render partial: 'xxx'

示例:

  • 您希望在不同的位置呈现作业列表(您称之为索引)。
  • 您创建了一个名为_job_list.html.erb

    的文件
    <% jobs.each do |j| %> ... <% end %>
    
  • 您可以从作业#index和其他类似的操作中调用此块:

    <%=render partial: 'job_list', locals: { jobs: @jobs } %>
    

Secundo,嵌套的资源可以在这样的路线中设置:

#in routes.rb
resources :jobs do
  resources :job_notes
end

job_notes INSIDE作业的节目将指向JobNoteController,但您将有两个参数而不是一个:job_idjob_note_id

然后,您可以在job_id中使用job_note_idJobNoteController两个参数:

  class JobNoteController < ApplicationController
      def show
          @job = Job.find params[:job_id]
          @job.job_notes.find params[:job_note_id]
      end
   end

如果您希望您的控制器更通用(例如,您希望还有一个指向/ job_notes /:id的路径,而不是确定特定作业的范围),您可以改进这段代码:

  class JobNoteController < ApplicationController
      def show
          @scope = params[:job_id] ? Job.find(params[:job_id]).job_notes : JobNote
          @scope.find params[:job_note_id]
      end
   end

这种情况下,您将根据上下文(您想要的DRY:P)确定查询范围 我不确定我是否已经回答了你想要的东西,随便解释你想做什么。

此致

亚辛。

答案 2 :(得分:0)

嵌套和REST

在REST中描述嵌套资源非常简单:

users/1/pets

直接告诉我们关于资源的信息。但是,在嵌套时(或之前)应该记住一些事情。

  1. 除非上下文至关重要,否则不要嵌套!
  2. 不要嵌套多个级别!
  3. 尽可能使用浅嵌套(恕我直言)。
  4. 所以在你的情况下你会想要使用:

    resources :jobs, shallow: true do 
      resources :jobs_notes
    end
    

    哪会给你

     GET|POST            /jobs/:job_id/jobs_notes
     GET|PATCH|DELETE    /jobs_notes/:id # no context required!
    

    浅嵌套是如此甜蜜的原因是,它允许您执行redirect_to(@child)而不是redirect_to([@parent, @child]),并且当您拥有可以具有多个父类型的资源时,它会使其更清晰。

    新的怎么样?

    /new路由是简单资源的rails约定。在其他情况下,从父项show甚至index操作创建资源可能更有意义。如果你没有采取新的行动,Rails警察就不会来。

    nested_attributes_for

    当您想要同时创建父记录和子记录时,最常使用

    nested_attributes_for

    但是,如果要使用单个请求创建/编辑多个子记录,它也很有用。假设您有类todo列表应用程序:

    class List < ActiveRecord::Base
      has_many :items
      accepts_nested_attributes_for :list, allow_destroy: true
    end
    
    class Item < ActiveRecord::Base
      belongs_to :list
    end
    

    然后,您可以使用以下内容创建多个Item记录

    POST /lists, { list: { items_attributes: [{ desc: 'Feed cat' }] } }
    

    或者使用以下命令同时创建更新和删除:

    PATCH /lists/1, { list: {
       items_attributes: [
          { desc: 'Feed cat' }, 
          {id: '2', desc: 'new desc'}, 
          {id: '3', _destroy: '1'}
       ]
    }}
    

    当然,如果你创建一个像SPA这样的AJAX繁重的应用程序,最好将它作为单独的原子操作来执行,因为嵌套的params往往会使事情变得复杂和脆弱,因为有很多事情一次发生。

    重用组件。

    这里你真正想要的是不重复使用控制器动作,而是重复使用视图的一部分,例如表单。那是partials进来的地方。

    # things/_form.html.erb
    <%= form_for(thing) %>
      # ...
    <% end %>
    
    # parents/show.html.erb
    render_partial 'things/form', thing: @parent.thing.new
    

    要在控制器之间共享诸如回调或操作之类的代码,您可以使用mixins作为模块或垂直继承的形式。请注意,这并不意味着您可以/应该在单个请求周期中使用多个控制器操作!相反,你可以这样做,以避免重复相同的方法。

    class TaggableController < ApplicationController
      before_action :set_resource
      def create
        @tag = @resource.tag.create
        # ...
      end
    end
    
    class Posts::TagsController < TaggableController
      private
        def set_resource
          @resource = Post.find(params[:tag_id])
        end
    end
    
    class Images::TagsController < TaggableController
      private
        def set_resource
          @resource = Image.find(params[:image_id])
        end
    end
    
    # or use an ActiveSupport::Concern.
    module Taggable
      def self.included(base)
        base.class_eval do
          before_action :set_resource
        end
      end
    
      def create
        @tag = @resource.tag.create
        # ...
      end
    end
    
    class Posts::TagsController < ApplicationController
      include Taggable
    
      private
        def set_resource
          @resource = Post.find(params[:tag_id])
        end
    end
    
    class Images::TagsController < ApplicationController
      include Taggable
    
      private
        def set_resource
          @resource = Image.find(params[:image_id])
        end
    end
    

    你甚至可以将两者结合起来。