atomic insert or increment in ActiveRecord/Rails

I am not aware of any ActiveRecord method that implemts atomic increments in a single query. Your own answer is a far as you can get.

So your problem may not be solvable using ActiveRecord. Remember: ActiveRecord is just a mapper to simplify some things, while complicating others. Some problems just solvable by plain SQL queries to the database. You will loose some portability. The example below will work in MySQL, but as far as I know, not on SQLlite and others.

 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;");

Here is what I have come up with so far. This assumes a unique index on the url column.

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 method is atomic, and translates to SQL like the following:

UPDATE 'likes' SET 'count' = COALESCE('count', 0) + 1 WHERE 'likes'.'id' = 1

Catching an exception to implement normal business logic seems rather ugly, so I would appreciate suggestions for improvement.


You can use pessimistic locking,

like = Like.find_or_create_by_url("http://example.com")
like.with_lock do
    like.increment!(:count)
end