根据关联查询集合,然后返回整个关联

时间:2015-08-07 20:28:32

标签: ruby-on-rails ruby sqlite activerecord arel

很难将整个问题简化为一个简短的标题。我有一个记录模型,每个记录has many :tagshas many :words, through: :tags。此外,Word表格有一个string列,其中包含单词的字符串形式。

我尝试构建搜索查询,以便用户可以搜索包含特定单词的记录,并查看每条返回记录的所有单词。但是,到目前为止,当我查询记录时,只包括我查询的单词。我需要为每条记录显示所有单词(甚至是未搜索的单词)。但我无法解决这个问题,而且阅读SO问题和Rails文档需要一到两周的时间。我尝试过使用普通的rails ActiveRecord,我尝试使用Arel表。我有一个有效的解决方案,但是这需要构建一系列所有找到的唱片的弹拨ID,并再次找到它们,这只会给我的口味带来不好的味道。无论如何,这就是我所拥有的:

record.rb

class Record < ActiveRecord::Base
  has_many :tags, dependent: :destroy
  has_many :words, through: :tags

  # this is the kind of query that I want
  # +search+ is an array of words, ex: %w{chick fil a}
  # however, `scope.first.words.count != scope.first.words(true).count`
  # that is, the first record's words are not all force reloaded automatically
  def self.that_have_all_words_in_rails(search)
    scope = includes(:words)
    search.each do |search_word|
      scope = scope.where(words: { string: search_word })
    end
    scope
  end

  # I also tried this in arel, but it seems to give the same result as above
  def self.that_have_all_words_in_arel(search)
    scope = joins(:words)
    search.each do |search_word|
      scope = scope.where(word_table[:string].eq(search_word))
    end
    scope.includes(:words)
  end

  def word_table
    Record.arel_table
  end

  # This is the only working version that I have, but it seems
  # sloppy to use the record_ids array like this.
  # That could be a 1000+ element array!
  def self.that_have_all_words_in(search)
    records = includes(:words)
    record_ids = search.inject(pluck(:id)) do |ids, search_word|
      ids & records.where(words: { string: search_word }).pluck(:id)
    end
    where(id: record_ids)
  end

word.rb

class Word < ActiveRecord::Base
  belongs_to :category
  has_many :tags, dependent: :destroy
  has_many :records, through: :tags
end

所以,关于如何执行查询的任何想法,如:

Record.that_have_all_words_in(['chick', 'fil', 'a']).first.words

这样我就能得到第一条记录中的所有单词,包括不是小鸡的单词。或者&#39; fil&#39;或者&#39; a&#39;,而不必强制重新加载first.words(true)

感谢。

更新:我的架构的相关部分

ActiveRecord::Schema.define(version: 20150727041434) do
  create_table "records", force: true do |t|
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  create_table "tags", force: true do |t|
    t.integer  "word_id"
    t.integer  "record_id"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  create_table "words", force: true do |t|
    t.string   "string"
    t.datetime "created_at"
    t.datetime "updated_at"
  end
end

另外,我使用sqlite3作为我的数据库。

1 个答案:

答案 0 :(得分:1)

您可以使用aliased表编写自定义联接以匹配搜索条件:

def self.where_tagged_words_match_strings(search)
  search.uniq!
  joins("INNER JOIN tags mtags ON mtags.record_id = records.id INNER JOIN words mwords ON mwords.id = mtags.word_id")
    .where("mwords.string IN (?)", search).group("records.id").having("COUNT(DISTINCT mwords.string) = #{search.length}")
end

ETA 此查询应选择包含与任意搜索数组匹配的字词的记录。它通过选择与单词中的任何字符串相匹配的记录来完成此操作,然后按记录的id进行分组,只选择那些匹配字符串数等于查询字符串数的字符串。

然后,您可以使用includes(:words)将其链接到关联字词的所有,因为上面的查询使用别名mwords

Record.where_tagged_words_match_strings(search).includes(:words)

相关地说,虽然上述所有内容都适用于SQLite,但我强烈建议您切换到功能更强大,生产就绪的SQL数据库,例如MySQL或PostgreSQL。

相关问题