Перетворення масиву об'єктів в ActiveRecord :: Відношення


104

У мене є масив об'єктів, назвемо його an Indicator. Я хочу запустити def self.subjectsна цьому масиві методи класу Indicator (методи сорту, області застосування тощо). Єдиний спосіб, яким я знаю запускати методи класу на групі об'єктів, це мати їх 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 :: відношення? Переважно, не роблячи whereкожного разу.
  2. Під час запуску def self.subjectsтипу типу на ActiveRecord :: Relation, як я можу отримати доступ до самого об'єкта ActiveRecord :: Relation?

Дякую. Якщо мені потрібно щось уточнити, дайте мені знати.


3
Якщо Ваша єдина причина намагатися перетворити цей масив назад у відношення - це тому, що Ви отримали його через .all, просто використовуйте, .scopedяк вказано у відповіді Ендрю Маршалла (Хоча в рейках 4 він буде працювати .all). Якщо вам доведеться перетворити масив у відношення, ви десь помилилися ...
nzifnab

Відповіді:


46

Як я можу перетворити масив об'єктів у ActiveRecord :: відношення? Переважно, не роблячи куди щоразу.

Ви не можете перетворити масив в ActiveRecord :: Relation, оскільки відношення є лише конструктором SQL-запиту, і його методи не працюють на фактичних даних.

Однак якщо ви хочете відносини, то:

  • для ActiveRecord 3.x, не дзвоніть, allа замість цього телефонуйте scoped, що поверне відношення, яке представляє ті самі записи, які allб вам дали у масиві.

  • для ActiveRecord 4.x просто зателефонуйте all, що поверне відношення.

Під час запуску def self.subjectsтипу типу на ActiveRecord :: Relation, як я можу отримати доступ до самого об'єкта ActiveRecord :: Relation?

Коли метод викликається об'єктом Relation, selfце відношення (на відміну від класу моделі, який визначено в).


1
Дивіться @Marco Prins нижче про рішення.
Джастін

як щодо. придушеного методу натискання в рейки 5
Jaswinder

@GstjiSaini Я не впевнений у точному методі, на який ви звертаєтесь, вкажіть документ або посилання на джерело. Хоча, якщо воно застаріле, це не дуже життєздатне рішення, оскільки воно, швидше за все, скоро піде.
Ендрю Маршалл

І саме тому ви можете зробити class User; def self.somewhere; where(location: "somewhere"); end; end, а потімUser.limit(5).somewhere
Доріан

Це єдина відповідь, яка справді освічує ОП. Питання розкриває недосконалі знання про те, як працює ActiveRecord. @Justin просто підкреслює відсутність розуміння того, чому погано зберігати проходять масиви об'єктів навколо, щоб просто нанести на них карту та створити ще один непотрібний запит.
james2m

162

Ви можете перетворити масив об’єктів arrв ActiveRecord :: відношення, як це (якщо ви знаєте, до якого класу відносяться об'єкти, що ви, ймовірно, робите)

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

Ви повинні користуватися, whereхоча це корисний інструмент, який ви не повинні охоче використовувати. А тепер у вас є однолінійний перетворення масиву у відношення.

map(&:id)перетворить ваш масив об'єктів у масив, що містить лише їхні ідентифікатори. І передача масиву до пункту де генерує оператор 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але все-таки запит.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.