Associated models and a nested form with validation not working

。_饼干妹妹 提交于 2019-12-08 18:17:30

Your validation does not work due to a Catch-22

To apply for this job, you would have to be insane; but if you are insane, you are unacceptable.

ActiveRecord models get their ID from the database when they are saved. But the validation on the nested user runs before the organization is inserted into the the database.

You would guess that just checking validates_presence_of instead would pass:

validates_presence_of :organization, unless: -> { usertype == 1 }

Unfortunatly not. In order for validates_presence_of :organization to pass the organization must be persisted to the database. Catch-22 again.

In order for the validation to pass we would need to split creating the organization and user into two steps:

org = Organization.create(name: 'M & M Enterprises')
user = org.users.build(username: 'milo_minderbinder', ...)
user.valid? 

Unfortunatly the means that you cannot use accepts_nested_attributes_for :users - well at least not straight off the bat.

By using a transaction we can insert the organization into the the database and and roll back if the user is not valid.

def create
  @organization = Organization.new(new_params.except(:users_attributes))
  @organization.transaction do
    @organization.save!
    if new_params[:users_attributes].any?
      @organization.users.create!(new_params[:users_attributes])
    end
  end
  if @organization.persisted?
    # ...
    if @organization.users.any?
      # send emails ... 
    end
  else
    @organization.users.build if @organization.users.blank? 
    render :new
  end
end

Followup questions

We use @organization.persisted? since we presumably want to redirect to the newly created organisation no matter if the there is a User record created.

because the emails are sent to users? It shouldn't matter since organization is rolled back if no user is created.

The transaction is not rolled back if there is no user created. Only if the user(s) fails to save due to invalid parameters. This is based on your requirement:

But an organization can also (temporarily) have no users.

If you need the @organisation to be invalid without users you could do:

  @organisation.errors.add(:users, 'No users provided') unless new_params[:users_attributes].any?
  @organization.transaction do
    @organization.save!
    if new_params[:users_attributes].any?
      @organization.users.create!(new_params[:users_attributes])
    end
  end

You would use @organization.users.any? to check if there are any users. @organization.users.persisted? will not work since .persisted? is a method on model instances - not collections.

On a different note, I assume it's not possible to overwrite/update an existing organization/user with this method (which shouldn't be) instead of always creating a new record?

Right, since this will always issue two SQL insert statements it will not alter existing records.

It is up to you however to create validations that guarantee the uniqueness of the database columns (IE you don't want several records with the same user.email or organiation.name).

On the plus side is that none of these caveats apply when updating an existing organization:

def update
  @organisation.update(... params for org and and users ...)
end

Since you don't get the whole chicken or egg dilemma when validating the users.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!