1

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

  • 学生 has_and_belongs_to_many :styles
  • 样式 has_many :ranks
  • 学生 has_many :ranks, through: :gradings (反之亦然)

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

评分表

所以标题是由Style模型生成的(太极,空手道......),然后它们的排名如下(取自Rank模型),并且“Dojo”和“Date”字段应该属于Grading创建后的模型。

问题:我知道如何构建一个创建一个关联(或一个关联+它的子关联)的表单,但是我如何构建一个同时创建多个关联的表单?

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

  1. 只有被勾选的行才会成为关联
  2. 勾选的行必须填写道场和日期才能成功保存
  3. 如果取消选中一条线,它将破坏任何先前创建的关联

这是我目前为检索正确记录而实施的:

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
4

1 回答 1

1

像这样的复杂表单最好在主要资源createupdate操作中启动的服务对象中处理。这使您可以轻松找到之后发生逻辑的位置。在这种情况下,您似乎可以在GradingsController. 我也更喜欢格式化标记中的大量数据,以使服务对象中的处理更容易。这可以通过传递一个类似"grade[style]"and的名称来完成"grade[rank]"。这会将您的参数格式化为方便的哈希:{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处理边缘情况时需要额外的逻辑,但这将为您提供一个以可维护和可扩展的方式处理此问题的框架。

于 2013-07-15T21:30:41.877 回答