很难将整个问题简化为一个简短的标题。我有一个记录模型,每个记录has many :tags
和has 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作为我的数据库。
答案 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。