nsommer nsommer - 11 months ago 43
Ruby Question

Creating model and nested model (1:n) at once with ActiveRecord

My Rails5 application has an organization model and a user model (1:n relationship). The workflow of creating an organization should include the creation of the organization's first user as well. I thought this would be able with ActiveRecord through nested models, however the create action fails with the error message "Users organization must exist".

class Organization < ApplicationRecord
has_many :users, dependent: :destroy
accepts_nested_attributes_for :users

class User < ApplicationRecord
belongs_to :organization

class OrganizationsController < ApplicationController
def new
@organization = Organization.new

def create
@organization = Organization.new(organization_params)
if @organization.save
redirect_to @organization
render 'new'

def organization_params
params.require(:organization).permit(:name, users_attributes: [:name, :email, :password, :password_confirmation])

In the view I use the
<%= f.fields_for :users do |user_form| %>

Is this a bug on my side, or isn't this supported by ActiveRecord at all? Couldn't find anything about it in the rails guides. After all, this should be (theoretically) possible: First do the INSERT for the organization, then the INSERT of the user (the order matters, to know the id of the organization for the foreign key of the user).

Answer Source

As described in https://github.com/rails/rails/issues/18233, Rails5 requires integrity checks. Because I didn't like a wishy-washy solution like disabling the integrity checks, I followed DHH's advice from the issue linked above:

I like aggregation through regular Ruby objects. For example, we have a Signup model that's just a Ruby object orchestrating the build process. So I'd give that a go!

I wrote a ruby class called Signup which encapsulates the organization and user model and offers a save/create interface like an ActiveRecord model would. Furthermore, by including ActiveModel::Model, useful stuff comes in to the class for free (attribute hash constructor etc., see http://guides.rubyonrails.org/active_model_basics.html#model).

# The Signup model encapsulates an organization and a user model.
# It's used in the signup process and helps persisting a new organization
# and a referenced user (the owner of the organization).
class Signup
  include ActiveModel::Model

  attr_accessor :organization_name, :user_name, :user_email, :user_password, :user_password_confirmation

  # A save method that acts like ActiveRecord's save method.
  def save
    @organization = build_organization

    return false unless @organization.save

    @user = build_user


  # Checks validity of the model.
  def valid?
    @organization = build_organization
    @user = build_user

    @organization.valid? and @user.valid?

  # A create method that acts like ActiveRecord's create method.
  # This builds the object from an attributes hash and saves it.
  def self.create(attributes = {})
    signup = Signup.new(attributes)


  # Build an organization object from the attributes.
  def build_organization
    @organization = Organization.new(name: @organization_name)

  # Build a user object from the attributes. For integritiy reasons,
  # a organization object must already exist.
  def build_user
    @user = User.new(name: @user_name, email: @user_email, password: @user_password, password_confirmation: @user_password_confirmation, organization: @organization)

Special thanks to @engineersmnky for pointing me to the corresponding github issue.