【问题标题】:Who's responsible for creating associated has_many model in Rails?谁负责在 Rails 中创建关联的 has_many 模型?
【发布时间】:2013-05-03 05:17:37
【问题描述】:

我有负责创建 Foobar 的“快速添加”表单。一旦提交/保存,您将返回到表单以添加下一个 Foobar。我现在有多态 Tasks,我想在创建 Foobar 时将其添加到它。在这种情况下,任务不需要任何详细信息,因此我选择在表单上设置一个复选框,上面写着“执行此任务”。如果在提交表单时选中,我希望创建一个任务并将其关联到 Foobar。

这样做并不难,但我正在努力寻找似乎是“正确”的做法。

选项 1: Foobar 接受_nested_attributes_for 任务,因此我可以创建嵌套表单,但实际上没有什么可嵌套的。该复选框不代表任务上的有效字段,只是我希望创建任务。我可以使用@foobar.build_task 并在任务的表单中放置一些隐藏字段,但如果未选中复选框,我必须使用 JS voodoo 来防止字段提交。这看起来很肮脏和错误。

选项 2: 我可以在 FoobarController#create 中放置一些逻辑,用于查找复选框并在保存之前将任务构建到 Foobar 上。这里的问题是 Task 是多态的,也可能与其他事物相关联。甚至可能有多种“类型”的任务与正在创建的单个 Foobar 相关联。我认为这个解决方案比选项 1 更好,但不是很干燥。控制器中的任何此类逻辑最终都会在控制器中被复制以用于其他任务。

选项 3: 在 Foobar 上有一个 before_save,它会查找名为“create_task”的伪字段的存在,然后构建任务。这会将副本从控制器中移出并放入可任务模型中,但它并不比每个模型中重复的“has_many :tasks”行更多。尽管如此,寻找这样一个领域并采取行动似乎并不是模型的工作。

所以....我真的很感激一些想法。

更新 #1:一些额外的信息...

一个任务也有一个创建者和一个受让人,两者都是系统中的用户。应该根据 current_user 方法自动分配创建者,这当然在 View 和 Controller 中可用,但在模型中不可用。我认为立即排除了答案纯粹基于模型的可能性(例如选项 3)。我认为它还暗示答案不是基于视图的(例如选项 1),因为应该将用户设置在服务器端,使其无法被篡改。所以也许答案就在控制器的某个地方?也许某种帮助方法来包装要在每个控制器中复制的逻辑来处理可任务的事情?

更新 #2:我目前的倾向...

我花了一些时间与一位受人尊敬的开发人员朋友交谈,并进一步确信答案最接近选项 2。视图和模型似乎都是错误的。控制器是有道理的,但主要问题是重复代码的可能性。我认为答案将是找到最好的方法来分解控制器代码,该控制器代码处理任务(或其他多态事物,如 cmets、文件上传等)与控制器负责的对象的附件。当我有一个我满意的解决方案时,我会尝试在这里发布。谢谢大家的意见!

【问题讨论】:

    标签: ruby-on-rails model-view-controller polymorphic-associations


    【解决方案1】:

    我会选择 选项 1 的衍生产品。不过,实际上我可能会使用两种解决方案。

    解决方案 1

    这个更简单,但没有那么可扩展。

    使用现有的accepts_nested_attributes_for :tasks,但请确保您有此选项:

    accepts_nested_attributes_for :tasks, :allow_destory => true
    

    然后在您的表单中使用(假设task_collection 是预构建任务的集合):

    <%= form_for(@record) do |form| %>
       ...
       <%= form.fields_for :tasks, task_collection do |task_fields| %>
         <%= task_fields.check_box(:_destroy,{},0,1) %> Do This Task
       <% end %>
       ...
    <% end %>
    

    基本上这将采用Task关联对象,如果选中该框,则将其保留,否则将其标记为销毁。这应该防止任何所谓的“JS voodoo”,因为任何此类逻辑都在模型中处理。

    解决方案 2

    其他选项,但更复杂的选项,我可能会使用:

    def tasks_attributes=(tasks_attributes)
    

    在每个适用的模型(或包含的模块)中执行此操作,然后在从表单提交时处理所有任务属性。

    它涉及处理模型中的task_attributes。这意味着你必须以某种方式和其他一些微妙的细节来构建你的表单。但是使用这种方法,您不必构建Task 对象然后销毁它,您只需处理提交数据即可创建tasks

    这样做的细节非常深入,并不是我真正想要深入探讨的。但你似乎足智多谋,只要知道它的存在就可以帮助你。

    更多信息请参见fields_for documentationaccepts_attributes_for implementation

    【讨论】:

    • 我不知道 :allow_destroy 选项或相关的 :_destroy 字段,谢谢!
    • 糟糕 - 通过“Enter”键提前提交了之前的评论。不过,我更新了我的问题,以包含一些关于为什么我怀疑答案不在视图中的想法。我部分倾向于同意您的解决方案 1,但它似乎对视图承担了太多责任。我熟悉tasks_attributes= 的想法,但我认为它仍然存在我在更新问题和回复@user1438190 时提到的症状。如前所述,我知道几种方法可以做到这一点,但实际上只是在寻找最干净的方法。谢谢!
    • @Dan,关于选项 1,我使用了 Ryan Bates 的 nested_form gem。
    • 谢谢,@Josh。正如我在更新中提到的,我仍然怀疑答案在控制器中,而不是视图(该 gem 可能对它有用)或模型。
    • 你描述它的方式让我想起了为错误问题寻找解决方案的一点点。无论您要将current_user 注入到控制器级别的某些东西中。真正的问题是如何将这些信息传递给当前模型。如果问题真的超出了我对问题的理解程度,无法提供帮助,对不起。
    【解决方案2】:

    在这种情况下,我会这样:

    class Foobar
      has_many :tasks
      attr_accessor :add_task
      after_create :create_task, if: add_task == true
    
      private
      def create_task
        tasks.create(...)
      end
    end
    

    【讨论】:

    • 感谢您的回复!这基本上是选项 3 的一个稍微干净的版本。我仍然无法摆脱创建任务不应该与 foobar 相结合的感觉。我没有提到的一件事加强了我的担忧,那就是任务跟踪当前用户作为它的创建者。 current_user 方法可用于控制器/视图,但在模型中不(也不应该)可用。这似乎是一种“代码气味”,表明在模型中解决问题的方法存在问题。我仍然觉得答案在模型上游的某个地方。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-19
    • 1970-01-01
    • 1970-01-01
    • 2017-03-23
    相关资源
    最近更新 更多