经过大量研究后,我将发布我的结果以帮助其他人封装他们需要通过多对多关系将记录列表附加到模型,并且能够对视图中的选择进行排序。
Ryan Bates 对现有记录进行排序有一个很棒的截屏视频:http ://railscasts.com/episodes/147-sortable-lists-revised
但是在我的情况下,我需要在我的 Person 模型存在之前进行排序。
我可以使用 builder 轻松添加关联字段,或者 simple_form_for 使这更加容易。结果将是 params 包含每个关联字段的属性 trait_ids(因为我的 Person has_many Traits):
#view code (very basic example)
<%= simple_form_for @character do |f| %>
<%= (1..5).each do |i| %>
<%= f.association :traits %>
<% end %>
<% end %>
#yaml debug output
trait_ids:
- ''
- '1'
- ''
- '2'
- ''
- '3'
- ''
- '4'
- ''
- '5'
那么问题是在提交表单时是否会遵守 DOM 中元素的顺序。特别是如果我实现 jQuery UI 可拖动?我发现这个帖子表单中的数据顺序与网络表单中的数据顺序相同吗?我同意这个答案。正如我所怀疑的那样,假设订单将永远保留,风险太大。即使它现在适用于所有浏览器,也可能导致错误。
因此,经过仔细研究,我得出结论 jQuery 是一个很好的解决方案。与 rails 中的虚拟属性一起处理自定义输出。经过大量测试后,我放弃了使用acts_as_list 来做我想做的事情。
稍微解释一下这个发布的解决方案。本质上,我缓存了对虚拟属性的更改。然后,如果设置了缓存(进行了更改),我验证他们选择了五个特征。出于我的目的,我保留了无效/空选项,以便如果在返回视图时验证失败,则顺序将保持不变(例如,如果他们跳过了中间选择框)。
然后 after_save 调用将这些更改添加到数据库中。after_save 中的任何错误仍然包含在事务中,因此如果任何部分出错,则不会进行任何更改。因此,最简单的做法是删除所有捐赠并保存新捐赠(这里可能有更好的选择,不确定)。
class Person < ActiveRecord::Base
attr_accessible :name, :ordered_traits
has_many :endowments
has_many :traits, :through => :endowments, :order => "endowments.position"
validate :verify_list_of_traits
after_save :save_endowments
def verify_list_of_traits
return true if @trait_cache.nil?
check_list = @trait_cache.compact
if check_list.nil? or check_list.size != 5
errors.add(:ordered_traits, 'must select five traits')
elsif check_list.uniq{|trait| trait.id}.size != 5
errors.add(:ordered_traits, 'traits must be unique')
end
end
def ordered_traits
list = @trait_cache unless @trait_cache.nil?
list ||= self.traits
#preserve the nil (invalid) values with '-1' placeholders
list.map {|trait| trait.nil?? '-1' : trait.id }.join(",")
end
def ordered_traits=(val)
@trait_cache = ids.split(',').map { |id| Trait.find_by_id(id) }
end
def save_endowments
return if @trait_cache.nil?
self.endowments.each { |t| t.destroy }
i = 1
for new_trait in @trait_cache
self.endowments.create!(:trait => new_trait, :position => i)
i += 1
end
end
然后用简单的形式添加一个隐藏字段
<%= f.hidden :ordered_traits %>
我使用 jQuery 将错误和提示范围移动到我构建的五个选择框的 div 内的正确位置。然后我在表单上有一个提交事件处理程序,并按照它们在 DOM 中的顺序将五个文本框的选择转换为逗号分隔的数字数组,并在隐藏字段上设置值。
为了完整起见,这里是其他类:
class Trait < ActiveRecord::Base
attr_accessible :title
has_many :endowments
has_many :people, :through => :endowments
end
class Endowment < ActiveRecord::Base
attr_accessible :person, :trait, :position
belongs_to :person
belongs_to :trait
end