如何在关系类中定义after_add方法呢?

时间:2017-02-24 20:37:03

标签: ruby-on-rails ruby ruby-on-rails-4 associations rails-activerecord

我有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类清洁的同时解决此问题的解决方案,并且最好保持关系在该关系类中的每个关系的逻辑。

2 个答案:

答案 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只实现了与外部类需要知道的一样多的方法,并且兄弟管理所需的所有逻辑都被整齐地隐藏起来。