上下文
在Ruby on Rails应用程序的上下文中,在学校的项目中。
让我们考虑一个基于团队的游戏的背景,有许多角色可供选择。我想在不同的语境中表示两个角色之间的亲密关系,这意味着两个角色是组合在一起还是彼此面对,或者即使一个角色存在于游戏中而另一个角色缺失。
然后我会在我的数据库中看到类似这样的表
这些<name>-Relation
表中的每一个都表示字符之间的多对多关系,并且附加分数代表关系的强度
当然,角色之间的关系可能会发生变化。我们可能会出于任何原因决定一个关系变得无关紧要,或者我们在刚出现之前没有想到的其他关系。
在显示方面,我们希望查找特定关系中的最佳和最差其他字符。
问题
我想出了类似的东西。
class Relation < ActiveRecord::Base
scope :best, ->(character_id) {
Character.find(where(character_left: character_id).order("score desc").limit(5).pluck(:character_right))
}
end
其中character_left
和character_right
是关系中要考虑的两个字符,分数是债券的强项。
但是,在获取数据时,我的老师认为最好在Characters模型中使用范围来查找特定关系中的最佳和最差其他字符。这是因为,当他想要显示字符时,正在做HTML代码的队友不会对关系的结构发表任何看法。他告诉我有关使用has_and_belongs_to_many
的信息,他向我绘制了一些他希望看起来像Character.best(:relation)
的代码来获取数据。
虽然我认为我所做的更好(显然:))。具有将从Relation模型中获取字符的范围,因为它们会出现和消失,从而保持请求关系细节。这使我们无法在每次遇到关系时修改角色模型。
看起来像Relation.best(:hero)
的某些东西对我来说似乎更清晰。
你怎么看?围绕这种非常具体的情况有什么好的做法。有没有正确的方法在Ruby on Rails应用程序中应用和使用模块化的多对多关系?
答案 0 :(得分:1)
您使用score
列在正确的轨道上并使用它来订购关系。但是,您需要考虑一个字符可以位于连接模型的任一列中。
class Character
has_many :relationships_as_left, foreign_key: 'left_id'
has_many :relationships_as_right, foreign_key: 'right_id'
end
# renamed to not get it mixed up with ActiveRecord::Relation
class Relationship
belongs_to :left, class_name: 'Character'
belongs_to :right, class_name: 'Character'
end
您希望确保设置唯一索引和正确的外键:
class CreateRelationships < ActiveRecord::Migration
def change
create_table :relationships do |t|
t.references :left, index: true, foreign_key: false
t.references :right, index: true, foreign_key: false
t.integer :score, index: true
t.timestamps null: false
end
add_foreign_key :relationships, :characters, column: :left_id
add_foreign_key :relationships, :characters, column: :right_id
add_index :relationships, [:left_id, :right_id], unique: true
end
end
查询此表有点棘手,因为可以在relationships.left_id
或relationships.right_id
中引用字符。
class Relationship < ActiveRecord::Base
belongs_to :left, class_name: 'Character'
belongs_to :right, class_name: 'Character'
def self.by_character(c)
sql = "relationships.left_id = :id OR relationships.right_id = :id"
where( sql, id: c.id )
end
def self.between(c1, c2)
where(left_id: [c1,c2]).merge(where(right_id: [c1,c2]))
end
def other_character(c)
raise ArgumentError unless c == left || c == right
c == left ? right : left
end
end
between
方法需要一些解释:
where(left_id: [c1,c2]).merge(where(right_id: [c1,c2]))
这将生成以下查询:
SELECT
"relationships".* FROM "relationships"
WHERE
"relationships"."left_id" IN (1, 2)
AND
"relationships"."right_id" IN (1, 2)
你和你的教授都是错的 - Character
上的范围不起作用,因为范围是类级别,你想要的是检查实例上的关系。
class Character
def worst_enemies(limit = 10)
relations = Relationship.joins(:left, :right)
.by_character(self)
.order('relationship.score ASC')
.limit(limit)
relations.map do |r|
r.other_character(self)
end
end
end
你可以用子查询更优雅地做到这一点。