You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用deliver_later时,如何在邮件中展示订单及订单项的变更差异?

在Rails中使用deliver_later时展示订单及订单项的变更差异

方法一:原生实现——预存变更快照

由于deliver_later会将邮件任务放入后台队列,内存中的ActiveRecord Dirty属性会丢失,核心思路是在订单更新回调中提前提取并序列化所有变更信息,再传给邮件任务。

步骤1:在Order模型中提取变更数据

class Order < ApplicationRecord
  has_many :order_line_items, dependent: :destroy

  after_update :send_change_notification

  private

  def send_change_notification
    changes_data = {
      order_changes: extract_order_attr_changes,
      line_item_changes: extract_line_item_changes
    }
    OrderChangeMailer.notify(changes_data).deliver_later
  end

  # 提取订单自身属性变更
  def extract_order_attr_changes
    tracked_attrs = %w[po_number notes]
    tracked_attrs.each_with_object({}) do |attr, hash|
      next unless saved_change_to_attribute?(attr)
      hash[attr] = {
        before: send("#{attr}_before_last_save"),
        after: send(attr)
      }
    end
  end

  # 提取订单项的增/删/改变更
  def extract_line_item_changes
    original_items = order_line_items_was.to_a # Rails原生关联快照,获取更新前的订单项
    current_items = order_line_items.to_a

    changes = {
      added: current_items.reject { |item| original_items.any? { |orig| orig.id == item.id } },
      updated: current_items.select do |item|
        orig_item = original_items.find { |orig| orig.id == item.id }
        orig_item && (orig_item.item_description != item.item_description || orig_item.quantity != item.quantity)
      end.map do |item|
        orig_item = original_items.find { |orig| orig.id == item.id }
        {
          id: item.id,
          description: { before: orig_item.item_description, after: item.item_description },
          quantity: { before: orig_item.quantity, after: item.quantity }
        }
      end,
      removed: original_items.reject { |item| current_items.any? { |curr| curr.id == item.id } }
    }

    # 过滤空变更类型
    changes.reject { |_, v| v.empty? }
  end
end

步骤2:邮件控制器与模板

邮件控制器接收序列化后的变更数据:

class OrderChangeMailer < ApplicationMailer
  def notify(changes_data)
    @changes_data = changes_data
    mail(to: 'recipient@example.com', subject: '订单更新通知')
  end
end

邮件模板(ERB)展示变更:

<h1>订单更新通知</h1>

<% if @changes_data[:order_changes].present? %>
  <h2>订单基本信息变更</h2>
  <ul>
    <% @changes_data[:order_changes].each do |attr, values| %>
      <li>
        <strong><%= attr.humanize %>:</strong>
        <%= values[:before] %> → <%= values[:after] %>
      </li>
    <% end %>
  </ul>
<% end %>

<% if @changes_data[:line_item_changes].present? %>
  <h2>订单项变更</h2>

  <% if @changes_data[:line_item_changes][:added].present? %>
    <h3>新增订单项</h3>
    <ul>
      <% @changes_data[:line_item_changes][:added].each do |item| %>
        <li>描述: <%= item.item_description %>,数量: <%= item.quantity %></li>
      <% end %>
    </ul>
  <% end %>

  <% if @changes_data[:line_item_changes][:updated].present? %>
    <h3>修改订单项</h3>
    <ul>
      <% @changes_data[:line_item_changes][:updated].each do |item| %>
        <li>
          订单项ID: <%= item[:id] %>
          <ul>
            <% if item[:description].present? %>
              <li>描述: <%= item[:description][:before] %> → <%= item[:description][:after] %></li>
            <% end %>
            <% if item[:quantity].present? %>
              <li>数量: <%= item[:quantity][:before] %> → <%= item[:quantity][:after] %></li>
            <% end %>
          </ul>
        </li>
      <% end %>
    </ul>
  <% end %>

  <% if @changes_data[:line_item_changes][:removed].present? %>
    <h3>删除订单项</h3>
    <ul>
      <% @changes_data[:line_item_changes][:removed].each do |item| %>
        <li>描述: <%= item.item_description %>,数量: <%= item.quantity %></li>
      <% end %>
    </ul>
  <% end %>
<% end %>

方法二:使用Gem简化变更跟踪

如果原生代码过于繁琐,可借助审计类Gem自动记录变更历史,推荐paper_trail:

步骤1:安装与配置

# Gemfile
gem 'paper_trail'

运行迁移生成审计表:

rails generate paper_trail:install
rails db:migrate

在模型中启用审计:

class Order < ApplicationRecord
  has_paper_trail
  has_many :order_line_items, dependent: :destroy
end

class OrderLineItem < ApplicationRecord
  has_paper_trail
  belongs_to :order
end

步骤2:在回调中传递审计版本

class Order < ApplicationRecord
  after_update :send_change_notification

  private

  def send_change_notification
    order_version = versions.last
    # 获取订单更新前后的订单项审计记录
    line_item_versions = OrderLineItem.where(order_id: id).versions.where('created_at >= ?', updated_at - 1.second)
    OrderChangeMailer.notify(order_version, line_item_versions).deliver_later
  end
end

步骤3:邮件模板展示审计数据

<h1>订单更新通知</h1>

<% if @order_version.changeset.present? %>
  <h2>订单基本信息变更</h2>
  <ul>
    <% @order_version.changeset.each do |attr, (before, after)| %>
      <li>
        <strong><%= attr.humanize %>:</strong>
        <%= before %> → <%= after %>
      </li>
    <% end %>
  </ul>
<% end %>

<% if @line_item_versions.present? %>
  <h2>订单项变更</h2>
  <ul>
    <% @line_item_versions.each do |version| %>
      <li>
        订单项ID: <%= version.item_id %>
        <ul>
          <% version.changeset.each do |attr, (before, after)| %>
            <li><%= attr.humanize %>: <%= before %> → <%= after %></li>
          <% end %>
        </ul>
      </li>
    <% end %>
  </ul>
<% end %>

关键注意事项

  • 原生方案中,order_line_items_was是Rails提供的关联快照,可直接获取更新前的订单项集合,无需额外查询
  • 使用deliver_later时,必须传递序列化后的普通哈希或审计版本对象,不能直接传ActiveRecord实例(后台序列化会丢失Dirty属性)
  • Gem方案会增加数据库存储开销,但适合需要长期保留变更历史的场景

内容的提问来源于stack exchange,提问作者karns

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 13:47:02