ActiveRecord has_many通过依赖:destroy问题

时间:2016-06-03 19:00:10

标签: ruby-on-rails ruby activerecord

我有3个模型设置如下:

Class A
  has_many class_b dependent :destroy
  has_many class_c dependent :destroy
end

Class B
  belongs_to class_a
  has_many class_c through class_a conditions: proc {<conditions>} dependent :destroy
end

Class C
  belongs to class_a
end

conditions: only a subset of the class_c objects that belong to class_a also belong
to class_b. class_c has a column that is essentially class_b_id, so only those
instances will be deleted.

我试图销毁class_b的实例,但是我收到了这个错误:

ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection (Cannot modify 'association ClassB#class_c' because the source reflection class 'ClassC' is associated to 'ClassA' via :has_many.)

我该怎么做才能解决此错误?我是否必须重新修改我的协会?

2 个答案:

答案 0 :(得分:3)

老实说,您的模型配置非常直观。你有父母A级,可能有B&C和C&C。然后,由于某种原因,您希望删除任何B(对于单个A可能很多)将转到其父A并删除其所有C(该父A的)。然后,例如,同一父母A的另一个孩子B最终将通过什么都不做而失去它所有的C。

在这种情况下,C看起来不像是依赖,在结构上它们与B处于同一水平。如上所述,这可能会导致一些意想不到的事情 - 想象你并行和第二个B及其C一起工作,突然之间它们都因第一个B而消失了。

为类关联提供真实姓名和逻辑会更好地理解如何更好地设计关系 - 但它们肯定需要改变

<强>更新即可。内部(连接表)的has_one或has_many关联不能用于通过关联进行。所以你的class_A必须属于class_C(目标类),所以Class_B可以通过class_A来拥有它。

查看Rails sources通过连接实现,检查source_reflection - 这是中间(加入)类与目标(目标)类的连接。

  def ensure_mutable
      unless source_reflection.belongs_to? #interim connection must be belongs_to or it fails
        if reflection.has_one?
          raise HasOneThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
        else
          raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
        end
      end
    end

您还可以查看here

与此同时,您也无法通过 - see here进行所有权 - 请参阅接受的答案

更新2 - 反映Rails来源的长篇故事。

首先,ActiveRecord :: Base类(您继承了所有模型)包括关联模块(不是类!)here

include Associations

然后,通过使用Concerns关联模块将向Base类添加几个类方法。具体来说,它将has_many方法添加到您用于构建关联的模型中。此方法创建Builder :: HasMany类的实例,并传递self(在您的情况下是对B类的引用)和名称(即:class_c符号),以及所有支持选项,包括选项[:through]等于Class_A 。 看in sources

  def has_many(name, scope = nil, options = {}, &extension)
    reflection = Builder::HasMany.build(self, name, scope, options, &extension)
    Reflection.add_reflection self, name, reflection
  end

从技术上讲,HasMany是CollectionAssociation类之上的一个小类,它按顺序放在Association类上,所有这些都在Builder :: name-scope中(注意它现在所有的类,不同于父Association模块)。 HasMany

module ActiveRecord::Associations::Builder # :nodoc:
 class HasMany < CollectionAssociation #:nodoc:
    def self.macro
      :has_many
    end

CollectionAssociation

的继承
module ActiveRecord::Associations::Builder # :nodoc:
  class CollectionAssociation < Association #:nodoc:

现在,Association类具有上面用于在上面的has_many方法调用中创建has_many反射的构建方法。我们来看sources。请注意,它使用create方法创建了一个Reflection:

module ActiveRecord::Associations::Builder # :nodoc:
  class Association #:nodoc:
   def self.build(model, name, scope, options, &block)
      ..     
      reflection = create_reflection model, name, scope, options, extension
      ..
      reflection
   end
   def self.create_reflection(model, name, scope, options, extension = nil)
      ..    
      ActiveRecord::Reflection.create(macro, name, scope, options, model)
   end
   ...

注意,在你的情况下传递以下参数:宏将是&#34;:has_many&#34;符号,name(class_c)将是第一个参数,model是self(B类)。它还有你的选项[:through] = class_a inside options。

现在让我们看一下使用的Reflection模块创建方法。顺便说一句,Reflection模块也包含在ActiveRecord :: Base类中,但是通过命名空间引用以避免命名混淆.create调用。此方法将创建一个特定类型的Reflecton子类。 sources

module ActiveRecord
  # = Active Record Reflection
  module Reflection # :nodoc:
    extend ActiveSupport::Concern

    def self.create(macro, name, scope, options, ar)
      klass = case macro
              when :composed_of
                AggregateReflection
              when :has_many
                HasManyReflection
              when :has_one
                HasOneReflection
              when :belongs_to
                BelongsToReflection
              else
                raise "Unsupported Macro: #{macro}"
              end

      reflection = klass.new(name, scope, options, ar)
      options[:through] ? ThroughReflection.new(reflection) : reflection
    end

在您的情况下,它将创建ThroughReflection.new(因为存在options [:through])具有内部HasManyReflection实例的类实例(因为:has_many值为macro)。内部HasManyReflection将包含其中的所有原始参数 - 类c的名称,而ar(似乎表示active_record)是最初设置为self(类b)的模型,也是一个through选项。

在同一反射模块here内也定义了ThroughReflection类。

 # Holds all the meta-data about a :through association as it was specified
 # in the Active Record class.
 class ThroughReflection < AbstractReflection #:nodoc:

它具有source_reflection方法的实现,该方法将在ensure_mutable调用中使用(第736行)。

  # Returns the source of the through reflection. It checks both a singularized
  # and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
  #
  #   class Post < ActiveRecord::Base
  #     has_many :taggings
  #     has_many :tags, through: :taggings
  #   end
  #
  #   class Tagging < ActiveRecord::Base
  #     belongs_to :post
  #     belongs_to :tag
  #   end
  #
  #   tags_reflection = Post.reflect_on_association(:tags)
  #   tags_reflection.source_reflection
  #   # => <ActiveRecord::Reflection::BelongsToReflection: @name=:tag, @active_record=Tagging, @plural_name="tags">
  #
  def source_reflection
    through_reflection.klass._reflect_on_association(source_reflection_name)
  end

在您的情况下,source_reflection调用将返回HasManyReflection类型的值作为类C的Class A has_many。查看提供的注释以了解方法以了解它的作用。

另请参阅下一行的through_reflection定义 - 它会检查您用于获取所需类的关联 - 它是从class_b到class_a的关联(class_b属于class_a)。然后它将检查从临时类(class_a)到上面source_reflection中的最终类(class_c)的关联。

  # Returns the AssociationReflection object specified in the <tt>:through</tt> option
  # of a HasManyThrough or HasOneThrough association.
  #
  #   class Post < ActiveRecord::Base
  #     has_many :taggings
  #     has_many :tags, through: :taggings
  #   end
  #
  #   tags_reflection = Post.reflect_on_association(:tags)
  #   tags_reflection.through_reflection
  #   # => <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @active_record=Post, @plural_name="taggings">
  #
  def through_reflection
    active_record._reflect_on_association(options[:through])
  end

- 的最后 -

ThroughReflection的内部HasManyReflection实例将在association_class方法中返回HasManyThroughAssociation类 - here

class HasManyReflection < AssociationReflection # :nodoc:
      def association_class
        if options[:through]
          Associations::HasManyThroughAssociation
        else
          Associations::HasManyAssociation
        end
      end
    end

返回的association_class名称在Associations模块中使用(同样,不是类),如上所述,它包含在ActiveRecord :: Base中。它用于创建关联(并将其保存到您的class_b模型) - 在您的情况下将是HasManyThroughAssociation类。 HasManyThroughAssociation实例也将在其中传递的ThroughReflection以new形式传递(参见反射参数)。 sources

def association(name) #:nodoc:
   if association.nil? # if was not created before
     unless reflection = self.class._reflect_on_association(name)
        raise AssociationNotFoundError.new(self, name)
     end
     association = reflection.association_class.new(self, reflection)
     association_instance_set(name, association)
   end
   association
 end

_reflect_on_association(name)这里从本地&#34;存储&#34;加载先前创建的反射(of ThroughReflection)。模型(class_b)的名称(在您的情况下为class_a)。所以对于&#34; class_a&#34; name参数,当你编写has_many:class_a。

时,它具有ThroughReflection

构造函数调用here

class HasManyThroughAssociation < HasManyAssociation #:nodoc:
      include ThroughAssociation

      def initialize(owner, reflection)
        super

        @through_records     = {}
        @through_association = nil
      end

以防万一,这是最终将被超级here调用的构造函数调用:

 class Association #:nodoc:       
      delegate :options, :to => :reflection
      def initialize(owner, reflection)
        @owner, @reflection = owner, reflection
       ...

因此它保存了传递的反射(这是通过反射)。

现在,让我们看一下已创建的HasManyThroughAssociation类,其内部包含ThroughReflection - sources。 它实际上包括ThroughAssociation模块的可重用部分:

include ThroughAssociation

它还将source_reflection和through_reflection调用委托给它保存的反射变量 - here。如你所知,反射将是ThroughReflection类。

  delegate :source_reflection, :through_reflection, :to => :reflection

Included ThroughAssociation,在其情况下,具有引发异常的ensure_mutable调用 - here

 def ensure_mutable
      unless source_reflection.belongs_to?
        if reflection.has_one?
          raise HasOneThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
        else
          raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
        end
      end
  end

source_reflection调用被委托给保存的反射,即ThroughReflection 如上所述(在ThroughReflection中查找上面的source_reflection方法定义),您的ThroughReflection的source_reflection将是has_many类型,因为Class A has_many属于C类。因此它不属于belongs_to(临时类a不属于目标类c)。

代码中的一般评论:

 # Construct attributes for :through pointing to owner and associate. This is used by the
 # methods which create and delete records on the association.
 #
 # We only support indirectly modifying through associations which have a belongs_to source.
 # This is the "has_many :tags, through: :taggings" situation, where the join model
 # typically has a belongs_to on both side. In other words, associations which could also
 # be represented as has_and_belongs_to_many associations.
 #
 # We do not support creating/deleting records on the association where the source has
 # some other type, because this opens up a whole can of worms, and in basically any
 # situation it is more natural for the user to just create or modify their join records
 # directly as required.

答案 1 :(得分:1)

我不想更改代码库中的基本关联,所以我最终要做的就是从nc -zv my_nat_ip 2222 dependent: destroy的关联中删除class_b只需在class_c

before_destroy内撰写自定义查询