简洁的解释
数据库中有一行显示了“雨伞”的当前状态,该状态是从“产品”模型中得出的。
您想快速轻松地访问与伞相关的更改的完整历史记录,包括相关模型。
问题在于,当事件表的长度为数万行时,纸笔痕迹不会带来麻烦,并且由于包含重要的历史记录,因此您无法截断它,并且由于它必须解析成千上万条记录,因此其性能令人担忧YAML行以查找“相关”更改。
做完背景阅读,仍然不知道问题是什么
对于我来说,这似乎是基本的东西,但是我看不到有其他人使用纸笔迹来解决它,因此,我根本不知道它的非专有名称是什么(如果有的话)。我能想到的最好的标题是:“在没有纸轨的情况下,什么是与什么是架构”?我要在模型和时间之间建立一对多关系吗?
已经阅读了“ A !!! Ruby中的设计模式,2007”,其中提到了四个人的设计模式,而没有提到这个问题吗?
尝试过“纸痕”宝石,但还不能完全解决
问题
假设您拥有产品,公司和类别,并且
Product: id, name, price, barcode, (also company_id and category_id)
Company: id, name, registered_company_number
Category: id, name, some_immutable_field
Company has many Products
Category has many Products
并且您需要查看每个产品的历史记录,包括其自身的变化,例如价格,其所属公司的变化,公司名称的变化,类别的相同,例如:
date | event | company name | cmp | category | cat | name | price
| | | id | name | id | |
------|---------------|--------------|-----|----------|-----|----------|------
jan11 | created | megacorp | 1 | outdoors | 101 | umbrella | 10
feb11 | cat change | megacorp | 1 | fashion | 102 | umbrella | 10
mar11 | cat rename | megacorp | 1 | vogue | 102 | umbrella | 10
apr11 | cmp rename | megacorp inc | 1 | vogue | 102 | umbrella | 10
may11 | cmp change | ultra & sons | 2 | vogue | 102 | umbrella | 12
jul11 | cmp change | megacorp | 1 | vogue | 102 | umbrella | 12
请注意,虽然雨伞与Ultra&Sons一起使用,但megacorp inc更名为megacorp,但由于其与该产品无关,因此在此历史中我们并未显示。 (公司1的名称更改发生在6月11日,但未显示)
这可以通过papertrail来完成,但是执行该操作的代码非常复杂,冗长且过程繁琐。或如果按照papertrail的意图“优雅地”编写,则非常慢,因为它使许多数据库调用当前非常膨胀的事件表。
为什么纸张拖尾不是此处的正确解决方案
Paper Trail将所有更改存储在YAML中,数据库表是多态的,并存储来自许多不同模型的大量数据。这张桌子以及这张宝石似乎很适合识别谁做了哪些更改...但是像我需要使用它一样将它用于历史记录,就像一个上帝桌子,该桌子存储了关于过去发生的事情和承担太多责任的所有信息。
我追求的历史记录并不关心对对象的所有更改,仅关心某些字段。 (但是我们仍然需要记录所有小的变化,只是不将它们包括在产品的历史中,所以我们不能仅仅记录这些事情,因为纸质记录纸有其常规职责来确定谁做了什么,而不能仅仅对其进行优化。以此目的)。提取此信息需要获取所有记录,其中item_type为Product,item_id为当前正在查看的product_id,然后解析YAML,并查看我们是否对更改感兴趣(一个字段是否已更改,这是一个字段,有兴趣看到更改吗?)。然后,对产品在其生命周期中与之相关联的每个类别和公司进行相同的操作,但仅保留将产品与所述类别/公司相关联的窗口中发生的更改。
纸迹可以很容易地关闭...因此,如果您的一名开发人员在运行某些操作的同时在代码中禁用了它作为一种优化,但是却忘记编写代码将其重新打开,就没有历史了记录下来。并且由于纸质痕迹更多地是循环中的人而不是循环中的人,所以如果它不运行,您可能不会注意到(然后必须编写过于复杂的代码,以捕获所有可能的带有漏洞数据的情况)。需要一种强制保存历史记录的解决方案。
半烤溶液
从概念上讲,我认为模型应该在持久模型和变化模型之间划分。我很惊讶这不是从头开始扎根的东西,但是有一些问题:
Product: id, barcode
Product_period: id, name, price, product_id, start_date, (also company_id and product_id)
Company: id, registered_company_number
Company_period: id, name, company_id, start_date
Category: id, some_immutable_field
Category_period: id, name, category_id, start_date
每次产品价格或产品的company_id更改时,都会在product_period中添加新行,该行记录新时代的开始,该时代现在的价格为11美元,以及吉祥的start_date(时间,时间)期开始。
因此,在产品模型中,所有对不变的事物的调用或我们仅关心最新值的调用都保持原样;而不断变化和我们关心的事情,对于外部用户(或现有代码)而言似乎具有在产品模型上运行的方法,但实际上是对该产品的最新product_period进行调用并在其中获取最新值。
这从表面上解决了问题,但是有点麻烦,而且仍然存在您必须通过company_period和category_period选择相关条目(例如在公司/类别中经历更改,并且这是在产品与之相关联),而不是更优雅的事物。
至少MySQL将运行得更快,并且有更多的自由创建索引,并且不再有成千上万的YAML解析器陷入困境。
为了编写更具可读性的代码,这些改进是否足够?别人在做什么?这有名字吗?是否有更优雅的解决方案或只是权衡取舍?
最佳答案
Rails还有很多其他版本和历史记录的宝石(我在10年前就贡献了第一个宝石!)-在此处找到它们,https://www.ruby-toolbox.com/categories/Active_Record_Versioning
它们都有不同的存储方法,如您上面建议的那样,其中一些是可配置的。我也不同意所有用户的多态上帝表,但是如果您有不错的索引,它也不算太慢。