如何使用Ruby on Rails中的映射表跟踪模型历史记录?

梦想

我想记录用户何时更改其地址。

这样,当下订单时,它将始终能够引用订单放置时使用的用户地址。

可能的架构

users ( id username email ... ) user_addresses ( id label line_1 line_2 city state zip ... ) user_addresses_map ( user_id user_address_id start_time end_time ) orders ( id user_id user_address_id order_status_id ... created_at updated_at ) 

在sql中,这可能类似于:[sql]

 select ua.* from orders o join users u on u.id = o.user_id join user_addressses_map uam on uam.user_id = u.id and uam.user_address_id = o.user_address_id join user_addresses ua on ua.id = uam.user_address_id and uam.start_time = o.created_at or uam.end_time is null) ; 

编辑:解决方案

@KandadaBoggu发布了一个很棒的解决方案。 Vestal Versions插件是一个很好的解决方案

下面的代码段取自http://github.com/laserlemon/vestal_versions

最后,DRY ActiveRecord版本控制!

technoweenie的 acts_as_versioned是一个很好的开始,但它无法跟上ActiveRecord在2.1版中引入脏对象的步伐 。 此外,每个版本化的模型都需要自己的版本表,该表复制了大多数原始表的列。 然后使用通常复制大多数原始记录属性的记录填充版本表。 总而言之,不是很干。

vestal_versions只需要一个版本表(与其父模型多态关联),并且不需要对现有表进行任何更改。 但它通过存储模型变化的序列化哈希来实现DRYer的一步。 想想现代版控制系统。 通过遍历变更记录,模型可以恢复到任何时间点。

而这正是vestal_versions所做的。 模型不仅可以恢复到以前的版本号,还可以恢复到日期或时间!

使用Vestal版本插件 :

有关更多详细信息,请参阅此屏幕演示。

 class Address < ActiveRecord::Base belongs_to :user versioned end class Order < ActiveRecord::Base belongs_to :user def address @address ||= (user.address.revert_to(updated_at) and user.address) end end 

以为我会添加一个更新的答案。 似乎paper_trail gem已成为Rails中最受欢迎的版本。 它也支持Rails 4。

https://github.com/airblade/paper_trail

从他们的自述文件:

要设置和安装:

 gem 'paper_trail', '~> 3.0.6' bundle exec rails generate paper_trail:install bundle exec rake db:migrate 

基本用法:

 class Widget < ActiveRecord::Base has_paper_trail end 

对于Widget类的特定实例:

 v = widget.versions.last v.event # 'update' (or 'create' or 'destroy') v.whodunnit # '153' (if the update was via a controller and # the controller has a current_user method, # here returning the id of the current user) v.created_at # when the update occurred widget = v.reify # the widget as it was before the update; # would be nil for a create event 

我只玩过它,但我即将开始一个非常雄心勃勃的网站,需要对某些类进行良好的版本控制,我决定使用paper_trail

===编辑====

我已经在paper_trail实现了paper_trail gem的生产,并且使用上述方法运行良好。 唯一的变化是我在我的Gemfile使用gem 'paper_trail', '~> 4.0.0.rc'

从数据架构的角度来看,我建议解决你陈述的问题

…当下订单时,它将始终能够引用订单下达时使用的用户地址。

…您只需将此人的地址复制到订单模型中即可。 这些项目将在OrderItem模型中。 我会将问题重新表述为“订单在某个时间点发生。订单处理包括该时间点的所有相关数据。”

这是不正常的吗?

不,因为OrderHeader代表一个时间点,而不是持续的“真相”。

以上是处理订单标题数据的标准方法,并从模式中移除了大量复杂性,而不是跟踪模型中的所有更改。

– 使用解决方案来解决真正的问题,而不是可能的问题 – 是否有人需要用户更改的历史记录? 或者您是否只需要订单标题来反映订单本身的实际情况?

补充:请注意,您需要知道哪个地址最终用于发送订单/发票。 您不希望查看旧订单并查看用户的当前地址,您希望查看订单发货时订单使用的地址。 有关详细信息,请参阅下面的评论。

请记住,最终,系统的目的是模拟现实世界。 在现实世界中,一旦订单被打印出来并与订购的商品一起发送,订单的收货方式就不再变化。 如果您要发送软商品或服务,那么您需要从更简单的示例中进行推断。

订单系统是一个很好的案例,了解业务需求和现实是非常重要的 – 不要只是与业务经理交谈,还要与一线销售人员,订单文员,应收账款文员,运输部门人员交谈等

您正在寻找acts_as_audited插件 。 它提供了一个用于代替地图的审计表和模型。

要进行设置,请运行迁移并将以下内容添加到用户地址模型中。

 class UserAddress < ActiveRecord::Base belongs_to :user acts_as_audited end 

设置完成后,您只需按顺序定义地址方法即可。 像这样的东西:

 class Order < ActiveRecord::Base belongs_to :user attr_reader :address def address @address ||= user.user_address.revision_at(updated_at) end end 

您可以使用@order.address访问订单完成时的用户地址

revision_at是acts_as_audited添加到审计模型的方法。 它需要一个时间戳并重建模型,就像在那个时间点一样。 我相信在给定时间之前,通过对特定模型的审核将修订整合在一起。 因此,订单上的updated_at是否与时间完全匹配并不重要。

我想这会很简单:

 Users: id name address_id UserAddresses: id user_id street country previous_address_id Orders id user_id #to get the users name user_address_id #to get the users address 

然后,当用户更改其地址时,您可以通过创建新的UserAddress对旧数据执行某种“逻辑删除”,并将“previous_address_id”字段设置为指向旧数据的指针。 这消除了对地图表的需要,并创建了一种链表 。 通过这种方式,无论何时下订单,您都可以将其与特定的UserAddress相关联,保证永远不会更改。

这样做的另一个好处是它允许您跟踪用户地址的更改,有点像一个基本的记录器。