如何让每个月1次运行的工作是幂等的(Rails 5)?

时间:2017-01-11 17:55:27

标签: ruby-on-rails ruby datetime ruby-on-rails-5 delayed-job

根据EST / EDT时区,我需要每月生成一次发票(客户遍布全国各地,但在此行业中,计费时间在同一时区)。

我正在创建GenerateInvoicesJob,但我无法推断出100%完美的生成发票的方式,因此不会出现任何可能的重复/混淆:

  • 每月仅生成一次发票
  • 让工作每天都在运行
  • 让工作成为幂等的

然后最后一点对我来说很难,我如何确保没有EST / DST的错误和1小时的滑动。

这是我的clock.rb:

every(1.day, 'GenerateInvoicesJob', tz: 'America/New_York', at: '04:00') do
  Delayed::Job.enqueue GenerateInvoicesJob.new, queue: 'high'
end

这是我工作的首要任务:

Unit.where(enabled: true)
  .joins(:user)
  .where('last_invoice_generated_at <= ?', Time.now.utc.end_of_month)
  .each do |unit|

  ActiveRecord::Base.transaction do
    unit.update_attributes(
      last_invoice_generated_at: Time.now.utc
    )
    invoice = Invoice.create!(
      ...
    )
    line_item = LineItem.create!(
      ...
    )
  end

我意识到直接条件逻辑可能是错误的,所以这不完全是我的问题......我对这个问题的主要补充是最好的总体方式,所以我可以确保所有时间都在EST占100%,包括奇怪的1小时错误等。这项工作非常重要,所以我对制作完美的方式犹豫不决。

除此之外,我不确定是否应该将UTC存储在数据库中....通常我知道你总是应该存储UTC,但我知道UTC没有DST所以我就是这样我担心,如果我这样存储,工作可以运行一次,发票将无法正常运行

1 个答案:

答案 0 :(得分:0)

我会在工作人员中做这样的事情:

# `beginning_of_month` because we want to load units that haven't 
# been billed this month
units_to_bill = Unit.where(enabled: true)
  .where('last_invoice_generated_at < ?', Time.current.beginning_of_month)

# `find_each` because it needs less memory
units_to_bill.find_each do |unit|

  # Beginn a transaction to ensure all or nothing is updated
  Unit.transaction do

    # reload the unit, because it might have been updated by another 
    # task in the meantime
    unit.reload

    # lock the current unit for updates
    unit.lock!

    # check if the condition is still true
    if unit.last_invoice_generated_at < 1.month.ago

      # generate invoices
      # invoice = Invoice.create!(
      # line_item = LineItem.create!(

      # last step update unit
      unit.update_attributes(
        last_invoice_generated_at: Time.current
      )
    end
  end
end