Rails HABTM查询 - 包含所有标签的文章

时间:2014-02-07 03:42:55

标签: sql ruby-on-rails ruby-on-rails-3 postgresql activerecord

我在我的应用程序中创建了两个表(Rails 3):

def change
  create_table :articles do |t|
    t.string :name
    t.text :content
    t.timestamps
  end

  create_table :tags do |t|
    t.string :name
    t.timestamps
  end

  create_table :articles_tags do |t|
    t.belongs_to :article
    t.belongs_to :tag
  end

  add_index :articles_tags, :article_id
  add_index :articles_tags, :tag_id
end

我希望能够以两种方式搜索基于标签的文章:

  1. 包含任何给定标签的文章(联合
  2. 包含所有给定标签的文章(交叉点
  3. 所以,换句话说,允许我这样做的事情:

    tag1 = Tag.create(name: 'tag1')
    tag2 = Tag.create(name: 'tag2')
    
    a = Article.create; a.tags << tag1
    b = Article.create; b.tags += [tag1, tag2]
    
    Article.tagged_with_any(['tag1', 'tag2'])
    # => [a,b]
    
    Article.tagged_with_all(['tag1', 'tag2'])
    # => [b]
    

    第一个相对容易。我刚刚在文章中提出了这个范围:

    scope :tagged_with_any, lambda { |tag_names|
      joins(:tags).where('tags.name IN (?)', tag_names)
    }
    

    问题是第二个问题。我不知道如何在ActiveRecord或SQL中执行此操作。

    我认为我可以做一些像这样的事情:

    scope :tagged_with_all, lambda { |tag_names|
      new_scope = self
    
      # Want to allow for single string query args
      Array(tag_names).each do |name|
        new_scope = new_scope.tagged_with_any(name)
      end
      new_scope
    }
    

    但是我认为这种效率很低,而且只是闻起来。有关如何正确执行此操作的任何想法?

1 个答案:

答案 0 :(得分:1)

正如你所说,这个范围是疯狂的低效(而且丑陋)。

尝试这样的事情:

def self.tagged_with_all(tags)
  joins(:tags).where('tags.name IN (?)', tags).group('article_id').having('count(*)=?', tags.count).select('article_id')
end

密钥位于having子句中。您可能还想查看表之间的SQL分区操作。