मेरे पास after_save और after_commit हुक वाला एक मॉडल है। after_save हुक डेटाबेस में कुछ जानकारी संग्रहीत करता है जो after_commit हुक द्वारा आवश्यक हैं।

क्या यह मान लेना सुरक्षित है कि सभी डेटाबेस लेनदेन after_commit हुक आग से पहले समाप्त हो गए हैं?

समस्या का वर्णन करने के लिए यहां एक छोटा सा उदाहरण दिया गया है:

class TicketComment < ApplicationRecord
  after_save :extract_mentions
  after_commit :notify

  def extract_mentions
    current_mentions = mentions.map(&:user).map(&:username)
    new_mentions = description.scan(/(?<=^|(?<=[^a-zA-ZÀ-ž0-9_-]))@([a-zA-ZÀ-ž]+[a-zA-ZÀ-ž0-9_]+)/).flatten

    mentions_to_add = new_mentions - current_mentions
    mentions_to_remove = current_mentions - new_mentions

    users_to_add = User.select(:id).where("username IN (?)", mentions_to_add.flatten).map(&:id)
    users_to_remove = User.select(:id).where("username IN (?)", mentions_to_remove.flatten).map(&:id)

    users_to_add&.each do |id_to_add|
      mentions.create(user_id: id_to_add)
    end

    mentions.where(user_id: users_to_remove).destroy_all
  end

  def notify
    user_ids = ticket.participants.pluck(:id) - [Current.user.id]
    TicketCommentMailer.comment_added(id, user_ids).deliver_later
  end
end

कृपया ध्यान रखें कि यह उदाहरण सरलीकृत है। extract_mentions-पार्ट एक मॉडल चिंता का विषय है जिसका उपयोग कई मॉडलों में किया जाता है, इसलिए मैं extract_mentions-हुक के अंदर notify कोड नहीं चला सकता।

2
Slevin 9 सितंबर 2020, 11:02

1 उत्तर

सबसे बढ़िया उत्तर

कुछ संदर्भों के साथ मेरी टिप्पणी का विस्तार करने के लिए, हाँ आप मान सकते हैं कि आपके #after_commit कॉलबैक चलने से पहले सभी डेटाबेस लेनदेन समाप्त हो गए हैं। यहाँ active_record/transaction.rb (github में एक टिप्पणी दी गई है ):

लेन-देन के तुरंत बाद लेनदेन के भीतर सहेजे या नष्ट किए गए प्रत्येक रिकॉर्ड पर #after_commit कॉलबैक को कॉल किया जाता है। लेन-देन या सेवपॉइंट के वापस लुढ़कने के तुरंत बाद लेन-देन के भीतर सहेजे गए या नष्ट किए गए प्रत्येक रिकॉर्ड पर #after_rollback कॉलबैक कॉल किए जाते हैं।

ये कॉलबैक अन्य सिस्टम के साथ इंटरैक्ट करने के लिए उपयोगी हैं क्योंकि आपको गारंटी दी जाएगी कि कॉलबैक केवल तभी निष्पादित किया जाएगा जब डेटाबेस स्थायी स्थिति में हो।

लेन-देन कोड थोड़ा बालों वाला है, क्योंकि यह नेस्टेड लेनदेन, सेवपॉइंट इत्यादि को संभालता है। अंततः, हालांकि, TransactionManager.commit_transaction (active_record/connect_adapters/abstract/transaction.rb), प्रबंधक वास्तव में पहले डेटाबेस-विशिष्ट प्रतिबद्ध कार्रवाई निष्पादित करता है माता-पिता के लेन-देन से जुड़े सभी विशिष्ट रिकॉर्ड आईडी पर #after_commit कॉलबैक चलाना।

def commit_transaction
  @connection.lock.synchronize do
    ...
    transaction.commit  # executes database-specific commit action
    transaction.commit_records # executes after_commit callbacks
  end
end

सभी को बचाने और नष्ट करने के संचालन, उनके दृढ़ता कॉलबैक सहित, एक लेनदेन ब्लॉक में निहित रूप से लिपटे हुए हैं। रेल में एक अन्य टिप्पणी के अनुसार active_record/transaction.rb:

#लेनदेन कॉल नेस्ट किया जा सकता है। डिफ़ॉल्ट रूप से, यह नेस्टेड ट्रांजेक्शन ब्लॉक में सभी डेटाबेस स्टेटमेंट को पैरेंट ट्रांजेक्शन का हिस्सा बना देता है।

इसलिए, आपके #after_save कॉलबैक में #create और #destroy_all द्वारा जेनरेट किए गए डेटाबेस स्टेटमेंट मूल पैरेंट ट्रांजैक्शन के साथ एक ही समय में प्रतिबद्ध हैं, और #after_commit पर निर्भर हो सकते हैं। वापस कॉल करें। उदाहरण के लिए एक सरलीकृत उदाहरण:

class Foo < ApplicationRecord
  after_save :do_stuff
  after_commit :check

  def do_stuff
    Bar.create!(name: 'bar')
  end

  def check
    Rails.logger.info "Bar count: #{Bar.count}"
    Rails.logger.info "name: #{Bar.first.name}"
  end
end

आउटपुट लॉग:

> Foo.create!(name: "Jerry")
  (0.0ms)  begin transaction
  Foo Create (0.3ms)  INSERT INTO "foos" ("name", "created_at", "updated_at") VALUES (?, ?, ?)  [["name", "Jerry"], ["created_at", "2020-09-16 01:49:57.480367"], ["updated_at", "2020-09-16 01:49:57.480367"]]
  Bar Create (0.2ms)  INSERT INTO "bars" ("name", "created_at", "updated_at") VALUES (?, ?, ?)  [["name", "bar"], ["created_at", "2020-09-16 01:49:57.485953"], ["updated_at", "2020-09-16 01:49:57.485953"]]
 (2.3ms)  commit transaction
 (0.1ms)  SELECT COUNT(*) FROM "bars"
Bar count: 1
  Bar Load (0.1ms)  SELECT "bars".* FROM "bars" ORDER BY "bars"."id" ASC LIMIT ?  [["LIMIT", 1]]
bar

ध्यान दें कि निश्चित रूप से किसी भी #after_commit कॉलबैक को लेन-देन के भीतर बनाए गए, अपडेट किए गए या नष्ट किए गए किसी भी मॉडल के लिए लागू किया जाएगा। इसलिए, यदि Bar #after_commit कॉलबैक को भी परिभाषित करता है, तो वे भी चल जाते हैं।

1
rmlockerd 16 सितंबर 2020, 05:02