根据一个关联查询一个collection,然后return整个关联

Query a collection based on an association, and then return the whole association

很难将整个问题压缩成一个简短的标题。我有一个 Records 模型,每条记录 has many :tags,和 has many :words, through: :tags。此外,单词 table 有一个 string 列,用于保存单词的字符串形式。

我正在尝试构建一个搜索查询,以便用户可以搜索包含特定词的记录并查看每条返回记录包含的所有词。但是,到目前为止,当我有查询记录时,只包含我查询的单词。我需要显示每条记录的所有单词(甚至 un-searched 个单词)。但我无法弄清楚这一点,并且已经花了一两个星期阅读 SO 问题和 Rails 文档。我试过使用普通的 rails ActiveRecord 东西,我试过使用 Arel tables。我有一个可行的解决方案,但这涉及到构建一个包含所有已找到记录的采摘 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

这样我就可以得到第一条记录的所有单词,包括不是 'chick' 或 'fil' 或 'a' 的单词,而不必使用 [=21= 强制重新加载]?

谢谢。

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

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。

您可以使用 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 该查询应该 select 记录与任意搜索数组匹配的单词。它通过 selecting 匹配数组中字符串的 any 的记录,然后按记录的 id 分组,selecting 只有那些有数字的记录匹配字符串的数量等于查询的字符串数。

然后您可以将它与 includes(:words) 链接起来以获得 所有 相关的单词,因为上面的查询使用别名 mwords:

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

相关地,虽然以上所有内容都应该在 SQLite 中工作,但我强烈建议您切换到更强大且可用于生产的 SQL 数据库,例如 MySQL 或PostgreSQL.