Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Validate presence of polymorphic parent

I am developing a Rails 3.2 application with the following models:

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

What I am trying to do is validate whether a user always has an authenticatable parent. This works fine in itself, but in my form the user model complains that the authenticatable is not present.

I am using the following controller to show a form for a new physician which accepts nested attributes for the user:

def new
  @physician = Physician.new
  @physician.build_user

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

And this is my create method:

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

On submitting the form, it says that the user's authenticatable must not be empty. However, the authenticatable_id and authenticatable_type should be assigned as soon as @physician is saved. It works fine if I use the same form to edit a physician and its user, since then the id and type are assigned.

What am I doing wrong here?

like image 646
weltschmerz Avatar asked Jan 27 '13 23:01

weltschmerz


2 Answers

I believe this is expected:

https://github.com/rails/rails/issues/1629#issuecomment-11033182 ( last two comments).

Also check this out from rails api:

One-to-one associations

Assigning an object to a has_one association automatically saves that object and the object being replaced (if there is one), in order to update their foreign keys - except if the parent object is unsaved (new_record? == true).

If either of these saves fail (due to one of the objects being invalid), an ActiveRecord::RecordNotSaved exception is raised and the assignment is cancelled.

If you wish to assign an object to a has_one association without saving it, use the build_association method (documented below). The object being replaced will still be saved to update its foreign key.

Assigning an object to a belongs_to association does not save the object, since the foreign key field belongs on the parent. It does not save the parent either.

and this

build_association(attributes = {}) Returns a new object of the associated type that has been instantiated with attributes and linked to this object through a foreign key, but has not yet been saved.

You have to create a Parent first. Then assign it's id to polymorphic object.

From what I can see, you create an object Physician.new which builds User but at this point it's not saved yet, so it doesn't have an id, so there is nothing to assign to polymorphic object. So validation will always fail since it's called before save.

In other words: In your case when you call build_user, it returns User.new NOT User.create . Therefore authenticatable doesn't have a authenticatable_id assigned.

You have several options:

  • Save associated user first.

    OR

  • Move validation in to after_save callback ( Possible but very annoying and bad)

    OR

  • Change your app structure - maybe avoid polymorphic association and switch to has_many through? Hard for me to judge since I don't know internals and business requirements. But it seems to me this is not a good candidate for polymorphic association. Will you have more models than just User that will be authenticatable?

IMHO the best candidates for polymorphic associations are things like Phones, Addresses, etc. Address can belong to User, Customer, Company, Organization, Area51 etc, be Home, Shipping or Billing category i.e. It can MORPH to accommodate multiple uses, so it's a good object to extract. But Authenticatable seems to me a bit contrived and adds complexity where there is no need for it. I don't see any other object needing to be authenticable.

If you could present your Authenticatable model and your reasoning and maybe migrations (?) I could advise you more. Right now I'm just pulling this out of thin air :-) But it seems like a good candidate for refactoring.

like image 91
konung Avatar answered Sep 18 '22 18:09

konung


You can just move validation to before_save callback and it will work fine:

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
like image 45
Drakula2k Avatar answered Sep 22 '22 18:09

Drakula2k