2010-09-28 2 views
11

У меня есть приложение, в котором мои пользователи могут иметь набор настроек. Оба сохраняются как ActiveRecord-модель следующим образом:Rails: создать ассоциацию, если не найдено, чтобы избежать ошибок nil

class User < AR::Base 
    has_one :preference_set 
end 

class PreferenceSet < AR::Base 
    belongs_to :user 
end 

теперь я могу получить доступ к предпочтениям пользователя:

@u = User.first 
@u.preference_set => #<PreferenceSet...> 
@u.preference_set.play_sounds => true 

Но это не удается, если набор предпочтения не создан, так как @u. preference_set будет возвращать нуль, и я буду звонить play_sounds по телефону nil.

Что я хочу архивировать, так это то, что User.preference_set всегда возвращает экземпляр PreferenceSet. Я попытался его определения, как это:

class User < .. 
    has_one :preference_set 

    def preference_set 
    preference_set || build_preference_set 
    end 
end 

Это вызывает 'Stack level too deep', так как он вызывает себя рекурсивно.

Мой вопрос заключается в следующем:

Как я могу гарантировать, что @user.preference_set возвращает либо соответствующий preference_set-запись или, если таковой не существует, строит новый?

Я знаю, что могу просто переименовать свою ассоциацию (например, preference_set_real) и избежать рекурсивных вызовов таким образом, но для простоты в своем приложении я хотел бы сохранить именование.

Спасибо!

ответ

27

Ну лучший способ сделать это, чтобы создать соответствующую запись при создании первичного:

class User < ActiveRecord::Base 
    has_one  :preference_set, :autosave => true 
    before_create :build_preference_set 
end 

Это будет настроить его таким образом всякий раз, когда User создается, так это PreferenceSet. Если вам нужно инициализировать связанную запись с помощью аргументов, тогда вызовите другой метод в before_create, который вызывает build_preference_set(:my_options => "here"), а затем возвращает true.

После этого вы можете просто нормализовать все существующие записи, итерации по всем, у которых нет PreferenceSet, и построить один по телефону #create_preference_set.

Если вы хотите создать только PreferenceSet, когда это абсолютно необходимо, то вы можете сделать что-то вроде:

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 
+3

Wow, не попадаются alias_method_chain. Это круто. – Chowlett

+0

Следует иметь в виду, что 'alias_method_chain' устарел в пользу' Module # prepend'. Больше информации здесь: https://github.com/rails/rails/pull/19434 –

38

или просто

class User < ApplicationRecord 
    has_one :preference_set 

    def preference_set 
    super || build_preference_set 
    end 
end 
Смежные вопросы