Перетворення масиву об'єктів в 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.