validation多态父级的存在

我正在使用以下模型开发Rails 3.2应用程序:

class User < ActiveRecord::Base # Associations belongs_to :authenticatable, polymorphic: true # Validations validates :authenticatable, presence: true # this is the critical line end class Physician < ActiveRecord::Base attr_accessible :user_attributes # Associations has_one :user, as: :authenticatable accepts_nested_attributes_for :user end 

我要做的是validation用户是否始终拥有可validation的父级。 这本身工作正常,但在我的forms中,用户模型抱怨不存在authenticatable。

我正在使用以下控制器为新医生显示一个表单,该表单接受用户的嵌套属性:

 def new @physician = Physician.new @physician.build_user respond_to do |format| format.html # new.html.erb format.json { render json: @physician } end end 

这是我的创建方法:

 def create @physician = Physician.new(params[:physician]) respond_to do |format| if @physician.save format.html { redirect_to @physician, notice: 'Physician was successfully created.' } format.json { render json: @physician, status: :created, location: @physician } else format.html { render action: "new" } format.json { render json: @physician.errors, status: :unprocessable_entity } end end end 

在提交表单时,它表示用户的authenticable不能为空。 但是,一旦保存@physician ,就应该分配authenticatable_id和authenticatable_type。 如果我使用相同的表单来编辑医生及其用户,它可以正常工作,从那时起就分配了id和类型。

我在这做错了什么?

我相信这是预期的:

https://github.com/rails/rails/issues/1629#issuecomment-11033182 (最后两条评论)。

还可以从rails api中查看:

一对一协会

将对象分配给has_one关联会自动保存该对象和要替换的对象(如果有),以便更新其外键 – 除非父对象未保存(new_record?== true)。

如果其中任何一个保存失败(由于其中一个对象无效),则会引发ActiveRecord :: RecordNotSavedexception并取消分配。

如果您希望将对象分配给has_one关联而不保存它,请使用build_association方法(如下所示)。 仍将保存要替换的对象以更新其外键。

将对象分配给belongs_to关联不会保存对象,因为外键字段属于父对象。 它也没有保存父母。

还有这个

build_association(attributes = {})返回已使用属性实例化并通过外键链接到此对象但尚未保存的关联类型的新对象。

你必须先创建一个Parent。 然后将它的id分配给多态对象。

从我所看到的,你创建一个对象Physician.new,它构建用户,但此时它还没有保存,所以它没有id,因此没有任何内容可以分配给多态对象。 因此,validation将始终失败,因为它在保存之前被调用。

换句话说:在您调用build_user的情况下,它返回User.new NOT User.create。 因此,authenticatable没有分配authenticatable_id。

你有几个选择:

  • 首先保存关联用户。

    要么

  • 将validation移至after_save回调(可能但非常烦人和坏)

    要么

  • 更改您的应用程序结构 – 可能避免多态关联并切换到has_many通过? 由于我不了解内部和业务要求,因此很难判断。 但在我看来,这不是多态关联的好选择。 你会有更多的模型而不仅仅是可validation的用户吗?

恕我直言,多态关联的最佳候选者是电话,地址等。地址可以属于用户,客户,公司,组织,Area51等,是家庭,航运或计费类别即它可以MORPH以适应多种用途,所以它是一个提取的好对象。 但Authenticatable在我看来有点做作,并且在没有必要的地方增加了复杂性。 我没有看到任何其他对象需要可编辑。

如果您可以展示您的Authenticatable模型以及您的推理和迁移(?),我可以为您提供更多建议。 现在我只是凭空捏出这个:-)但它似乎是重构的好候选人。

您可以将validation移动到before_save回调,它将正常工作:

 class User < ActiveRecord::Base # Associations belongs_to :authenticatable, polymorphic: true # Validations before_save :check_authenticatable def check_authenticatable unless authenticatable errors[:customizable] << "can't be blank" false end end end 

在创建操作中,我必须手动分配它:

 @physician = Physician.new(params[:physician]) @physician.user.authenticatable = @physician 

我的问题有点不同(has_many和不同的validation),但我认为这应该有效。

我能够通过覆盖嵌套的属性setter来实现这一点。

 class Physician has_one :user, as: :authenticatable accepts_nested_attributes_for :user def user_attributes=(attribute_set) super(attribute_set.merge(authenticatable: self)) end end 

为了干它,我把多态代码移到了一个问题:

 module Authenticatable extend ActiveSupport::Concern included do has_one :user, as: :authenticatable accepts_nested_attributes_for :user def user_attributes=(attribute_set) super(attribute_set.merge(authenticatable: self)) end end end class Physician include Authenticatable ... end 

对于has_many关联,可以使用map完成相同的操作:

 class Physician has_many :users, as: :authenticatable accepts_nested_attributes_for :users def users_attributes=(attribute_sets) super( attribute_sets.map do |attribute_set| attribute_set.merge(authenticatable: self) end ) end end class User belongs_to :authenticatable, polymorphic: true validates :authenticatable, presence: true end 

所有这一切,我认为konung的最后评论是正确的 – 你的例子看起来不像多态性的好候选人。

我不确定这是否能解决您的问题,但在validation多态父级存在时,我会使用类似的东西。

以下是我在video模型中使用的一些代码,其中parent作为多态关联。 这进入了video.rb

  validates_presence_of :parent_id, :unless => Proc.new { |p| # if it's a new record and parent is nil and addressable_type is set # then try to find the parent object in the ObjectSpace # if the parent object exists, then we're valid; # if not, let validates_presence_of do it's thing # Based on http://www.rebeccamiller-webster.com/2011/09/validate-polymorphic/ if (new_record? && !parent && parent_type) parent = nil ObjectSpace.each_object(parent_type.constantize) do |o| parent = o if o.videos.include?(p) unless parent end end parent }