如何使用ORM设置发布修订/历史跟踪?

时间:2009-12-19 19:35:46

标签: orm revision

我正在试图弄清楚如何为帖子和其他内容设置修订系统。我想这意味着它需要使用基本的belongs_to / has_one / has_many / has_many_though ORM(任何好的ORM都应该支持这个)。

我在想我可以拥有一些像(匹配模型)的表格

[[POST]] (has_many (text) through (revisions)
id
title

[[Revisions]] (belongs_to posts/text)
id
post_id
text_id
date

[[TEXT]]
id
body
user_id

我可以通过修订表加入最新的TEXT主体。但是我对它如何运作有点模糊。有没有人设置这样的东西?

基本上,我需要能够加载文章并请求最新的内容条目。

// Get the post row
$post = new Model_Post($id);
// Get the latest revision (JOIN through revisions to TEXT) and print that body.
$post->text->body;

有能力及时改回以前的版本并删除修订版也将是一个很大的帮助。

无论如何,这些只是我认为某种历史跟踪可行的想法。我愿意接受任何形式的跟踪,我只想知道最佳实践是什么。

:编辑:

似乎向前推进,两张桌子似乎最有意义。由于我打算存储两份文本,这也有助于节省空间。第一个表posts将存储当前版本的数据,以便快速读取而无需任何连接。帖子body将是匹配修订版text字段的值 - 但会通过markdown / bbcode / tidy / etc进行处理。这将允许我保留原始文本(用于下一次编辑),而不必在一个修订行中存储该文本两次(或者每次显示时都必须重新解析它。)

所以提取将是ORM友好的。然后,对于创建/更新,我将不得不单独处理修订,然后只使用新的当前修订版值更新post对象。

  CREATE TABLE IF NOT EXISTS `posts` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `published` tinyint(1) unsigned DEFAULT NULL,
  `allow_comments` tinyint(1) unsigned DEFAULT NULL,
  `user_id` int(11) NOT NULL,
  `title` varchar(100) NOT NULL,
  `body` text NOT NULL,
  `created` datetime NOT NULL,
  PRIMARY KEY (`id`),
  KEY `user_id` (`user_id`),
  KEY `published` (`published`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;

CREATE TABLE IF NOT EXISTS `postsrevisions` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `post_id` int(10) unsigned NOT NULL,
  `user_id` int(10) unsigned NOT NULL,
  `is_current` tinyint(1) unsigned DEFAULT NULL,
  `date` datetime NOT NULL,
  `title` varchar(100) NOT NULL,
  `text` text NOT NULL,
  `image` varchar(200) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `post_id` (`post_id`),
  KEY `user_id` (`user_id`),
  KEY `is_current` (`is_current`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;

4 个答案:

答案 0 :(得分:2)

您展示的Revisions表格会模拟PostsText之间的多对多关系。除非Text中的给定行可以为Posts中的多行提供内容,否则这可能您想要的内容。这不是大多数CMS架构的工作方式。

你当然不需要三张桌子。我不知道为什么你认为3NF需要这个。 3NF的要点是属性不应该依赖于非键属性,它不会说你应该不必要地拆分成多个表。

因此,您可能只需要两个表之间的一对多关系:PostsRevisions。也就是说,对于每个帖子,可以有多个修订,但给定的修订仅适用于一个帖子。其他人提出了两种寻找当前职位的方法:

  • Revisions中的标记列,用于记录当前版本。更改当前版本非常简单,只需将标记更改为所需版本中的true,将false更改为以前的当前版本。

  • Posts中对于给定帖子当前修订的外键。这甚至更简单,因为您可以在一次更新而不是两次更新中更改当前版本。但循环外键引用可能会导致备份和问题。恢复,级联更新等

您甚至可以使用单个表格

来实施修订系统
CREATE TABLE PostRevisions (
  post_revision_id SERIAL PRIMARY KEY,
  post_id INT NOT NULL,
  is_current TINYINT NULL,
  date DATE,
  title VARCHAR(80) NOT NULL,
  text TEXT NOT NULL,
  UNIQUE KEY (post_id, is_current)
);

我不确定将title存储到每个修订版本是否重复,因为标题可以修改为与文本一样多,不是吗?

is_current应为1或NULL。唯一约束不计算NULL,因此您只能有is_current为1的行和无限数量的行。

这确实需要更新两行才能使修订版本成为当前版本,但通过将模型简化为单个表格可以获得一些简单性。当您使用ORM时,这是一个很大的优势。

您可以创建视图以简化查询当前帖子的常见情况:

CREATE VIEW Posts AS SELECT * FROM PostRevisions WHERE is_current = 1;

更新:更新您的更新问题:我同意正确的关系设计会鼓励两个表格,以便您可以为所有帖子的修订版本生成Post不变量的一些属性。但是大多数ORM工具都假设一个实体存在于一个表中,并且ORM在连接多个表中的行以构成给定实体时很笨拙。所以我想说如果使用ORM是一个优先级,你应该将帖子和修订存储在一个表中。牺牲一点关系正确性来支持ORM范式的假设。

另一个建议是考虑Dimensional Modeling。这是一个支持OLAP和数据仓库的数据库设计学院。它明智地使用非规范化,因此您通常可以在Star Schema中组织数据。主实体(“Fact Table”)由单个表表示,因此这将是以ORM为中心的应用程序设计的胜利。

答案 1 :(得分:0)

在这种情况下,你最好在Post表上放置一个CurrentTextID,以避免必须弄清楚哪个版本是最新的(另一个是修订版上的标志,但我认为帖子上的CurrentTextID将会给你更简单的查询。)

使用Post上的CurrentTextID,您的ORM应该在Post类上放置一个属性(CurrentText),这样您就可以使用您提供的语句来访问当前文本。

您的ORM还应该为您提供一些基于Post加载修订的方法;如果您想了解更多相关信息,那么您应该包含有关您正在使用的ORM以及如何配置它的信息。

答案 2 :(得分:0)

我认为两张桌子就足够了。邮政表及其修订版。如果您不担心重复数据,可以使用单个表(非规范化)。

答案 3 :(得分:0)

对于任何感兴趣的人,以下是wordpress如何使用单个MySQL帖子表处理修订。

CREATE TABLE IF NOT EXISTS `wp_posts` (
  `ID` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `post_author` bigint(20) unsigned NOT NULL DEFAULT '0',
  `post_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `post_date_gmt` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `post_content` longtext NOT NULL,
  `post_title` text NOT NULL,
  `post_excerpt` text NOT NULL,
  `post_status` varchar(20) NOT NULL DEFAULT 'publish',
  `comment_status` varchar(20) NOT NULL DEFAULT 'open',
  `ping_status` varchar(20) NOT NULL DEFAULT 'open',
  `post_password` varchar(20) NOT NULL DEFAULT '',
  `post_name` varchar(200) NOT NULL DEFAULT '',
  `to_ping` text NOT NULL,
  `pinged` text NOT NULL,
  `post_modified` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `post_modified_gmt` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `post_content_filtered` text NOT NULL,
  `post_parent` bigint(20) unsigned NOT NULL DEFAULT '0',
  `guid` varchar(255) NOT NULL DEFAULT '',
  `menu_order` int(11) NOT NULL DEFAULT '0',
  `post_type` varchar(20) NOT NULL DEFAULT 'post',
  `post_mime_type` varchar(100) NOT NULL DEFAULT '',
  `comment_count` bigint(20) NOT NULL DEFAULT '0',
  PRIMARY KEY (`ID`),
  KEY `post_name` (`post_name`),
  KEY `type_status_date` (`post_type`,`post_status`,`post_date`,`ID`),
  KEY `post_parent` (`post_parent`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 ;