1

Banging my head on this problem for the past 2 hours. I have these two classes which are polymorphically linked. Doctors have doctor-y stuff in them and Users handle the web component (email addresses, devise, etc). A Doctor is a User.

The reason is there are different User classes (Nurses, MedicalInstitutions), all requiring their own fields, but are linked together by the fact that they are users. So I chose polymorphic association in this case.

The problem is I cannot seem to instantiate both of them because both of them doesn't get saved. Assume an empty database how exactly do I create both of them because they need each other? Is there a way to "save both" at the same time.

class User < ActiveRecord::Base
  rolify
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

  validates_presence_of :profile

  belongs_to :profile, polymorphic: true
end

class Doctor < ActiveRecord::Base
  has_one :user, as: :profile, dependent: :destroy
  accepts_nested_attributes_for :user

  validates_presence_of :user

  # doctor-specific code here

  delegate :email, :password, to: :user
end

class Nurse < ActiveRecord::Base
  # has_one, accepts... same as doctor. nurse-specific code here
end

I'd also appreciate it if someone can solve how to instnantiate this in FactoryGirl.

Aside: I've seen this, but they don't have validations on both sides.

Community
  • 1
  • 1
Daryll Santos
  • 2,031
  • 3
  • 22
  • 40
  • Not entirely clear of your setup here - going to try to ask some q's to clarify. So a Doctor is a User - so is a Nurse or a MedicalInstitution similar to a Doctor then, in terms of its code? That's what you mean by different user classes? – Richard Jordan Feb 27 '14 at 05:04
  • @RichardJordan thanks for answering. Yes, they are sort of the same. They have their own class-specific fields. – Daryll Santos Feb 27 '14 at 05:09
  • @DaryllSantos so basically everyone is a User. Only different is that they have different profiles(doctor/nurse/etc)? – Prasad Surase Feb 27 '14 at 05:17
  • @prasad.surase exactly. I just can't get the saving to work because the user requires the profile and vice-versa. I figured this would make sense for the integrity (don't want to have random users right now with no profiles, and don't want to have profiles with no email addresses). – Daryll Santos Feb 27 '14 at 05:24
  • @DaryllSantos m adding answer. the solution i implemented is a bit lengthy. Adding it in below. – Prasad Surase Feb 27 '14 at 05:37

2 Answers2

0

You can just skip the validation for your first record:

your_record.save(validate: false)

EDIT:

Actually, you don't need to skip the validation. The reason it isn't working is probably because validates_presence_of checks to see if the associated object exists in the database. So for Doctor, Nurse, etc (the has_many side) it is okay to do this because those tables are not storing the user_id. But you should change the User model's validation to check for:

validates :profile_id, presence: true

This will ensure that the profile_id attribute is assigned, even if the Profile (Doctor, Nurse, etc) does not exist yet. You must create the objects in the proper order though:

u = User.new
d = Doctor.new
u.profile = d
u.save # it should be happy at this point because profile_id should be set
d.save
Mike S
  • 11,329
  • 6
  • 41
  • 76
  • Thanks Mike. Uh, how would I make sure everything is nicely validated? – Daryll Santos Feb 27 '14 at 05:12
  • I updated my answer, I think this will solve your issue. It might be slightly off because of the Polymorphism but the idea should be correct. – Mike S Feb 27 '14 at 05:28
0

Store the type(Nurse/Doctor) as resource for User and have a controller for User

class User
  #resource will be nurse/doctor etc
  belongs_to :resource, polymorphic: true, dependent: :destroy 
  accepts_nested_attributes_for :resource

  #add user specific validations here
end

#user will be either doctor or nurse and not both

class Doctor 
  has_one :user, as :resource 
  #add doctor specific validations
end

class Nurse
  has_one :user, as :resource
  #add nurse specific validations
end

now, when u are creating user of type doctor/nurse, u can ask the user to select the type and as per the choice render a nested form for the selected type. You can either render nested forms for both types right in the user form or add them using an ajax when user selects the type.

example form as below in haml. 'resource_fields' is an html id.

= form_for @user ...
  = #user specific fields go here
  = #show dropdown to select type and send ajax with type to render type specific form.
  - if @user.resource.nil? #if resource is not set
    #resource_fields
  - else #if resource is present
    = render 'users/doctor_form', f: f if @user.resource.is_a? Doctor
    = render 'users/nurse_form', f: f if @user.resource.is_a? Nurse

controller method to respond to ajax for type form

def get_type_form
  @user = User.new
  @user.resource = case params[:selected_type]
                           when "Doctor"
                             Doctor.new
                           when 'Nurse'
                             Nurse.new
                           end
end

and in get_form_type.js.haml

:plain
 - if @user.resource.is_a? Doctor
   $('#resource_fields').replaceWith("#{escape_javascript(render partial: 'users/doctor_form', locals: { f: nil })}");
 - if @user.resource.is_a? Nurse
   $('#resource_fields').replaceWith("#{escape_javascript(render partial: 'users/nurse_form', locals: { f: nil })}");

and partial for doctor_form will be as

 #resource_fields
 - if f.nil?
   = fields_for "user[resource_attributes]", @user.resource do |doctor|
     = render 'users/doctor_fields', :doctor: doctor
 - if f
   = f.fields_for :resource, @user.resource do |doctor|
     = render 'users/doctor_fields', doctor: doctor

and the partial for doctor_fields will be as

 = doctor.text_field :degree
 = doctor.text_field :specialization

I had a similar functionality and i implemented using the above way.

Prasad Surase
  • 6,486
  • 6
  • 39
  • 58
  • Awesome, I'll check this out... I noticed you removed the delegates/validations and moved the `accepts_nested_attributes_for` the resource. If you don't mind me asking, why is this better than the old code? – Daryll Santos Feb 27 '14 at 05:27
  • This will change the intended behavior. In the question, a Doctor or Nurse cannot exist independently of a User because of the validations. But this would effectively be the same as just removing those validations because now a Doctor can exist without a User. – Mike S Feb 27 '14 at 05:32
  • @MikeSlutsky we can add validation on doctor and nurse to check that user is always present and on user to check that resource is always present. – Prasad Surase Feb 27 '14 at 05:52
  • @DaryllSantos in my application, the requirement was that user should be able to select the type. Hence the parent was user object and not doctor or nurse object. Hence parent accepts nested attributes for child. – Prasad Surase Feb 27 '14 at 05:55
  • @DaryllSantos you can add model specific validation and fields. The forms will display validation errors. – Prasad Surase Feb 27 '14 at 06:06