Преобразование массива объектов в ActiveRecord :: Relation

105

У меня есть массив объектов, назовем его Indicator. Я хочу запустить def self.subjectsв этом массиве методы класса индикаторов (различных видов , областей действия и т. Д.). Единственный известный мне способ запустить методы класса для группы объектов - сделать их ActiveRecord :: Relation. Поэтому я прибегаю к добавлению to_indicatorsметода в Array.

def to_indicators
  # TODO: Make this less terrible.
  Indicator.where id: self.pluck(:id)
end

Иногда я объединяю несколько из этих областей, чтобы отфильтровать результаты в методах класса. Итак, хотя я вызываю метод для ActiveRecord :: Relation, я не знаю, как получить доступ к этому объекту. Я могу добраться до его содержания только через all. Но allэто массив. Итак, мне нужно преобразовать этот массив в ActiveRecord :: Relation. Например, это часть одного из методов:

all.to_indicators.applicable_for_bank(id).each do |indicator|
  total += indicator.residual_risk_for(id)
  indicator_count += 1 if indicator.completed_by?(id)
end

Я думаю, это сводится к двум вопросам.

  1. Как я могу преобразовать массив объектов в ActiveRecord :: Relation? Желательно не делать whereкаждый раз.
  2. def self.subjectsКак мне получить доступ к самому объекту ActiveRecord :: Relation при запуске метода типа в ActiveRecord :: Relation?

Спасибо. Если мне нужно что-то уточнить, дайте мне знать.

Натан
источник
3
Если ваша единственная причина для попытки преобразовать этот массив обратно в отношение заключается в том, что вы получили его через .all, просто используйте, .scopedкак указывает ответ Эндрю Маршалла (хотя в рельсах 4 он будет работать .all). Если вам нужно превратить массив в отношение, вы где-то пошли не так ...
nzifnab

Ответы:

46

Как я могу преобразовать массив объектов в ActiveRecord :: Relation? Желательно не делать каждый раз где.

Вы не можете преобразовать массив в ActiveRecord :: Relation, поскольку Relation - это просто построитель для SQL-запроса, а его методы не работают с фактическими данными.

Однако, если вы хотите отношения, тогда:

  • для ActiveRecord 3.x не вызывайте, allа вместо этого вызывайте scoped, что вернет Relation, которое представляет те же записи, allчто и в Array.

  • для ActiveRecord 4.x просто вызовите all, который вернет Relation.

def self.subjectsКак мне получить доступ к самому объекту ActiveRecord :: Relation при запуске метода типа в ActiveRecord :: Relation?

Когда метод вызывается для объекта Relation, selfэто отношение (в отличие от класса модели, в котором оно определено).

Эндрю Маршалл
источник
1
См. @Marco Prins ниже о решении.
Джастин
насчет .push устаревшего метода в рельсах 5
Jaswinder
@GstjiSaini Я не уверен, о каком именно методе вы говорите. Пожалуйста, предоставьте документ или ссылку на источник. Хотя, если он устарел, это не очень жизнеспособное решение, поскольку оно, скорее всего, скоро исчезнет.
Эндрю Маршалл,
И именно поэтому вы можете сделать class User; def self.somewhere; where(location: "somewhere"); end; end, а затемUser.limit(5).somewhere
Дориан
Это единственный ответ, который действительно проливает свет на ОП. Этот вопрос свидетельствует о недостаточном знании того, как работает ActiveRecord. @Justin просто усиливает непонимание того, почему плохо продолжать передавать массивы объектов, просто чтобы сопоставить их и создать еще один ненужный запрос.
james2m
162

Вы можете преобразовать массив объектов arrв ActiveRecord :: Relation, как это (при условии, что вы знаете, к какому классу относятся объекты, что вы, вероятно, делаете)

MyModel.where(id: arr.map(&:id))

Вы должны использовать whereего, это полезный инструмент, которым вы не должны отказываться. И теперь у вас есть однострочный преобразователь массива в отношение.

map(&:id)превратит ваш массив объектов в массив, содержащий только их идентификаторы. И передача массива в предложение where сгенерирует оператор SQL, INкоторый выглядит примерно так:

SELECT .... WHERE `my_models`.id IN (2, 3, 4, 6, ....

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

Марко Принс
источник
3
Молодец, именно то, что мне нужно. Вы можете использовать это для любого атрибута модели. у меня отлично работает.
nfriend21
7
Зачем создавать буквальный SQL самостоятельно, если вы можете это сделать where(id: arr.map(&:id))? И, строго говоря, это не преобразует массив в отношение, а вместо этого получает новые экземпляры объектов (как только отношение реализовано) с теми идентификаторами, которые могут иметь другие значения атрибутов, чем уже существующие в памяти экземпляры в массиве.
Эндрю Маршалл
7
Однако это теряет порядок.
Велизар Христов
1
@VelizarHristov Это потому, что теперь это отношение, которое можно упорядочить только по столбцу, а не так, как вы хотите. Отношения быстрее обрабатывают большие наборы данных, и здесь будут некоторые компромиссы.
Марко Принс
8
Очень неэффективно! Вы превратили коллекцию объектов, которые у вас уже есть в памяти, в те, для доступа к которым вы собираетесь выполнить запрос к базе данных. Я бы посмотрел на рефакторинг тех методов класса, которые вы хотите перебирать по массиву.
james2m
5

Что ж, в моем случае мне нужно преобразовать массив объектов в ActiveRecord :: Relation, а также отсортировать их по определенному столбцу (например, id). Поскольку я использую MySQL, функция поля может быть полезна.

MyModel.where('id in (?)',ids).order("field(id,#{ids.join(",")})") 

SQL выглядит так:

SELECT ... FROM ... WHERE (id in (11,5,6,7,8,9,10))  
ORDER BY field(id,11,5,6,7,8,9,10)

Функция поля MySQL

Синчэн Ся
источник
Для версии этого , что не работает с PostgreSQL, смотреть не дальше , чем эту тему: stackoverflow.com/questions/1309624/...
armchairdj
0

ActiveRecord::Relation связывает запрос к базе данных, который извлекает данные из базы данных.

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

Когда я бегу,

users = User.where(id: [1,3,4,5])
  User Load (0.6ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` IN (1, 3, 4, 5)  ORDER BY created_at desc

Здесь, выше, usersвозвращает Relationобъект, но связывает запрос базы данных за ним, и вы можете его просмотреть,

users.to_sql
 => "SELECT `users`.* FROM `users` WHERE `users`.`id` IN (1, 3, 4, 5)  ORDER BY created_at desc"

Таким образом, невозможно вернуться ActiveRecord::Relationиз массива объектов, который не зависит от запроса sql.

луч
источник
0

Во-первых, это НЕ серебряная пуля. По своему опыту я обнаружил, что преобразование в отношения иногда проще, чем альтернативы. Я стараюсь использовать этот подход очень экономно и только в тех случаях, когда альтернатива будет более сложной.

Это, как говорится, мое решение, я расширил Arrayкласс

# lib/core_ext/array.rb

class Array

  def to_activerecord_relation
    return ApplicationRecord.none if self.empty?

    clazzes = self.collect(&:class).uniq
    raise 'Array cannot be converted to ActiveRecord::Relation since it does not have same elements' if clazzes.size > 1

    clazz = clazzes.first
    raise 'Element class is not ApplicationRecord and as such cannot be converted' unless clazz.ancestors.include? ApplicationRecord

    clazz.where(id: self.collect(&:id))
  end
end

Пример использования будет array.to_activerecord_relation.update_all(status: 'finished'). Где мне это использовать?

Иногда нужно отфильтровать, ActiveRecord::Relationнапример убрать незавершенные элементы. В таких случаях лучше всего использовать прицел, elements.not_finishedи вы все равно оставите его ActiveRecord::Relation.

Но иногда это состояние более сложное. Выньте все незавершенные элементы, которые были произведены за последние 4 недели и были проверены. Чтобы избежать создания новых областей, вы можете отфильтровать массив, а затем преобразовать обратно. Имейте в виду, что вы все еще делаете запрос к БД, быстро, поскольку он выполняет поиск, idно все еще является запросом.

Харис Краина
источник