在Rails中使用计数器实现原型插入/更新的最佳方法是什么?我试图解决的问题的一个很好的类比是一个“喜欢”的计数器有两个字段:
url : string
count : integer
插入后,如果当前没有匹配网址的记录,则应创建一个计数为1的新记录;否则现有记录的count
字段应该递增。
最初我尝试了以下代码:
Like.find_or_create_by_url("http://example.com").increment!(:count)
但不出所料,结果SQL显示SELECT
发生在UPDATE
交易之外:
Like Load (0.4ms) SELECT `likes`.* FROM `likes` WHERE `likes`.`url` = 'http://example.com' LIMIT 1
(0.1ms) BEGIN
(0.2ms) UPDATE `likes` SET `count` = 4, `updated_at` = '2013-01-17 19:41:22' WHERE `likes`.`id` = 2
(1.6ms) COMMIT
是否存在用于处理此问题的Rails习惯用法,或者我是否需要在SQL级别实现此功能(从而失去一些可移植性)?
答案 0 :(得分:8)
您可以使用permissive lock,
like = Like.find_or_create_by_url("http://example.com")
like.with_lock do
like.increment!(:count)
end
答案 1 :(得分:7)
到目前为止,我已经提出了这个问题。这假定url
列上有唯一索引。
begin
Like.create(url: url, count: 1)
puts "inserted"
rescue ActiveRecord::RecordNotUnique
id = Like.where("url = ?", url).first.id
Like.increment_counter(:count, id)
puts "incremented"
end
Rails'increment_counter
方法是原子的,并转换为SQL,如下所示:
UPDATE 'likes' SET 'count' = COALESCE('count', 0) + 1 WHERE 'likes'.'id' = 1
捕获异常以实现正常的业务逻辑似乎相当丑陋,所以我希望提出改进建议。
答案 2 :(得分:6)
我不知道任何在单个查询中实现原子增量的ActiveRecord方法。你自己的答案就是你能得到的。
因此,使用ActiveRecord可能无法解决您的问题。请记住:ActiveRecord只是一个简化某些事情的映射器,同时使其他事情变得复杂。一些问题只能由纯SQL查询解决到数据库。你会失去一些便携性。以下示例适用于MySQL,但据我所知,不适用于SQLlite和其他。
quoted_url = ActiveRecord::Base.connection.quote(@your_url_here)
::ActiveRecord::Base.connection.execute("INSERT INTO likes SET `count` = 1, url = \"#{quoted_url}\" ON DUPLICATE KEY UPDATE count = count+1;");
答案 3 :(得分:0)
Rails支持Transactions,如果这是您正在寻找的内容,那么:
Like.transaction do
Like.find_or_create_by_url("http://example.com").increment!(:count)
end
答案 4 :(得分:0)
对于此用例和其他用例,我认为update_all
approach最干净。
num_updated =
ModelClass.where(:id => my_active_record_model.id,
:service_status => "queued").
update_all(:service_status => "in_progress")
if num_updated > 0
# we updated
else
# something else updated in the interim
end
不完全是您的示例,只是从链接页面粘贴。但是您可以控制where
条件以及要更新的内容。很漂亮
答案 5 :(得分:0)
My favorite solution thus far:
Like.increment_counter(
:count,
Like.where(url: url).first_or_create!.id
)