0

我有一个名为 Person 的模型,用户为其选择了五个个性特征。但是,他们选择它们的顺序很重要(他们选择描述性最强到描述性最差)。

我知道如何创建一个带有毒药的连接表并以这种方式进行排序。我也在使用acts_as_list。

但我似乎找不到任何帮助,如何为我的应用程序的用户创建一种设置特征顺序的方法。那就是我想在 HTML 中说五个选择框,让他们选择每一个,并使用 jQuery UI Sortable 之类的东西,让他们可以随意移动它们。

这是我的模型的基本概念(为了获得概念而进行了简化)。

class Person < ActiveRecord::Base
  has_many :personalizations
  has_many :traits, :through => :personalizations, :order => 'personalizations.position'
end

class Personalization < ActiveRecord::Base
  belongs_to :person
  belongs_to :trait
end

class Trait  < ActiveRecord::Base
  has_many :persons
  has_many :persons, :through => :personalizations
end

我只是不知道如何在我的视图/控制器中进行定位,以便在提交表单时知道哪个特征在列表中的哪个位置。

4

1 回答 1

1

经过大量研究后,我将发布我的结果以帮助其他人封装他们需要通过多对多关系将记录列表附加到模型,并且能够对视图中的选择进行排序。

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
于 2013-01-16T05:04:35.773 回答