Creating a related object before validation

Hello,

I have two models, User and Person. Basically users are users of the
system
while people is a kind of address book. But every user is also a person
so I
created this relationship:

class User < ActiveRecord::Base
belongs_to :person
end

class Person < ActiveRecord::Base
has_one :user
end

Now, on creating a user I want to automatically create a person. Is this
the
way to do it:

before_validation :create_person

def create_person
if person.nil? && person_id.nil?
self.person = Person.create!(:name => email, :tenant => tenant)
end
end

Thanks.

2011/8/28 J. Pablo F. [email protected]

class Person < ActiveRecord::Base
self.person = Person.create!(:name => email, :tenant => tenant)
end
end

If you use the method above, there’s a big chance that a Person is
created
when the associated User fails the validation. Look at
accepts_nested_attributes_for.

For more options, visit this group at
http://groups.google.com/group/rubyonrails-talk?hl=en.

You might want to use after_initialize callback, e.g.

after_initialize :build_person_if_no_person

def build_person_if_no_person
build_person if new_record? && person.nil?
end

The person.nil? check might be needed in some cases, for example if
you want to use Person.new.build_user.

(I did not try the above code, but this is almost an excerpt from my
application, which uses this approach).

I wouldn’t recommend creating object in before_validation callback,
because:

  1. when validation fails, you’re left with not needed Person records
    in the database
  2. one day you may want to save User and skip validation, in which
    case you won’t have the Person object created.

Overall, I like the idea of using composition between User and Person
instead of inheritance - I usually do it the same way in my
applications.

As far as I know, all callbacks and the save is run inside a
transaction, so
if saving fails for the user, the person won’t be save. I still had the
issue that just calling user.valid? would create a record in the
database
when not expected. I did switch to calling new instead of create and all
my
tests about it are passing.

I’m assuming you don’t want to create a Person if your User
validations fail and vice versa. One way to do it is as follows. This
will validate your Person during the validation phase for User, and if
all is good save your Person then save User. Note that this doesn’t
preclude any database constraint/error from screwing up your User
save, resulting in orphaned Persons. To be really safe you should wrap
both Person and User save operations in a transaction (http://
api.rubyonrails.org/classes/ActiveRecord/Transactions/
ClassMethods.html).

class User < ActiveRecord::Base
bleongs_to :person, :validate => true

before_validation :create_person
before_create :save_person

def create_person
if person.nil? && person_id.nil?
self.person = Person.new(:name => email, :tenant => tenant)
end
end

def save_person
self.person.save!
end
end

Thanks. My code looks more like yours now, using new instead of create.
But
save_person is not needed, in my case at least, the person gets saved
automatically and as far as I know, all callbacks and the saving are
wrapped
in a transaction by Rails automatically.

Thanks for pointing to after_initialize, your point 2 is very valid.
About
point 1, everything is wrapped automatically in a transaction by Rails
and I
have tests for it. Is that not the case (and my tests are passing due to
the
magic of the universe or something?).

My problem is a little bit more complex, as both users and people are in
tenant, my code looks like this:

def create_person
if person_id.nil? && person.nil?
self.person = Person.new
self.tenant.people << self.person
end
end

The problem is that in after_initialize, self.tenant is still nil.