2013-01-17 42 views
9

रेल में एक काउंटर के साथ एक मॉडल के लिए एक परमाणु सम्मिलित/अपडेट लागू करने के लिए सबसे अच्छा तरीका क्या है? ,परमाणु डालने या वेतन वृद्धि

url : string 
count : integer 

डालने पर अगर वहाँ वर्तमान में एक मिलान यूआरएल के साथ एक रिकॉर्ड नहीं है, एक नया रिकार्ड बनाया जाना चाहिए: समस्या मैं हल करने के लिए कोशिश कर रहा हूँ के लिए एक अच्छा सादृश्य एक "की तरह" दो क्षेत्रों के साथ काउंटर है गिनती 1 के साथ; अन्यथा मौजूदा रिकॉर्ड का count फ़ील्ड बढ़ाया जाना चाहिए।

शुरू में मैं की तरह कोड की कोशिश की है:

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

लेकिन आश्चर्य है, जिसके परिणामस्वरूप एसक्यूएल पता चलता है कि SELECTUPDATE लेन-देन के बाहर होता है:

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 

वहाँ से निपटने के लिए एक रेल मुहावरा है यह, या मुझे एसक्यूएल स्तर पर इसे लागू करने की आवश्यकता है (और इस प्रकार कुछ पोर्टेबिलिटी खोना)?

+0

4 साल बाद भी, एक स्थिर/सामुदायिक-समर्थित उत्तर की तलाश में है। –

उत्तर

5

मुझे किसी भी 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;"); 
+1

मैं आपके समाधान की विविधता का उपयोग करके समाप्त हुआ (quoted_url दो बार उद्धृत किया गया है)। ActiveRecord :: RecordNotUnique को पकड़ने से यह लगभग 5x तेज हो गया। लगता है कि रेल कुछ प्रदान करना चाहिए। –

0

रेल, Transactions का समर्थन करता है कि यदि आप जो खोज रहे हैं, इसलिए:

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

दुर्भाग्यवश कि स्वयं ही काम नहीं करेगा। यदि आपके पास यूआरएल कॉलम पर कोई अनन्य अनुक्रमणिका नहीं है, तो डुप्लिकेट आवेषण प्राप्त करना संभव है। यदि आपके पास एक अद्वितीय इंडेक्स है, तो आपको ActiveRecord :: RecordNotUnique मिलेगा।मैंने लेनदेन ब्लॉक में "नींद 5" जोड़कर दोनों परिदृश्यों का परीक्षण किया। मुझे लगता है कि इस काम को इस तरह के बनाने के लिए सर्जिकल के डीबी अलगाव स्तर की आवश्यकता होगी। –

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 

रेल 'increment_counter विधि परमाणु है, और निम्नलिखित की तरह SQL करने के लिए अनुवाद:

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

एक अपवाद पकड़ने सामान्य व्यापार तर्क लागू करने के लिए नहीं बल्कि बदसूरत लगता है, इसलिए मैं बेहतरी के सुझावों की सराहना करेंगे।

5

आप permissive lock उपयोग कर सकते हैं,

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

0

उपयोग के इस मामले और दूसरों के लिए, मुझे लगता है कि update_allapproach साफ है।

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 स्थिति को नियंत्रित करते हैं और क्या अपडेट किया जाना है। बहुत निफ्टी