Есть ли проверка, которую я могу использовать для определенного атрибута во всех связанных записях?

У меня есть Question, который has_many :answers (прямо как ТАК). Я также хочу, чтобы каждый вопрос имел только 1 accepted_answer, поэтому я просто добавил атрибут :accepted к модели Answer, которая является просто логическим значением.

Итак, теперь, чтобы получить принятый ответ на мой вопрос, я написал метод для своей модели, который просто делает это:

  def accepted_answer
    answers.where(accepted: true)
  end

Это позволяет мне выполнять question.accepted_answer и возвращает объект ActiveRelation, как и следовало ожидать.

Ничего особенного. Просто и эффективно.

Однако я хочу убедиться, что на каждый вопрос может быть только один ответ accepted: true в любой момент времени.

Как лучше всего подойти к этому?

Я думал об использовании валидатора, но не смог найти такого, который обрабатывал бы связанные объекты таким образом. В некоторых есть интересные фрагменты, но я не могу собрать все воедино. Например, присутствие интересно, как и отсутствие и validates_with (но последнее кажется слишком тяжелым).

Предложения?


person marcamillion    schedule 22.06.2016    source источник


Ответы (1)


Скорее всего, лучше всего использовать обратный вызов after_add (пример здесь), что установит значение false для всех ваших существующих accepted записей через update_all, а последний ответ с accept будет установлен в значение true. Все зависит от вашей логики.

Вы также можете использовать некоторые другие обратные вызовы, такие как before_save, before_update и т. д. с аналогичной функциональностью в зависимости от специфики вашего приложения.

Это не выход из проверки, но он будет эффективно поддерживать требуемое состояние вашей модели. Кроме того, цель проверки - предупредить вас, когда что-то недействительно, но я думаю, вы хотите сохранить свой объект без таких сбоев и просто применить один принятый ответ.

Дайте мне знать, если вы хотите прекратить добавлять ответы после того, как первый был принят. В этом случае потребуется другой функционал.

person Community    schedule 22.06.2016
comment
Нет необходимости прекращать добавлять ответы после принятия первого. Функционал очень похож на SO. Мне просто нужно, чтобы в любой момент времени был только один accept_answer. - person marcamillion; 22.06.2016
comment
Тогда вам должно быть хорошо с тем, что я описал. Я использую что-то подобное для создания основного изображения среди набора изображений. - person ; 22.06.2016
comment
Интересный. Никогда не знал о update_all. На самом деле это хорошая идея. В моем действии accept_answer на моем контроллере я мог бы просто сделать что-то вроде q.answers.update_all(accepted: false), а затем просто назначить тот, который я хочу, @answer.update(accepted: true). Что-то в этом роде, о чем вы подумали? - person marcamillion; 22.06.2016
comment
@marcamillion: Да, именно это я и имел в виду. update_all очень эффективен, потому что он генерирует один оператор обновления и не запускает обратные вызовы для затронутых моделей (при условии, что вы согласны с этим). - person ; 22.06.2016
comment
@marcamillion: Еще кое-что. Будьте осторожны, когда вы делаете @answer.update(accepted: true), потому что это может вызвать обратные вызовы, и вы можете попасть в бесконечный цикл. Вместо этого вы можете попробовать update_column (apidock.com/rails/ActiveRecord/Persistence/update_column), который также не должен вызывать никаких обратных вызовов, насколько я помню. - person ; 22.06.2016
comment
Да, это идеально подходит для того, что я хочу. Спасибо за совет! - person marcamillion; 22.06.2016
comment
@marcamillion: Кроме того, подумав о вашем утверждении @answer.update(accepted: true), я начал подозревать, что вы выполняете эту логику в контроллере. Это правильно? Я бы посоветовал попробовать перенести его на модель «Ответ». Почему? Потому что в будущем у вас может появиться другой контроллер, который будет делать примерно то же самое, и вам придется дублировать код. Вы хотите сделать свои модели достаточно умными, чтобы их можно было обслуживать самостоятельно, поэтому вы можете попытаться создать в своем ответе метод для выполнения project.accepted_answers.update_all(accepted_answer: false); self.update_column(accepted_answer: true). - person ; 22.06.2016