使用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
相关产品推荐
相关产品推荐

