如何通过一个表单创建多个“has_many through”关联?

时间:2013-07-09 07:51:48

标签: ruby-on-rails ruby-on-rails-3.2

我正在建立一个与武术相关的数据库,目前我已经建立了以下协会:

  • 学生has_and_belongs_to_many:styles
  • Style has_many:rank
  • 学生has_many:排名,通过::评分(反之亦然)

我根据学生的风格生成如下表格:

grading form

所以标题是由Style模型(太极,空手道......)生成的,然后是下面列出的排名(取自Rank模型),以及“Dojo”和“一旦创建,日期“字段应属于Grading模型。

问题:我知道如何构建一个创建一个关联(或一个关联及其子项)的表单,但是如何构建一次创建多个关联的表单?

此外,实现以下内容的干净方法是什么:

  1. 只有勾选的行才会成为关联
  2. 必须填写Dojo和日期以便勾选成功以便成功保存
  3. 如果一行未被攻击,它将销毁任何先前创建的关联
  4. 这是我目前用于检索正确记录的内容:

    class GradingsController < ApplicationController
      before_filter :authenticate_sensei!
    
      def index
        @student = Student.includes(:styles).find(params[:student_id])
        @ranks = Rank.for_student_styles(@student)
        split_ranks_by_style
      end
    
      private
    
      def split_ranks_by_style
        @karate = @ranks.select_style("Karate")
        @tai_chi = @ranks.select_style("Tai Chi")
        @weaponry = @ranks.select_style("Weaponry")
      end
    end
    
    # Rank model
    def self.for_student_styles(student)
      includes(:style).where("styles.id in (?)", student.styles.map(&:id))
    end
    
    def self.select_style(style)
      all.map { |r| r if r.style.name == style }.compact
    end
    

1 个答案:

答案 0 :(得分:1)

这样的复杂表单最好在主要资源的createupdate操作中启动的服务对象中处理。这使您可以轻松找到之后逻辑发生的位置。在这种情况下,您似乎可以在GradingsController中启动服务对象。我也更喜欢格式化标记中的大量数据,以便在服务对象中更容易处理。这可以通过传递"grade[style]""grade[rank]"之类的名称来完成。这将格式化您的params作为方便的哈希:{grade: {style: "karate", rank: "3"}}。该哈希值可以传递给您的服务对象以进行解析。

如果没有真正掌握您的具体要求的全部范围,请将示例表格放在一起:

<%= form_for :grading, url: gradings_path do |f| %>
  <h1><%= @rank.name %></h1>
  <%- @grades.each do |grade| %>
    <div>
      <%= hidden_field_tag "grade[#{grade.id}][id]", grade.id %>
      <%= check_box_tag "grade[#{grade.id}][active]" %>
      ...
      <%= text_field_tag "grade[#{grade.id}][date]" %>
    </div>
  <%- end %>
  <%= submit_tag %>
<%- end %>

使用这样的表格,你可以让你的参数进入控制器,看起来像这样:

"grade"=>{
  "1"=>{"id"=>"1", "active"=>"1", "date"=>"2013-06-21"},
  "3"=>{"id"=>"3", "date"=>"2013-07-01"}
}

很好地格式化我们交给我们的服务对象。让我们的控制器保持良好和干净:

class GradingsController < ApplicationController

  def index
    # ...
  end

  def create
    builder = GradeBuilder.new(current_user, params['grade'])
    if builder.run
      redirect_to gradings_path
    else
      flash[:error] = 'Something went wrong!' # maybe even builder.error_message
      render :action => :index
    end
  end

end

所以现在我们只需要将任何自定义逻辑放入我们的构建器中,我可能建议在/lib目录中创建一个简单的ruby类。它可能看起来像这样:

class GradeBuilder

  attr_reader :data, :user

  def self.initialize(user, params={})
    @user = user
    @data = params.values.select{|param| param['active'].present? }
  end

  def run
    grades = data.each{|entry| build_grade(entry)}
    return false if grades.empty?
  end

  private

  def build_grade(entry)
    grade = Grade.find(entry['id'])
    rank = grade.rankings.create(student_id: user, date: entry['date'])
  end

end

显然需要更多的工作来传递表单中所需的所有特定数据,以及GradeBuilder中用于处理边缘情况的额外逻辑,但这将为您提供一个框架来处理此问题一种可维护和可扩展的方式。