2013-01-17 14 views
9

Jaki jest najlepszy sposób na wprowadzenie wkładki atomowej/aktualizacji dla modelu z licznikiem w Railsach? Dobrą analogią do problemu próbuję rozwiązać jest „podobny” licznik z dwóch pól:Wkładka atomowa lub inkrementacja w ActiveRecord/Rails

url : string 
count : integer 

Upon wkładki, jeśli nie jest obecnie rekord z pasującym url, nowy rekord należy stworzyć z policzeniem 1; w przeciwnym razie pole rekordu count powinno zostać zwiększone.

Początkowo próbowałem kod jak poniżej:

Like.find_or_create_by_url("http://example.com").increment!(:count) 

Ale nic dziwnego, uzyskany SQL pokazuje, że SELECT dzieje poza transakcją 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 

istnieje idiom Szyny do czynienia z to, czy też muszę to wdrożyć na poziomie SQL (a tym samym stracić trochę przenośności)?

+0

4 lata później wciąż szukam spójnej/wspieranej przez społeczność odpowiedzi. –

Odpowiedz

5

Nie jestem świadomy żadnej metody ActiveRecord, która implikuje przyrosty atomowe w jednym zapytaniu. Twoja własna odpowiedź jest jak najdalej.

Twój problem może nie być rozwiązany przy użyciu ActiveRecord. Pamiętaj: ActiveRecord jest po prostu maperem, który upraszcza niektóre rzeczy, jednocześnie komplikując inne. Niektóre problemy są po prostu rozwiązywane przez zwykłe zapytania SQL do bazy danych. Stracisz trochę przenośności. Poniższy przykład będzie działał w MySQL, ale o ile wiem, nie na SQLlite i innych.

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

Skończyło się na tym, że użyłem odmiany twojego rozwiązania (quoted_url jest cytowane dwukrotnie). W sumie było to około 5 razy szybsze niż przechwytywanie ActiveRecord :: RecordNotUnique. Wygląda na to, że Rails powinien je dostarczyć. –

0

Rails obsługuje Transactions, jeśli to, co szukasz, więc:

Like.transaction do 
    Like.find_or_create_by_url("http://example.com").increment!(:count) 
end 
+3

Niestety to samo w sobie nie zadziała. Jeśli nie masz unikalnego indeksu w kolumnie adresu URL, możesz uzyskać duplikaty wstawek. Jeśli masz unikalny indeks, otrzymasz ActiveRecord :: RecordNotUnique.Właśnie przetestowałem oba scenariusze, dodając "uśpienie 5" w bloku transakcji. Myślę, że poziom izolacji DB SERIALIZABLE byłby wymagany, aby działało to tak jak jest. –

7

Oto co mam wymyślić tak daleko. Zakłada to unikalny indeks na kolumnie url. increment_counter metoda

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 

szyn jest atomowy, i przekłada do SQL jak poniżej:

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

Catching wyjątek wdrożyć normalnej logiki biznesowej wydaje się raczej brzydki, więc byłbym wdzięczny sugestie dotyczące poprawy.

5

Można użyć permissive lock,

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

0

W tym przypadku użycia i innych, myślę update_allapproach jest najczystszy.

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 

Niezupełnie Twój przykład, właśnie wklejony z połączonej strony. Ale kontrolujesz warunek where i co jest do aktualizacji. Bardzo fajne

Powiązane problemy