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

mač*_*ček 9 schema activerecord 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
)
Run Code Online (Sandbox Code Playgroud)

在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
  and (uam.end_time >= o.created_at or uam.end_time is null)
;
Run Code Online (Sandbox Code Playgroud)

编辑:解决方案

@KandadaBoggu发布了一个很好的解决方案.在维斯塔版本的插件是一个很好的解决方案.

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

最后,DRY ActiveRecord版本控制!

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

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

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

Har*_*tty 13

使用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
Run Code Online (Sandbox Code Playgroud)


wul*_*ong 8

以为我会添加一个更新的答案.似乎paper_trailgem已成为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
Run Code Online (Sandbox Code Playgroud)

基本用法:

class Widget < ActiveRecord::Base
  has_paper_trail
end
Run Code Online (Sandbox Code Playgroud)

对于类的特定实例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
Run Code Online (Sandbox Code Playgroud)

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

===编辑====

我已经paper_trail在www.muusical.com上实现了生产中的宝石,并且使用上述方法效果很好.唯一的变化是我在使用gem 'paper_trail', '~> 4.0.0.rc'我的Gemfile.


Lar*_*y K 6

从数据架构的角度来看,我建议解决您提出的问题

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

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

是不正常的吗?

不,因为 OrderHeader 代表一个时间点,而不是正在进行的“真相”。

以上是处理订单标题数据的标准方法,与跟踪模型中的所有更改相比,它从您的架构中消除了很多复杂性。

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

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

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

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