Could you tell me whats the best practice to create has_one relations?
f.e. if i have a user model, and it must have a profile...
How could i accomplish that
I had an issue with this and accepts_nested_attributes_for because if nested attributes were passed in, the associated model was created there. I ended up doing
after_create :ensure_profile_exists
has_one :profile
accepts_nested_attributes_for :profile
def ensure_profile_exists
profile || create_profile
end
If this is a new association in an existing large database, I'll manage the transition like this:
class User < ActiveRecord::Base
has_one :profile
before_create :build_associations
def profile
super || build_profile(avatar: "anon.jpg")
end
private
def build_associations
profile || true
end
end
so that existing user records gain a profile when asked for it and new ones are created with it. This also places the default attributes in one place and works correctly with accepts_nested_attributes_for in Rails 4 onwards.
Best practice to create has_one relation is to use the ActiveRecord callback before_create
rather than after_create
. Or use an even earlier callback and deal with the issues (if any) of the child not passing its own validation step.
Because:
How to do it:
# in your User model...
has_one :profile
before_create :build_default_profile
private
def build_default_profile
# build default profile instance. Will use default params.
# The foreign key to the owning User model is set automatically
build_profile
true # Always return true in callbacks as the normal 'continue' state
# Assumes that the default_profile can **always** be created.
# or
# Check the validation of the profile. If it is not valid, then
# return false from the callback. Best to use a before_validation
# if doing this. View code should check the errors of the child.
# Or add the child's errors to the User model's error array of the :base
# error item
end
Your solution is definitely a decent way to do it (at least until you outgrow it), but you can simplify it:
# user.rb
class User < ActiveRecord::Base
has_one :profile
after_create :create_profile
end
There is a gem for this:
https://github.com/jqr/has_one_autocreate
Looks like it is a bit old now. (not work with rails3)
Probably not the cleanest solution, but we already had a database with half a million records, some of which already had the 'Profile' model created, and some of which didn't. We went with this approach, which guarantees a Profile model is present at any point, without needing to go through and retroactively generate all the Profile models.
alias_method :db_profile, :profile
def profile
self.profile = Profile.create(:user => self) if self.db_profile.nil?
self.db_profile
end