Как узнать, был ли сделан вызов «include» при работе с одной записью? - PullRequest
1 голос
/ 27 февраля 2020

Мотивация

Мотивация состояла в том, что я хочу встроить сериализацию любой модели, которая была включена в цепочку отношений. То, что я сделал, работает на уровне отношений, но если я получу одну запись, сериализация не сможет воспользоваться тем, что я сделал.

Чего я достиг до сих пор

В основном я использую метод includes_values класса ActiveRecord::Relation, который просто сообщает мне, что было включено до сих пор. Т.е.

> Appointment.includes(:patient).includes(:slot).includes_values
=> [:patient, :slot]

Чтобы воспользоваться этим, я перезаписываю метод as_json на уровне ActiveRecord::Relation с помощью этого инициализатора:

# config/initializers/active_record_patches.rb
module ActiveRecord
  class Relation
    def as_json(**options)
      super(options.merge(include: includes_values)) # I could precondition this behaviour with a config
    end
  end
end

Что он делает, так это добавьте для меня опцию include в методе отношения as_json.

Итак, старую цепочку:

Appointment.includes(:patient).includes(:slot).as_json(include: [:patient, :slot])

можно написать без последнего включения:

Appointment.includes(:patient).includes(:slot).as_json

с получением тех же результатов (модели Patient и Slot встроены в сгенерированный га sh).

ПРОБЛЕМА

Проблема в том, что поскольку метод includes_values относится к классу ActiveRecord::Relation, я не могу использовать его на уровне записи, чтобы узнать, был ли выполнен вызов includes.

Итак в настоящее время, когда я получаю запись по таким запросам и вызываю as_json для нее, я не получаю встроенные модели.

И настоящая проблема заключается в ответе:

Как узнать, какие модели включены в цепочку запросов, которые извлекли текущую запись, учитывая, что это произошло?

Если бы я мог ответить на этот вопрос, то я мог бы Я перезапишу метод as_json в моих собственных моделях с помощью:

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
  extend Associations

  def as_json(**options)
    super(options.merge(include: included_models_in_the_query_that_retrieved_me_as_a_record))
  end
end

Одна идея

Одна из моих идей - переписать includes где-то (может быть в моем инициализаторе, перезаписывая непосредственно класс ActiveRecord::Relation, или мой класс ApplicationRecord). Но, оказавшись там, я не могу найти простой способ «запечатать» произвольную информацию в записях, созданных отношением.

1 Ответ

1 голос
/ 27 февраля 2020

Это решение кажется довольно неуклюжим, и там могут быть лучшие варианты.

class ApplicationRecord < ActiveRecord::Base
  def as_json(**options)
    loaded_associations = _reflections.each_value
      .select { |reflection| association(reflection.name).loaded? }
      .map(&:name)

    super(options.merge(include: loaded_associations))
  end
end

Обратите внимание, что это загружает только ассоциации 1-го уровня. Если Appointment.includes(patient: :person), то будет возвращено только :patient, поскольку :person является вложенным. Если вы планируете сделать это рекурсивным, остерегайтесь ассоциаций с круговой загрузкой.

Стоит отметить, что вы в настоящее время сливаете include: ... поверх предоставленных опций. Не давая пользователю выбора использовать другие параметры включения. Я рекомендую вместо этого использовать reverse_merge. Или поменяйте местами размещения вокруг {includes: ...}.merge(options).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...