【问题标题】:Using an index as an ActiveRecord attribute使用索引作为 ActiveRecord 属性
【发布时间】:2014-06-29 17:35:26
【问题描述】:

我正在尝试构建一个错误跟踪器,其中每个用户都有带有连续票号的票:

用户 1:

  • 票#1
  • 票#2
  • 票#3

用户 2:

  • 票#1
  • 票#2

默认情况下,Rails 给每条记录一个唯一的 id,我需要另一个属性,ticket_number,填充在create 上并取决于用户的票数。

为此我尝试使用 Rails,使用 before_create 钩子执行 user.tickets.count + 1,但这似乎不太安全,因为例如异步工作人员可以同时创建多个票证。

我可以利用数据库索引在数据库级别处理这个票号,就像它发生在 id 上一样,并将这个票号作为属性检索吗?

【问题讨论】:

    标签: mysql sql ruby-on-rails ruby activerecord


    【解决方案1】:

    我认为最安全的方法是

    • 为您的票添加一个额外的属性ticket_number
    • 在数据库层为复合索引user_id, ticket_number添加唯一约束
    • 为您的票证上的ticket_number 范围内的user_id 添加一个uniqueness 验证器以匹配数据库层
    • 添加一个处理程序来构造ticket_number 并解决模型内部的竞争条件。

    通过此设置,您可以确定

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

    你需要一个

    before_create :set_ticket_number 将计算正确的票号(将分配给在比赛条件下获胜的第一条记录)

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

    【讨论】:

      【解决方案2】:

      因此,您可能希望在 belongs_to 上签出 Rails 的 counter_cache 选项,自动执行该行为:

      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 将提供更灵活的解决方案。

      至于竞争条件,我并不肯定,但我认为这一切都是在表被锁定时在同一个事务中完成的。有待验证。

      注意:根据您的各种验证和业务逻辑,仅在数据库级别处理此问题可能会变得非常棘手。

      IMO:如果该计数在您的应用程序中很重要,也许值得在您需要时查询它 (SELECT COUNT(...))

      【讨论】:

      • 您需要的不仅仅是 counter_cache 工作,还需要在数据库中添加一个额外的列。
      • 确实如此。添加了文档链接和对此的评论
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-09-28
      • 2011-12-17
      • 2012-08-02
      • 1970-01-01
      • 2021-08-13
      • 1970-01-01
      • 2012-02-21
      相关资源
      最近更新 更多