2

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?.

Lance
  • 75,200
  • 93
  • 289
  • 503

4 Answers4

3

You can write your own User#profile that will build one for you if it doesn't exist:

class User < ActiveRecord::Base
  has_one :profile

  def profile_with_default
    profile_without_default || build_profile
  end
  alias_method_chain :profile, :default
end
bakineggs
  • 31
  • 2
2

This is a good answer:

class User < ActiveRecord::Base
 has_one :preference_set

 def preference_set
   super || build_preference_set
  end
end
Community
  • 1
  • 1
mahemoff
  • 44,526
  • 36
  • 160
  • 222
1

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 it's not really a good place for this kind of logic.

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

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

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

In the 2nd case the model is being saved. You can pass a hash with model attributes to both methods (don't pass :user, as it is set automatically).

wrtsprt
  • 5,319
  • 4
  • 21
  • 30
mdrozdziel
  • 5,528
  • 6
  • 39
  • 55
  • 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. – Lance Sep 19 '10 at 01:05
1

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
ericteubert
  • 4,531
  • 3
  • 31
  • 35
  • thanks, but I'm not a big fan of aliasing methods like this, it makes subclassing and including modules more difficult to manage. – Lance Sep 19 '10 at 01:03