【问题标题】:How to keep track of model history with mapping table in Ruby on Rails?如何在 Ruby on Rails 中使用映射表跟踪模型历史记录?
【发布时间】:2011-01-20 09:04:43
【问题描述】:

梦想

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

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

可能的架构

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
  and (uam.end_time >= o.created_at or uam.end_time is null)
;

编辑:解决方案

@KandadaBoggu 发布了一个很好的解决方案。 Vestal Versions plugin 是一个出色的解决方案

下面的sn-p取自http://github.com/laserlemon/vestal_versions

最后,DRY ActiveRecord 版本控制!

technoweenieacts_as_versioned 是一个很好的开始,但它未能跟上 ActiveRecord 在 2.1 版本中引入脏对象的速度。此外,每个版本化模型都需要自己的版本表,该表复制了原始表的大部分列。然后,版本表中会填充通常与原始记录的大部分属性重复的记录。总而言之,不是很干燥。

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

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

【问题讨论】:

  • PaperTrail 也能做到这一切。
  • 请不要在问题中编辑解决方案公告。改为自己创建一个答案或接受现有答案之一。

标签: ruby-on-rails activerecord schema


【解决方案1】:

为此使用Vestal versions plugin

请参阅this 截屏了解更多详情。

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

【讨论】:

  • KandadaBoggu,我得到了这个设置,看起来效果很好。但是,我在使用 .at 方法时遇到了问题。我得到了NoMethodError: undefined method at' for #<0x000001011c9070>
  • &lt;&gt;<b></b>
【解决方案2】:

我想我会添加一个更新的答案。似乎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

===编辑====

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

【讨论】:

    【解决方案3】:

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

    ...下单后, 总能参考用户 当时使用的地址 下单。

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

    不正常吗?

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

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

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

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

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

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

    【讨论】:

    • 这是一个非常有用的答案,我可能正在考虑。我可能会为Item 详细信息这样做。例如,item.priceitem.costitem.delivery_costitem.supplier_id 等将被复制到 Order 字段。这里的不同之处在于管理员将对项目详细信息进行更改,而我们不必深入研究修订历史的可能性较小。我想我不想看到Order 表被这么多的列夸大了。这有意义吗?
    • 我听说你有很多专栏。这是一个棘手的问题,因为订单本质上是法律文件。他们需要处于变更控制之下,并具有历史记录。但是每个订单都从其自己的历史记录开始,从订单创建时开始。订单的发货更改/历史记录与用户记录的历史记录相同。它们是两种不同的东西。用于跟踪更改的插件被称为“...已审核”并非偶然,请记住还要跟踪 对订单的任何数据进行了每次更改。
    【解决方案4】:

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

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

    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 是否与时间完全匹配并不重要。

    【讨论】:

    • 这看起来是一个非常优雅的解决方案。但是,在 Order#address 方法中,您使用的是revision_at(updated_at)——如果 updated_at 与审计表中的 user_address updated_at 字段不匹配怎么办?会不会找不到地址?
    • 我正在寻找有关acts_as_audited 的良好文档,但似乎找不到。请给个链接?
    • 不幸的是,acts_as_audited 的文档严重缺乏。我知道 revision_at 的唯一原因是,当我派生存储库以添加我想要的功能时,我已经深入挖掘了 act_as_audited 的源代码。也许有一天我会记录剩下的部分。现在我已经更新了解决方案来回答您关于匹配时间戳的问题。
    • 我一直在研究这个问题,发现您可以使用rake doc:plugins:acts_as_audited 生成自己的文档。我得到了这个设置的插件,但它没有产生最可靠的结果。例如,如果我使用 .revision_atfoo.barA 更新为 B,我将不会总是得到适当的值。使用 Sequel Pro,我可以看到审计表中的序列化更改,但在特定点重新组装模型似乎不能 100% 工作。这很可悲,因为否则这将是完美的......
    • EmFi,KandadaBoggu 在下面提到了 vestal_versions 插件。它的操作几乎与acts_as_audited 相同,但似乎功能更可靠。对于所有 acts_as_audited 用户,可能值得一试。
    【解决方案5】:

    我认为这很简单:

    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”字段设置为指向旧数据的指针,对旧数据进行某种“逻辑删除”。这消除了对映射表的需要,并创建了一种linked list。这样,无论何时下订单,您都可以将其与特定的 UserAddress 相关联,该 UserAddress 保证永远不会更改。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-10-10
      • 1970-01-01
      • 2020-11-07
      • 1970-01-01
      • 1970-01-01
      • 2011-04-19
      • 2011-04-01
      • 2023-04-06
      相关资源
      最近更新 更多