ruby-on-rails - 服务调用未更新 Active Record 值以增加连接模型
问题描述
我有一项服务会在条带结帐完成时触发。我希望将一张带有唯一编号的票添加到用户的库存中。
请注意 object.client_reference_id 和元数据来自 Stripe webhook 并且都被确认返回正确的数据。
根据该票的比赛的 max_tickets 值减去刚刚创建的票,创建票并给出票号。
# services/ticket_service.rb
class TicketService
def self.call(event)
new(event).call
end
attr_reader :event
def initialize(event)
@event = event
end
def call
user = User.find_by(id: object.client_reference_id)
return unless user
contest = Contest.find_by(id: object.metadata.order_item.to_i)
# Problem code below
user.tickets.create!(purchased: true, user_id: user.id,
contest_id: contest.id, ticket_number: contest.max_tickets -= 1 )
OrderMailer.with(contest: contest).new_order_email.deliver_later
end
private
def object
@object ||= event.data.object
end
end
但是,每次以这种方式购买门票时,竞赛.max_tickets -= 1部分似乎都无法按预期工作。无论我尝试购买这张票多少次,我的库存中都有同一张票,票号为:49 / 50 。
奇怪的是,当我在 rails 控制台中以相同的方式创建票证时,票证编号会按预期递减 49、47、48 等。
irb(main):024:0> user.tickets.create!(purchased: true, user_id: user.id, contest_id: contest.id, ticket_number: contest.max_tickets -= 1 )
(0.4ms) BEGIN
Contest Load (0.5ms) SELECT "contests".* FROM "contests" WHERE "contests"."deleted_at" IS NULL AND "contests"."id" = $1 LIMIT $2 [["id", 17], ["LIMIT", 1]]
Ticket Create (0.4ms) INSERT INTO "tickets" ("contest_id", "user_id", "created_at", "updated_at", "purchased", "ticket_number") VALUES ($1, $2, $3, $4, $5, $6) RETURNING "id" [["contest_id", 17], ["user_id", 63], ["created_at", "2020-07-25 02:18:33.819410"], ["updated_at", "2020-07-25 02:18:33.819410"], ["purchased", true], ["ticket_number", 43]]
(1.7ms) COMMIT
=> #<Ticket id: 599, contest_id: 17, user_id: 63, ticket_price: nil, created_at: "2020-07-25 02:18:33", updated_at: "2020-07-25 02:18:33", purchased: true, ticket_number: 43>
解决方案
调用-=
不会生成update
查询以保留您对列所做的任何更改,并且不清楚为什么这在您的数据库示例中似乎有效,因为输出中没有update
查询contests
表明它可能有效。
您需要在修改其字段后保存比赛,使用contest.save!
, 并且您希望在事务中执行此操作以确保两个表 (contests
和tickets
) 都已更新,并使用锁定防止多个并发购票创建竞争条件:
您想要的代码如下所示:
contest = Contest.find_by(id: object.metadata.order_item.to_i)
contest.with_lock do
# TODO: verify max_tickets is still > 0
user.tickets.create!(purchased: true, user_id: user.id,
contest_id: contest.id, ticket_number: contest.max_tickets -= 1)
contest.save!
end
请参阅 Rails 的悲观锁定with_lock
。
只需查看您的代码的一些注释:
- 你应该有一个唯一的索引
tickets
,对列[contest_id, ticket_number]
。同一场比赛的 33 号门票永远不可能被送出两次。 - 您应该在锁内验证
contest.max_tickets
仍然大于 0。您现在没有任何明显的检查,除非您依赖Ticket
模型内部的验证。如果没有上述锁定/索引,此验证是不够的。
推荐阅读
- java - 获取 URL 参数并在 HTML 页面中打印
- python - QFileDialog 总是在主窗口后面打开
- python-3.x - 如何迭代由元组列表组成的字典?
- module - Joomla 3.9.13 中的随机图像模块有问题吗?
- php - 关于设计课程系统。我应该何时检查到期或未到期的作业?我应该把这个逻辑放在哪里?
- angular - primeNG 表上的可编辑行问题
- reactjs - reactjs中formik表单初始值如何设置前置字段数据
- google-cloud-platform - 云任务未按时运行
- arrays - 如何最大化给定数组中有效索引的数量?
- sql-server - 如何使用 SQL/T-SQL 正确地对扩展字符集进行 Base64 编码