我正试图通过它的priority:integer
列来管理ActiveRecord模型。我想用after_update
和after_create
挂钩来管理行,以保持它们的顺序整洁。
我有两个需求:
-
获取项目的当前列表,并更新其优先级属性以遵循严格的顺序。示例:具有所述优先级列的三个项目的列表。
[a.priority = 4, b.priority = 66, c.priority = 92]
成为
[a.priority = 1, b.priority = 2, c.priority = 3]
-
更新所有行的优先级,以反映在列表的中间部分添加了新行。
[a.priority = 1, b.priority = 2, c.priority = 3, d.priority = 4]
添加
e.priority = 2
创建一个新的列表[a.priority = 1, e.priority = 2, b.priority = 3, c.priority = 4, d.priority = 5]
github回购:https://github.com/digitalcake/priority_manager
对于第一种情况,您可以执行类似的操作
Model.order("priority ASC").each_with_index {|m,i|
m.update_attribute(:priority, i+1) }
第二个
Model.where("priority >= ?", new_priority).each {|m|
m.update_attribute(:priority, m + 1) }
也就是说,如果你只对排序感兴趣,而不是对列表中的绝对位置感兴趣,那么如果你不使用整数来存储优先级,而是使用浮点值,那么效率会更高。通过在要插入行的对象的优先级之间指定it值来插入行。IE在b和c之间插入a,优先级分别为pb与pc为其分配pa=(pc+pb)/2
这样,整体顺序保持不变,但不需要每次插入新行时都以更高的优先级触摸并重新保存每个对象。
我刚刚在我正在构建的一个应用程序中处理了完全相同的场景。接受答案中的解决方案不起作用,因为它将递归地使用回调来调用您试图更新的对象(在update_attributes内)。此外,我们还需要跳过查询中self对象的id。
以下是我最后的做法,它似乎对所有情况都有效。
after_commit :order_priorities, :if => :persisted?
after_destroy :handle_priorities_when_destroyed
def order_priorities
correct_priority = MyModel.where('id != ? AND priority < ?',id,priority).count + 1
MyModel.where.not(id:id).order(:priority).each_with_index {|x,i|
if x.priority < priority
x.update_column(:priority, i+1)
else
x.update_column(:priority, i+2)
end
}
self.update_column(:priority,correct_priority)
end
def handle_priorities_when_destroyed
MyModel.where.not(id:id).order(:priority).each_with_index {|x,i|
x.update_column(:priority, i+1)
}
end
这里我使用after_commit回调,这样我就可以为我在方法中定义的self设置correct_priority。其他回调将不起作用,因为该值将在提交期间被覆盖。
使用update_column,以便在我不需要回调时跳过它们。
逻辑是不言自明的。