Rails: создайте ассоциацию, если ничего не найдено, чтобы избежать нулевых ошибок

У меня есть приложение, в котором мои пользователи могут иметь набор настроек. Оба хранятся как модели ActiveRecord следующим образом:

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 будет возвращать nil, и я буду вызывать 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) и таким образом избежать рекурсивных вызовов, но для простоты в моем приложении я хотел бы сохранить имя.

Спасибо!


person Mattias    schedule 28.09.2010    source источник


Ответы (2)


Что ж, лучший способ сделать это — создать связанную запись при создании основной:

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
person Bo Jeanes    schedule 28.09.2010
comment
Имейте в виду, что alias_method_chain устарел в пользу Module#prepend. Подробнее здесь: github.com/rails/rails/pull/19434 - person Kostas Rousis; 31.08.2016

Есть элегантно простая форма:

class User < ApplicationRecord
  has_one :preference_set
  
  def preference_set
    super || build_preference_set
  end
end

ActiveRecord намеренно разрешает такое использование super для переопределения поведения методов ассоциации.

person inopinatus    schedule 24.07.2013
comment
Это то, что я искал. - person Paul Danelli; 07.02.2019