我有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.)
我该怎么做才能解决此错误?我是否必须重新修改我的协会?
答案 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
的继承
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
内撰写自定义查询