I have an application where my users can have a set of preferences. Both are stored as ActiveRecord-models as follows:
class User < AR::Base
has_one :preference_set
end
class PreferenceSet < AR::Base
belongs_to :user
end
I can now access the preferences of a user:
@u = User.first
@u.preference_set => #<PreferenceSet...>
@u.preference_set.play_sounds => true
But this fails if a preference set is not already created, since @u.preference_set will be returning nil, and I'll be calling play_sounds
on nil
.
What I want to archive is that User.preference_set always returns a PreferenceSet instance. I've tried defining it like this:
class User < ..
has_one :preference_set
def preference_set
preference_set || build_preference_set
end
end
This is causing a 'Stack level too deep'
, since it is calling itself recursively.
My question is this:
How can I ensure that @user.preference_set
returns either the corresponding preference_set-record or, if none exists, builds a new one?
I know I could just rename my association (eg. preference_set_real
) and avoid recursive calls this way, but for the sake of simplicity in my app, I'd like to keep the naming.
Thanks!
Well the best way to do this is to create the associated record when you create the primary one:
class User < ActiveRecord::Base
has_one :preference_set, :autosave => true
before_create :build_preference_set
end
That will set it up so whenever a User
is created, so is a PreferenceSet
. If you need to initialise the the associated record with arguments, then call a different method in before_create
which calls build_preference_set(:my_options => "here")
and then returns true
.
You can then just normalise all existing records by iterating over any that don't have a PreferenceSet
and building one by calling #create_preference_set
.
If you want to only create the PreferenceSet
when it is absolutely needed, then you can do something like:
class User < ActiveRecord::Base
has_one :preference_set
def preference_set_with_initialize
preference_set_without_initialize || build_preference_set
end
alias_method_chain :preference_set, :initialize
end
or simply
class User < ApplicationRecord
has_one :preference_set
def preference_set
super || build_preference_set
end
end
This works because ActiveRecord defines the association method in a mixin.
来源:https://stackoverflow.com/questions/3812667/rails-create-association-if-none-is-found-to-avoid-nil-errors