Rails:通过多对多关系从另一个模型收集一个模型的数据的有效方法

时间:2014-05-26 02:26:14

标签: ruby-on-rails ruby acts-as-taggable-on

我有两个型号,书签和标签。标签由act-as-taggable-on gem实现,但我将在这里解释主要观点。

书签模型包含 url 字段。给定书签实例@bookmark,@ bookmart.tags返回其标签。不同的书签可以共享相同的标签(即标签中的许多部分)。

标签有name和taggings_count。 taggings_count字段存储标记标记的书签数量。在幕后,有一个标签表,但这没关系。

现在问题是,我想检索所有那些由具有特定网址值的书签标记的标记,结果应该按照标记某个标记的书签数量排序(该数字不相同)作为taggings_count字段,表示所有书签的标记计数,但此处需要特定网址的书签)。如何才能使生成的sql高效?

我知道我可以直接在sql中编写效率,但我也想知道Rails是否可以在不损害太多性能的情况下做同样的事情,因此我不必在我的Rails应用程序中注入sql代码

以下是表定义,在taggings表中,taggable_id充当Bookmark的外键,tag_id是Tag的外键:

CREATE TABLE `bookmarks` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) DEFAULT NULL,
  `url` varchar(255) DEFAULT NULL,
  `description` text,
  `private` tinyint(1) DEFAULT NULL,
  `read` tinyint(1) DEFAULT NULL,
  `list_id` int(11) DEFAULT NULL,
  `user_id` int(11) DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

CREATE TABLE `taggings` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `tag_id` int(11) DEFAULT NULL,
  `taggable_id` int(11) DEFAULT NULL,
  `taggable_type` varchar(255) DEFAULT NULL,
  `tagger_id` int(11) DEFAULT NULL,
  `tagger_type` varchar(255) DEFAULT NULL,
  `context` varchar(128) DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `taggings_idx` (`tag_id`,`taggable_id`,`taggable_type`,`context`,`tagger_id`,`tagger_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

CREATE TABLE `tags` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `taggings_count` int(11) DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `index_tags_on_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

2 个答案:

答案 0 :(得分:1)

这是一个解决方案:

bookmark_ids  = Bookmark.where(url: "http://foobar.com").pluck(:id)
taggings      = Tagging.where(taggable_id: bookmark_ids).where(taggable_type: "Bookmark")
tag_ids       = taggings.pluck(:tag_id).uniq
tags          = Tag.where(id: tag_ids).order(taggings_count: :desc)

理论上它可以使用ActiveRecord中的joins()方法编写,但我不知道您使用的宝石如何定义关联。
这可能会也可能不会起作用:

Tag.order(taggings_count: :desc)
   .joins(taggings: :bookmark)
   .where(bookmark: { url: "http://foobar.com" })

您也可以编写原始SQL,但在rails中感觉很脏。

答案 1 :(得分:0)

事实证明,acts-as-taggable-on实际上可以对关联进行操作。文档有样本,我只是没有注意到:

Bookmark.where(url: url).tag_counts_on(:tags).sort_by(&:taggings_count).reverse.map(&:name)

唯一的问题是,结果是按照这些标签的受欢迎程度排序,无论它们在哪种上下文中流行。但这并不重要。关键是这个rails语句只生成一个sql:

SELECT tags.*, taggings.tags_count AS count FROM "tags" JOIN (SELECT taggings.tag_id, COUNT(taggings.tag_id) AS tags_count FROM "taggings" INNER JOIN bookmarks ON bookmarks.id = taggings.taggable_id WHERE (taggings.taggable_type = 'Bookmark' AND taggings.context = 'tags') AND (taggings.taggable_id IN(SELECT bookmarks.id FROM "bookmarks"  WHERE "bookmarks"."url" = 'http://kindleren.com/forum.php?gid=49')) GROUP BY taggings.tag_id HAVING COUNT(taggings.tag_id) > 0) AS taggings ON taggings.tag_id = tags.id