延迟作业 - 销毁对象后无法为非持久化记录创建作业

Vic*_*tor 3 ruby-on-rails delayed-job

Rails 4 和 delay_job 4.1.2。我试图在销毁评论后延迟重新计算总体评分,但显然是因为在销毁评论对象后,评论对象没有 ID。因此,每次尝试销毁对象后,它都会尝试创建延迟作业,但会引发此错误:

ArgumentError (job cannot be created for non-persisted record: 
#<Review id: 44, review: "Bad", rating: 1, reviewable_id: 2, 
reviewable_type: "Spot", user_id: 1, created_at: "2016-05-30 17:13:29", 
updated_at: "2016-05-30 17:13:29">):
  app/controllers/reviews_controller.rb:40:in `destroy'
Run Code Online (Sandbox Code Playgroud)

我有以下代码:

# reviews_controller.rb
class ReviewsController < ApplicationController
  def destroy
    review.destroy
    flash[:success] = t("reviews.destroy.success")
  end
end

# review.rb
class Review < ActiveRecord::Base
  after_destroy :calculate_overall_rating

  def calculate_overall_rating
    if number_of_reviews > 0
      reviewable.update_attribute(:overall_rating, overall_rating)
    else
      reviewable.update_attribute(:overall_rating, 0)
    end
  end
  handle_asynchronously :calculate_overall_rating
end
Run Code Online (Sandbox Code Playgroud)

值得注意的是,calculate_overall_rating不需要Review对象。

如果我删除handle_asynchronously :calculate_overall_rating它会起作用,并重新计算。但我正试图推迟这项工作。

Bor*_*aMa 5

当您尝试延迟已删除(或尚未创建)记录上的方法时,delay_job确实会引发此错误。导致这个错误的直接原因是delayed_job在调用方法的时候通过self(即刚刚删除的review对象)作为目标对象handle_asynchronously。我不知道它为什么会这样,我刚刚从 gem 的一位作者那里找到了一份声明,说它的工作方式与 ActiveJob 相同。

无论如何,在我看来,您可能在错误的地方定义了重新计算方法。如果我理解正确,在销毁评论后,将根据给定的所有评论reviewable(例如一个点)重新计算平均评分。对我来说,这样的代码被定义为单个评论实例的方法似乎很奇怪。单个评论(更像是已删除的评论)不应该了解同一地点的其他评论,并且根本不必处理它们。

我猜的方法应该被定义为一方法来代替,与reviewable作为参数。当然,这意味着您还必须使用其他计算方法overall_ratingnumber_of_reviews类方法。但我认为这是一件好事,因为这些方法的领域不在单一审查范围内。类似于以下内容:

# review.rb
class Review < ActiveRecord::Base
  after_destroy :recalculate_overall_rating

  def recalculate_overall_rating
    self.class.calculate_overall_rating(reviewable)
  end

  def self.calculate_overall_rating(reviewable)
    if number_of_reviews(reviewable) > 0
      reviewable.update_attribute(:overall_rating, overall_rating(reviewable))
    else
      reviewable.update_attribute(:overall_rating, 0)
    end
  end
  handle_asynchronously :calculate_overall_rating
end
Run Code Online (Sandbox Code Playgroud)

另一种选择(我猜我更喜欢它)是将重新计算方法放在可审查的类中,例如在Post类中。如果您有更多可审查的类类型,您可以创建一个包含所有这些类的模块,例如Reviewable(我希望这不会与 Rails 关联名称冲突)并将重新计算方法放在其中,这次是作为实例方法。为什么?因为它是一个可评论的实例,它希望重新计算其所有评论,并且即使在删除评论后它仍然存在,因此它可以轻松地异步运行。类似于以下内容:

# reviewable.rb
module Reviewable
  def calculate_overall_rating
    if number_of_reviews > 0
      update_attribute(:overall_rating, overall_rating)
    else
      update_attribute(:overall_rating, 0)
    end
  end
  handle_asynchronously :calculate_overall_rating

  # overall_rating and number_of_reviews are also defined in this module
end

# review.rb
class Review < ActiveRecord::Base
  after_destroy :recalculate_overall_rating

  def recalculate_overall_rating
    reviewable.calculate_overall_rating
  end
end

# post.rb
class Post < ActiveRecord::Base
  include Reviewable
end
Run Code Online (Sandbox Code Playgroud)