我正在使用以下模型开发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
我要做的是验证用户是否始终拥有可验证的父级。这本身很好用,但在我的表单中,用户模型抱怨不存在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和类型。
我在这里做错了什么?
答案 0 :(得分:7)
我相信这是预期的:
https://github.com/rails/rails/issues/1629#issuecomment-11033182(最后两条评论)。
同时从rails api
中查看一对一协会
将对象分配给has_one关联会自动保存该对象 对象和被替换的对象(如果有的话),以便 更新其外键 - 除非父对象未保存 (new_record?== true)。
如果其中任何一个保存失败(由于其中一个对象存在) 无效),引发了一个ActiveRecord :: RecordNotSaved异常 作业被取消。
如果您希望在没有的情况下将对象分配给has_one关联 保存它,使用build_association方法(下面记录)。该 被替换的对象仍将被保存以更新其外键。
将对象分配给belongs_to关联不会保存 object,因为外键字段属于父键。它不是 保存父母。
和这个
build_association(attributes = {})返回一个新对象 已使用属性实例化并链接的关联类型 通过外键到达此对象,但尚未保存。
您必须先创建一个Parent。然后将它的id分配给多态对象。
从我所看到的,你创建了一个对象Physician.new,它构建了User,但此时它还没有保存,因此它没有id,所以没有任何东西要分配给多态对象。因此,验证将始终失败,因为它在保存之前被调用。
换句话说:在你调用build_user的情况下,它会返回User.new NOT User.create。因此,authenticatable并没有分配authenticatable_id。
您有几种选择:
首先保存关联用户。
或者
将验证移至after_save回调(可能但非常烦人且不好)
或者
更改您的应用结构 - 也许避免多态关联并切换到has_many通过?由于我不了解内部和业务要求,因此很难判断。但在我看来,这不是多态关联的良好候选者。你会有更多的模型,而不仅仅是可验证的用户吗?
恕我直言,多态关联的最佳候选者是电话,地址等等。地址可以属于用户,客户,公司,组织,Area51等,是家庭,航运或计费类别,即它可以 MORPH 以适应多种用途,因此它是一个很好的提取对象。但Authenticatable在我看来有点做作,并且在不需要的地方增加了复杂性。我没有看到任何其他需要可验证的对象。
如果您可以展示您的Authenticatable模型以及您的推理和迁移(?),我可以为您提供更多建议。现在我只是凭空捏出这个:-)但它似乎是重构的好选择。
答案 1 :(得分: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
答案 2 :(得分:0)
在创建动作中,我必须手动分配:
@physician = Physician.new(params[:physician])
@physician.user.authenticatable = @physician
我的问题有点不同(has_many和不同的验证),但我认为这应该有效。
答案 3 :(得分: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
关联,可以使用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的最后评论是正确的 - 你的例子看起来不像多态性的好候选人。
答案 4 :(得分:-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
}