14

我正在使用以下模型开发 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

我想要做的是验证用户是否总是有一个可验证的父级。这本身就可以正常工作,但是在我的表单中,用户模型抱怨不存在可验证的内容。

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

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_id 和authenticable_type 应该在@physician保存后立即分配。如果我使用相同的表单来编辑医生及其用户,它工作得很好,因为那时分配了 id 和 type。

我在这里做错了什么?

4

5 回答 5

8

我相信这是意料之中的:

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

还要从rails api检查一下:

一对一关联

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

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

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

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

和这个

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

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

从我所见,您创建了一个构建 User 的对象 Physician.new,但此时它尚未保存,因此它没有 id,因此没有任何内容可分配给多态对象。所以验证总是会失败,因为它是在保存之前调用的。

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

你有几个选择:

  • 首先保存关联用户。

    或者

  • 将验证移至 after_save 回调(可能但非常烦人且不好)

    或者

  • 改变你的应用程序结构 - 也许避免多态关联并通过切换到 has_many?我很难判断,因为我不了解内部和业务需求。但在我看来,这不是多态关联的好候选。除了用户之外,您还会拥有更多可验证的模型吗?

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

如果你能展示你的 Authenticatable 模型和你的推理,也许还有迁移(?)我可以给你更多建议。现在我只是凭空提出来 :-) 但它似乎是重构的好候选。

于 2013-06-20T15:23:57.383 回答
3

您可以将验证移至 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
于 2014-02-17T13:25:12.057 回答
0

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

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

我的问题有点不同(has_many 和不同的验证),但我认为这应该可行。

于 2013-11-15T03:59:30.133 回答
0

通过覆盖嵌套的属性设置器,我能够让它工作。

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关联,同样可以使用 a 来完成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 的最后一条评论是正确的——你的例子看起来不像是多态性的好候选者。

于 2018-02-02T23:47:36.803 回答
-1

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

这是我在具有多态关联的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
    }
于 2013-01-28T06:57:57.717 回答