ActiveRecord कॉलबैक आपके मॉडल के जीवन के विभिन्न चरणों के दौरान कोड चलाने का एक आसान तरीका है।
उदाहरण के लिए, मान लें कि आपके पास एक Q&A साइट है, और आप सभी प्रश्नों के माध्यम से खोज करने में सक्षम होना चाहते हैं। हर बार जब आप किसी प्रश्न में बदलाव करते हैं, तो आप इसे ElasticSearch जैसी किसी चीज़ में इंडेक्स करना चाहेंगे। इंडेक्सिंग में समय लगता है और यह तत्काल नहीं है, इसलिए आप इसे Sidekiq के साथ बैकग्राउंड में करेंगे।
यह after_save कॉलबैक का उपयोग करने का सही समय लगता है! तो अपने मॉडल में, आप कुछ ऐसा लिखेंगे:
class Question < ActiveRecord::Base after_save :index_for_search # ... private def index_for_search QuestionIndexerJob.perform_later(self) end endpre>app/jobs/question_indexer_job.rb class QuestionIndexerJob < ActiveJob::Base queue_as :default def perform(question) # ... index the question ... end endpre>यह बहुत अच्छा काम करता है! या, कम से कम, ऐसा लगता है। जब तक आप बहुत अधिक जॉब कतार में नहीं लगाते और ये त्रुटियां दिखाई नहीं देतीं:
2015-03-10T05:29:02.881Z 52530 TID-oupf889w4 WARN: Error while trying to deserialize arguments: Couldn't find Question with 'id'=3pre>ज़रूर, Sidekiq जॉब को रीट्राई करेगा और यह शायद अगली बार काम करेगा। लेकिन यह अभी भी थोड़ा अजीब है। Sidekiq उस प्रश्न को क्यों नहीं ढूंढ सकता जिसे आपने अभी सहेजा?
प्रोसेस के बीच एक रेस कंडीशन
Rails
after_saveकॉलबैक को रिकॉर्ड सहेजने के तुरंत बाद कॉल करता है। लेकिन उस रिकॉर्ड को अन्य डेटाबेस कनेक्शन द्वारा नहीं देखा जा सकता, जैसे कि Sidekiq उपयोग कर रहा है, जब तक कि डेटाबेस ट्रांजैक्शन कमिट नहीं हो जाता, जो थोड़ी देर बाद होता है। इसका मतलब है कि एक मौका है कि Sidekiq आपके प्रश्न को सहेजने के बाद ढूंढने की कोशिश करेगा, लेकिन इससे पहले कि आप इसे कमिट करें। यह आपके रिकॉर्ड को नहीं ढूंढ सकता, और यह फट जाता है।यह समस्या इतनी आम है कि Sidekiq के पास इसके बारे में एक FAQ एंट्री है। और एक आसान फिक्स है।
after_saveके बजाय:app/models/question.rb class Question < ActiveRecord::Base after_save :index_for_search # ... endpre>
after_commitका उपयोग करें:app/models/question.rb class Question < ActiveRecord::Base after_commit :index_for_search # ... endpre>और आपका जॉब तब तक कतार में नहीं लगेगा जब तक Sidekiq आपका मॉडल नहीं देख सकता।
तो, जब आप एक बैकग्राउंड जॉब कतार में लगाते हैं या किसी अन्य प्रोसेस को आपके द्वारा किए गए परिवर्तन के बारे में बताते हैं, तो
after_commitका उपयोग करें। यदि आप नहीं करते हैं, तो वे उस रिकॉर्ड को ढूंढने में सक्षम नहीं हो सकते जिसे आपने अभी छुआ था।लेकिन एक और समस्या है…
ठीक है, आपने अपने कई
after_saveहुक कोafter_commitका उपयोग करने के लिए बदल दिया। सब कुछ काम करने लगता है। इसे सब चेक इन करने और घर जाने का समय, है ना?पहले, आप अपने टेस्ट चलाना चाहेंगे:
test/models/question_test.rb require 'test_helper' class QuestionTest < ActiveSupport::TestCase test "A saved question is queued for indexing" do assert_enqueued_with(job: QuestionIndexerJob) do Question.create(title: "Is it legal to kill a zombie?") end end endpre>1) Failure: QuestionTest#test_A_saved_question_is_queued_for_indexing [/Users/jweiss/Source/testapps/after_commit/test/models/question_test.rb:7]: No enqueued job found with {:job=>QuestionIndexerJob}pre>उफ़! क्या टेस्ट को जॉब कतार में नहीं लगाना चाहिए था? वहां क्या हुआ?
डिफ़ॉल्ट रूप से, Rails प्रत्येक टेस्ट केस को अपने स्वयं के डेटाबेस ट्रांजैक्शन में लपेटता है। यह वास्तव में चीजों को गति दे सकता है। आपके द्वारा टेस्ट के दौरान किए गए सभी परिवर्तनों को पूर्ववत करने के लिए बस एक डेटाबेस कमांड लगती है।
लेकिन इसका यह भी अर्थ है कि आपका
after_commitकॉलबैक नहीं चलेगा। क्योंकिafter_commitकॉलबैक केवल तभी चलते हैं जब बाहरतम ट्रांजैक्शन कमिट हो चुका होता है।जब आप टेस्ट केस के अंदर
saveकॉल करते हैं, तो यह अभी भी एक ट्रांजैक्शन कमिट करता है (अधिक या कम), लेकिन वह अब दूसरा-सबसे-बाहरतम ट्रांजैक्शन है। तो आपकेafter_commitकॉलबैक तब नहीं चलेंगे जब आप उनसे उम्मीद करते हैं। और आप उनमें जो होता है उसे टेस्ट नहीं कर सकते।इस समस्या का भी एक आसान फिक्स है। अपने Gemfile में
test_after_commitजेम शामिल करें:Gemfile group :test do gem "test_after_commit" endpre>और आपके
after_commitहुक आपके दूसरे-से-अंतिम ट्रांजैक्शन कमिट होने के बाद चलेंगे। जो आप उम्मीद कर रहे थे वही है।आप सोच रहे होंगे, "यह अजीब है। मुझे Rails के साथ आने वाले कॉलबैक को टेस्ट करने के लिए एक पूरी अलग जेम का उपयोग क्यों करना पड़ता है? क्या यह स्वचालित रूप से नहीं होना चाहिए?"
आप सही हैं। यह अजीब है। लेकिन यह लंबे समय तक अजीब नहीं रहेगा।
एक बार Rails 5 शिप हो जाने पर, आपको
test_after_commitके बारे में चिंता करने की ज़रूरत नहीं होगी। क्योंकि इस समस्या को लगभग एक महीने पहले Rails में ठीक कर दिया गया था।मेरे अपने कोड में, मैं
after_commitका बहुत उपयोग करता हूं। मैं शायद इसका उपयोगafter_saveसे अधिक करता हूं! लेकिन यह अपनी समस्याओं और अजीब एज केस के बिना नहीं आया है।संस्करण दर संस्करण, हालांकि, यह बेहतर हो रहा है। और जब आप
after_commitका उपयोग सही स्थानों पर करते हैं, तो बहुत सारे अजीब, यादृच्छिक अपवाद बस अब नहीं होंगे।