class Sentence < ApplicationRecord
belongs_to :speaker
end
class Speaker < ApplicationRecord
has_many :sentences
# Primary language
belongs_to :language
# Secondary languages
has_many :speaker_languages
has_many :secondary_languages, through: :speaker_languages, source: :languages
end
class Language < ApplicationRecord
has_many :letters
# As a speaker's primary language
has_many :speakers
# As a speaker's secondary language
has_many :speaker_languages
has_many :secondary_speakers, through: :speaker_languages, source: :speakers
end
class Letters < ApplicationRecord
belongs_to :language
end
如何在
Sentence
上定义一个关联(或至少是一个方法)来获取 Letter
的所有可能的 Sentence
?例如,属于句子说话者的主要语言或句子说话者的第二语言之一的语言的所有字母。
如果它不能是关联而必须是方法,那么该方法可以返回 ActiveRecord 关系(不调用任何数据库)吗?
我们可以在
Speaker
上定义一个实例方法来获取所有语言(主要和次要):
class Speaker < ApplicationRecord
...
def all_languages
secondary_languages.or(Language.where(id: language_id))
end
end
然后我们可以在
Sentence
上定义一个实例方法来获取所有语言的所有字母:
class Sentence < ApplicationRecord
...
def possible_letters
speaker.all_languages.map(&:letters).flatten.uniq
end
end
但这会导致
Language
中的每个 all_languages
调用数据库。它还会导致调用数据库来获取 speaker
(与 has_many :languages, through: :speaker
关联不同)。
可以用你现在拥有的关系来完成,但是有一种更简洁的方式来表达这些关系。在我看来,说话者的主要语言应该只是所有说话者语言的“特例”,因此请在
speaker_languages
表中指定一个标志。此外,一个句子似乎更正确地与发言者语言相关联。
我建议:
class Sentence < ApplicationRecord
belongs_to :speaker_language
end
class SpeakerLanguage
belongs_to :speaker
belongs_to :language
end
class Speaker < ApplicationRecord
has_many :speaker_languages
has_many :languages, through: :speaker_languages
def primary_language
joins(speaker_languages: :language).where(speaker_languages: {primary: true}).first
end
def secondary_languages
joins(speaker_languages: :language).where(speaker_languages: {primary: false})
end
end
class Language < ApplicationRecord
has_many :letters
has_many :speaker_languages
has_many :speakers, through: :speaker_languages
end
class Letters < ApplicationRecord
belongs_to :language
end
现在字母查询已简化。对于
Sentence
的例子,我们找到说话者所说的所有语言的所有字母。由于我们想要返回字母,因此我们查询 Letter
模型:
# letter.rb
def self.all_possible_letters_for_speaker_of_sentence(sentence_id)
joins(language: {speakers: :sentences}).where(sentence: {id: sentence_id}).distinct
end