我有Character
班和Siblingship
班。
表示一个角色是"兄弟姐妹"另一方面,我保存了一个兄弟姐妹实例,其中character_id
("主要"正在编辑的字符)和sibling_id
(该字符被标记为主要角色的兄弟)。
我想添加自动创建带有反向ID的第二个兄弟姐妹实例的功能(所以如果我将Alice标记为Bob的兄弟姐妹,我也会将Bob标记为Alice' s兄弟)。
如果我将逻辑放在Character类中,这将是一个简单的after_add
:
class Character < ActiveRecord::Base
has_many :siblingships
has_many :siblings, through: :siblingships, after_add: :reciprocate
def reciprocate(sibling)
...
end
end
然而,这是一个更大的项目,有大约100种不同的关系(连接十几个不同的&#34;内容和#34;类,而不仅仅是字符),并存储after_add
(通常情况下,角色模型上的反转after_remove
)会变得非常笨拙。
所以我想存储&#34;当创建这种类型的关联时该怎么做&#34;在该关联上,而不是持有该关联的类。
类似的东西:
class Character < ActiveRecord::Base
has_many :siblingships
has_many :siblings, through: :siblingships, after_add: Siblingship.reciprocate
end
这样做有好办法吗?我还尝试将reciprocate
指定为实例方法,例如
has_many :siblings, through: :siblingships, after_add: Siblingship.new.reciprocate
但是这个方法不仅错误[错误的参数数量(给定0,预期1)],但是为了获得实例方法而在这里实例化Siblingships也感觉不对
我非常感谢任何有关如何在保持Character类清洁的同时解决此问题的解决方案,并且最好保持关系在该关系类中的每个关系的逻辑。
答案 0 :(得分:0)
看起来这可以通过加入模型上的简单挂钩来解决(例如Siblingship
),而不是尝试将它们添加到他们加入的相关类中(例如Character
)。
代码从我在问题中给出的具体示例中略微抽象出来,因为我需要一个解决方案,该解决方案可以在无限制的连接类中实现最少的代码重复。以下是我最终要做的事情,以实现双向链接(包括创建和删除):
class Character < ActiveRecord::Base
has_many :siblingships
has_many :siblings, through: :siblingships
end
然后,对于每个加入班级:
class Siblingship < ActiveRecord::Base
include SmartContentLinking
LINK_TYPE = :two_way
belongs_to :character
belongs_to :sibling, class_name: 'Character'
# Since this is a two-way relation, also create an opposite relation
after_create do
self.reciprocate relation: :siblingships, parent_object_ref: :character, added_object_ref: :sibling
end
# Since this is a two-way relation, also delete any opposite relation
after_destroy do
this_object = Character.find_by(id: self.character_id)
other_object = Character.find_by(id: self.sibling_id)
other_object.siblings.delete this_object
end
end
显然仍有一些事情可以让代码更清晰(并且完全被抽象到SmartContentLinking问题中),但现在这样做了。
SmartContentLinking问题:
require 'active_support/concern'
module SmartContentLinking
extend ActiveSupport::Concern
# Default linking to one-way. All possible values:
# - :one_way
# - :two_way
LINK_TYPE = :one_way
included do
def reciprocate relation:, parent_object_ref:, added_object_ref:
parent_object = self.send(parent_object_ref)
added_object = self.send(added_object_ref)
# if some_character.siblingships.pluck(:sibling_id).include?(parent_object.id)
if added_object.send(relation).pluck("#{added_object_ref}_id").include?(parent_object.id)
# Two-way relation already exists
else
# If a two-way relation doesn't already exist, create it
added_object.send(relation) << relation.to_s.singularize.camelize.constantize.create({
"#{parent_object_ref}": added_object, # character: sibling
"#{added_object_ref}": parent_object # sibling: character
})
end
end
end
end
答案 1 :(得分:0)
让我们忽略ActiveRecord,看看你想要实现的目标。
当角色添加了新兄弟时,兄弟姐妹就会存在 兄弟关系需要重新调整。
你提到这是一个约有100个相似关系的项目。因此,我们可以将上述内容概括为:
当形成新关系时,两者之间存在关系 双方需要重新调整。
我将使用第一个定义,因为我不知道你的其他域名。您可以根据对域的更好理解来更改此示例。
根据定义,代码可能类似于:
class Character
def add_sibling_relationship(another_character)
siblings.add(another_character)
another_character.realign_sibling_relationships(self)
end
def realign_sibling_relationships(sibling)
siblings.add(sibling)
end
end
一切都在Character
,一旦关系增长,这将成为一个问题。因此,让我们将该过程与ActiveRecord模型分离。
我们在这里谈论兄弟姐妹,所以我们称之为结果类:
class Siblinghood
def initialize(sibling_one, sibling_two)
@sibling_one, @sibling_two = sibling_one, sibling_two
end
def form
@sibling_one.siblings << @sibling_two unless @sibling_one.siblings.include?(@sibling_two)
@sibling_two.siblings << @sibling_one unless @sibling_two.siblings.include?(@sibling_one)
end
end
这改变了我们创建兄弟姐妹的方式。而不是
@character.siblings.add(@other_character)
我们现在需要致电:
Siblinghood.new(@character, @other_character).form
让我们更进一步。如果我们想要删除这种兄弟关系,那么合理的地方在哪里放置那个逻辑?
Siblinghood.new(@character, @other_character).destroy
现在,我们有一个地方可以巩固管理兄弟姐妹的逻辑。但是,这意味着我们的控制器和其他类需要知道Siblinghood
,这是不必要的 - 没有其他类关心如何形成兄弟姐妹。
让我们将调用移回Character
。
class Character
def add_sibling(other_character)
Siblinghood.new(self, other_character).form
end
def remove_sibling(other_character)
Siblinghood.new(self, other_character).destroy
end
end
这为我们提供了一个很好的平衡 - Character
只实现了与外部类需要知道的一样多的方法,并且兄弟管理所需的所有逻辑都被整齐地隐藏起来。