在具有唯一验证的记录之间交换值

时间:2016-11-28 20:35:02

标签: ruby-on-rails ruby-on-rails-4 activerecord

方案

我有Media而不是有很多音轨,我想在媒体中存储音轨序列,但对于特定媒体,序列必须是唯一的。因此,例如,Media对象可以具有10个轨道,每个轨道具有1,2,... 10的序列

class Medium < ActiveRecord::Base
  has_many   :tracks, inverse_of: :medium, dependent: :destroy
end

class Track < ActiveRecord::Base
  belongs_to :medium

  validates_uniqueness_of :sequence, scope: :medium, message: 'sequence is not unique for the medium'
end

然而,用户可以改变音轨的顺序,例如他们将第二个音轨移动到第8个位置 - 这将导致序列3 - 8的音轨序列减1(分别变为2 - 7)并且序列2的原始轨道成为轨道8.

问题

问题是我不能简单地更改值并保存轨道,因为当然验证失败了。

我能想到的唯一解决方案是:

  1. 使用临时但唯一的序列号设置第一个受影响的轨道并保存。
  2. 减少其他曲目的序列号并保存。
  3. 使用正确的序列号重新分配第一首曲目并保存。
  4. 然而,这似乎效率很低,只适合这种情况。例如,我可以从另一个方向移动轨道,从位置8到位置2。

    理想情况下,我希望允许用户更改曲目上的序列并修改其他数据,并且只有一旦满意,才会提交,这将导致调用将调用media.save的操作。

    在Rails中是否有更好的实践来处理这种情况,或者它看起来是如此复杂。将此作为特殊情况处理并将每个更改处理为原子序列是更好的做法 - 意味着在用户更改轨道序列时立即命中服务器(其他轨道属性更改由被调用的父保存处理,即。{{ 1}})

2 个答案:

答案 0 :(得分:2)

您可以使用act as list gem来解决排序和重新排序问题。

<强>的Gemfile

gem 'acts_as_list'

<强> medium.rb

class Medium < ActiveRecord::Base
  has_many :tracks,
           -> { order(sequence: :asc) },
           inverse_of: :medium,
           dependent: :destroy
end

<强> track.rb

class Track < ActiveRecord::Base
  belongs_to :medium
  acts_as_list column: :sequence, scope: :medium
end

答案 1 :(得分:1)

您可以在父媒体记录上使用transaction并执行所有更改,包括一个块中的序列重新编号。