2015-11-01 64 views
2

我有以下的数据库架构和模型,构成了自我指涉的关联,其中一个Phrase具有其他Phrase S的许多翻译,通过Translation协会:Rails的自我指涉协会加盟条件

模式

ActiveRecord::Schema.define(version: 20151003213732) do 

    enable_extension "plpgsql" 

    create_table "phrases", force: :cascade do |t| 
    t.integer "language" 
    t.string "text" 
    end 

    create_table "translations", force: :cascade do |t| 
    t.integer "source_id" 
    t.integer "destination_id" 
    end 
end 

短语

class Phrase < ActiveRecord::Base 

    has_many :translations, 
       class_name: "Translation", 
       foreign_key: "source_id" 
    has_many :destination_phrases, 
       through: :translations 
    has_many :inverse_translations, 
       class_name: "Translation", 
       foreign_key: "destination_id" 
    has_many :source_phrases, 
       through: :inverse_translations 

    enum language: [:eng, :spa, ...] 
end 

翻译

class Translation < ActiveRecord::Base 
    belongs_to :source_phrase, 
       class_name: "Phrase", 
       foreign_key: "source_id" 
    belongs_to :destination_phrase, 
       class_name: "Phrase", 
       foreign_key: "destination_id" 
end 

现在,我想基于语言和目的地语言运行查询。例如,我想查找英文短语及其各自的西班牙语翻译。目前,我正在根据来源查询短语,但之后我必须使用目标语言的select方法过滤出结果。我有如下所示:

@translations = Translation.includes(:source_phrase, :destination_phrase) 
          .where(phrases: {language: @source_language}) 

# Select only destination phrases where the language matches 
@translations = @translations.select { 
    |t| t.destination_phrase.language == @destination_language 
} 

我想消除select调用,因为这绝对应该在ActiveRecord的可能。 select将被替换为模型的where查询中的其他参数,但我无法弄清楚如何指定它。

它应该是这个样子:

@translations = 
    Translation.includes(:source_phrase, :destination_phrase) 
       .where([source_phrase: {language: @source_language}, 
         destination_phrase: {language: @destination_language}]) 

然而,ActiveRecord的认为(理所当然),该where子句中source_phrasedestination_phrase是表名。所以表名仍然是phrases,但是当我不能指定的连接条件时,两个连接,只是第一个。

如何在自我参照关联上指定两个单独的联接条件,它们都访问同一模型上的相同属性(Phrase模型上的language)?

+0

是否可以查询短语而不是翻译? (短语:“目的地短语”:{语言:“西班牙语”}})' –

+0

@LannyBose的确我可以查询短语模型,但不幸的是不是那种我正在寻找的反应 - 我最终需要的模型是一个“翻译”。在玩了大约两个小时后,并使用类似于您所建议的“加入”代码的东西后,我终于提出了解决方案。这也表明我的'短语'模型关联不正确(不奇怪)。我自我回答,如果你想看看! –

回答

3

我的Phrase模型原来定义不正确,这是第一个问题。我订正,像这样:

短语

class Phrase < ActiveRecord::Base 

    has_many :translations, 
       class_name: "Translation", 
       foreign_key: "source_id" 
    has_many :source_phrases,   # this changed 
       through: :translations 
    has_many :inverse_translations, 
       class_name: "Translation", 
       foreign_key: "destination_id" 
    has_many :destination_phrases,  # this changed 
       through: :inverse_translations 

    enum language: [:eng, :spa, ...] 
end 

翻译一直未变:

class Translation < ActiveRecord::Base 
    belongs_to :source_phrase, 
       class_name: "Phrase", 
       foreign_key: "source_id" 
    belongs_to :destination_phrase, 
       class_name: "Phrase", 
       foreign_key: "destination_id" 
end 

现在我能执行下述查询来得到正确的结果:

Translation 
    .includes(:source_phrase => :source_phrases, :destination_phrase => :destination_phrases) 
    .where(source_phrases_phrases: {language: 0}, destination_phrases_phrases: {language: 2}) 

Translation 
    .joins(:source_phrase => :source_phrases, :destination_phrase => :destination_phrases) 
    .where(source_phrases_phrases: {language: 0}, destination_phrases_phrases: {language: 2}) 
    .distinct 

答案是在指定(在本例中Phrasesource_phrasesdestination_phrases),其然后可以被用作where子句中不同的表名join编关联。

我要指出,我用source_phrases_phrasesdestination_phrases_phraseswhere子句中由于Rails似乎期望表名(phrases)被附加到协会(source_phrasesdestination_phrases)。它使一些丑陋的查询,但也许我能说出我在Phrase模型更好地组织...

它也似乎从EXPLAIN输出(太长的方式放在这里),该版本joins约25使用6,000个短语和12,000个翻译,速度更快。这些表越大,它的指数级越快。

经验教训?自我指涉性协会是一种痛苦。我当然希望这能帮助未来的人。