将索引用作ActiveRecord属性

3
我正在尝试建模一个缺陷跟踪器,其中每个用户都有具有连续票号的票据:
用户1: - 票号#1 - 票号#2 - 票号#3
用户2: - 票号#1 - 票号#2 - etc.
默认情况下,Rails会为每个记录分配唯一的ID。我需要另一个属性ticket_number,在创建时根据用户的票数进行填充。
我尝试使用Rails来实现这一点,使用before_create钩子来执行user.tickets.count + 1,但这似乎不太安全,因为例如异步工作者可能同时创建多个票。
我能否利用数据库索引来处理此ticket_number,就像对id一样,在数据库级别检索此ticket_number作为属性?
2个回答

1

我认为最安全的方法是:

  • 在你的票据中添加一个额外的属性 ticket_number
  • 在数据库层为复合索引 user_id, ticket_number 添加唯一约束条件
  • 为票据添加 唯一性 验证器,作用域为 user_id,匹配数据库层
  • 添加一个处理程序来构建 ticket_number 并解决模型内的竞争条件。

通过这种设置,你可以确保:

  • rails 将尝试不允许无效数据,
  • 如果它们通过了 rails,数据库将不会接受两个不一致的 user_id、ticket_number 组合,
  • rails 将优雅地处理数据库异常并重新分配 ticket_number

你需要一个

before_create :set_ticket_number,它将计算正确的票号(将分配给赢得竞争条件的第一条记录)

需要一个recalculate_number方法来处理因数据库唯一性约束(竞争条件的失败者)而无法持久化的记录。


0

因此,您可能希望在Rails中检查 counter_cache 选项,该选项在 belongs_to 上自动化了该行为:

class Ticket
  belongs_to :user, counter_cache: true
end

# you need to add `tickets_count` to your `users` table (or `name_of_counter_count`)
class User
  has_many :tickets
end

你可以从任何一个User实例访问#tickets_count。Rails会在创建票据时递增计数,在销毁票据时递减计数。
需要注意的是,它使用ActiveRecord回调,因此如果出于某种原因跳过了回调,则计数器将失去同步(可以使用#reset_counters(:counter_name)重置)。
还有counter_culture gem,提供更灵活的解决方案。
至于竞争条件,我不确定,但我认为这都是在同一事务内完成的,而表被锁定。待验证。
请注意:根据您的各种验证和业务逻辑,仅在数据库级别处理这些可能会变得非常棘手。
在我看来:如果该计数对您的应用程序很关键,在需要时查询它可能是值得的(SELECT COUNT(...))。

你需要更多的东西才能让counter_cache起作用,在数据库中需要一个额外的列。 - Eyeslandic
1
是的,确实如此。我添加了一个链接到文档,并对此进行了评论。 - midu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接