views:

52

answers:

3

I have something like this:

class User < ActiveRecord::Base
  has_one :profile
end

class Profile < ActiveRecord::Base
  belongs_to :user
end

user = User.new
user.profile.something #=> ERROR

What is a proper way to set a default profile object in this case? I have tried this:

class User < ActiveRecord::Base
  default_scope :include => :profile
  has_one :profile

  def after_initialize
    self.profile ||= Profile.new(:user => self)
  end
end

...but that creates N+1 queries. Any ideas?

Update

This is what I have now, works okay, still looking for something better:

class User < ActiveRecord::Base
  default_scope :include => :profile
  has_one :profile, :autosave => true

  def after_initialize
    self.profile = Profile.new(:user => self) if new_record?
  end
end

This way, you're going to have a Profile whenever you finally create your user. Otherwise, the only case is a new_record?.

A: 

which rails version do you use?

ipsum
Please put questions like this in comments, not in answers.
mdrozdziel
i wanted to do this, but there is no comment button on the first post ;)
ipsum
A: 

The correct answer relies on what are your intentions, cause there is no straight forward solution to this kind of problem.

The after_initialize callback is called after object is instantiated, so its not really a good please for this kind of logic.

Maybe you should try to use before_create / after_create instead? Those callbacks are called only during object creation time.

Also, don't use Profile.new, use one of below methods instead:

self.build_profile(...)
self.create_profile(...)

In the 2nd case model is beeing saved. You can pass hash with model attributes to both methods (dont pass :user, as it is set automatically).

mdrozdziel
I'm thinking about those, but actually for my specific case, the profile class depends on the subclass: `class Admin < User`, so `build_profile` has to build `"#{self.class.name}Profile".constantize.new`. I'm using something this for Group, RealEstate, and User.
viatropos
A: 

I think your answer is good. I've got a slightly different solution:

class User < ActiveRecord::Base
  default_scope :include => :profile
  has_one :profile
  alias_method :my_profile, :profile

  def my_profile
    self.profile = Profile.create(:user => self) unless self.profile
    self.profile
  end
end

Good

  • create profile when requested, not on instantiation

Not so good

  • You have to use my_profile (or however you would like to call it)
  • The unless self.profile check has to be done on each profile call
eteubert
thanks, but I'm not a big fan of aliasing methods like this, it makes subclassing and including modules more difficult to manage.
viatropos