Conversione di una matrice di oggetti in ActiveRecord :: Relation


104

Ho una serie di oggetti, chiamiamola Indicator. Voglio eseguire i metodi della classe Indicator (quelli della def self.subjectsvarietà, degli ambiti, ecc.) Su questo array. L'unico modo che conosco per eseguire metodi di classe su un gruppo di oggetti è di averli come ActiveRecord :: Relation. Quindi finisco per ricorrere all'aggiunta di un to_indicatorsmetodo a Array.

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

A volte incateno alcuni di questi ambiti per filtrare i risultati, all'interno dei metodi della classe. Quindi, anche se chiamo un metodo su ActiveRecord :: Relation, non so come accedere a quell'oggetto. Posso solo arrivare al contenuto attraverso all. Ma allè un array. Quindi devo convertire quell'array in ActiveRecord :: Relation. Ad esempio, questo fa parte di uno dei metodi:

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

Immagino che questo si conduca a due domande.

  1. Come posso convertire un array di oggetti in un ActiveRecord :: Relation? Preferibilmente senza fare una whereogni volta.
  2. Quando si esegue un def self.subjectsmetodo di tipo su un ActiveRecord :: Relation, come si accede all'oggetto ActiveRecord :: Relation stesso?

Grazie. Se ho bisogno di chiarire qualcosa, fammelo sapere.


3
Se la tua unica ragione per provare a riconvertire quell'array in una relazione è perché l'hai ottenuto tramite .all, usa semplicemente .scopedcome indica la risposta di Andrew Marshall (sebbene in rails 4 funzionerà con .all). Se ti ritrovi a dover trasformare un array in una relazione, hai sbagliato da qualche parte ...
nzifnab

Risposte:


46

Come posso convertire un array di oggetti in un ActiveRecord :: Relation? Preferibilmente senza fare un dove ogni volta.

Non è possibile convertire un array in ActiveRecord :: Relation poiché una relazione è solo un generatore per una query SQL ei suoi metodi non funzionano sui dati effettivi.

Tuttavia, se quello che vuoi è una relazione, allora:

  • per ActiveRecord 3.x, non chiamare alle invece chiama scoped, che restituirà una relazione che rappresenta gli stessi record che allti darebbero in un array.

  • per ActiveRecord 4.x, è sufficiente chiamare all, che restituisce una relazione.

Quando si esegue un def self.subjectsmetodo di tipo su un ActiveRecord :: Relation, come si accede all'oggetto ActiveRecord :: Relation stesso?

Quando il metodo viene chiamato su un oggetto Relation, selfè la relazione (al contrario della classe del modello in cui è definita).


1
Vedi @Marco Prins di seguito sulla soluzione.
Justin

che dire del metodo deprecato .push in rails 5
Jaswinder

@GstjiSaini Non sono sicuro del metodo esatto a cui ti riferisci, per favore fornisci un documento o un link alla fonte. Tuttavia, se è deprecato, non è una soluzione molto praticabile in quanto probabilmente scomparirà presto.
Andrew Marshall

Ed è per questo che puoi farlo class User; def self.somewhere; where(location: "somewhere"); end; end, alloraUser.limit(5).somewhere
Dorian

Questa è l'unica risposta che sta veramente illuminando l'OP. La domanda rivela una conoscenza imperfetta di come funziona ActiveRecord. @Justin sta solo rafforzando una mancanza di comprensione del motivo per cui è brutto continuare a passare matrici di oggetti solo per mappare su di loro e costruire un'altra query non necessaria.
james2m

162

Puoi convertire un array di oggetti arrin un ActiveRecord :: Relation come questo (supponendo che tu sappia quale classe sono gli oggetti, cosa che probabilmente fai)

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

Devi usare whereperò, è uno strumento utile che non dovresti essere riluttante a usare. E ora hai una riga che converte un array in una relazione.

map(&:id)trasformerà il tuo array di oggetti in un array contenente solo i loro ID. E passare un array a una clausola where genererà un'istruzione SQL con un INaspetto simile a:

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

Tieni presente che l'ordinamento dell'array andrà perso - ma poiché il tuo obiettivo è solo eseguire un metodo di classe sulla raccolta di questi oggetti, presumo che non sarà un problema.


3
Ben fatto, esattamente quello di cui avevo bisogno. Puoi usarlo per qualsiasi attributo sul modello. funziona perfettamente per me.
nfriend21

7
Perché costruire da solo SQL letterale quando puoi farlo where(id: arr.map(&:id))? E in senso stretto, questo non converte l'array in una relazione, ma ottiene invece nuove istanze degli oggetti (una volta realizzata la relazione) con quegli ID che possono avere valori di attributo diversi rispetto alle istanze già in memoria nell'array.
Andrew Marshall

7
Questo però perde l'ordine.
Velizar Hristov

1
@VelizarHristov Questo perché ora è una relazione, che può essere ordinata solo in base a una colonna e non nel modo desiderato. Le relazioni sono più veloci per gestire set di dati di grandi dimensioni e ci saranno alcuni compromessi.
Marco Prins

8
Molto inefficiente! Hai trasformato una raccolta di oggetti che hai già in memoria in oggetti a cui farai una query sul database per accedere. Vorrei esaminare il refactoring di quei metodi di classe che desideri iterare sull'array.
james2m

5

Bene, nel mio caso, ho bisogno di convertire un array di oggetti in ActiveRecord :: Relation e di ordinarli con una colonna specifica (id per esempio). Dato che sto usando MySQL, la funzione field potrebbe essere utile.

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

L'SQL ha questo aspetto:

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

Funzione di campo MySQL


Per una versione di questo che funziona con PostgreSQL, non guardare oltre questo thread: stackoverflow.com/questions/1309624/…
armchairdj

0

ActiveRecord::Relation lega la query del database che recupera i dati dal database.

Supponiamo che abbia senso, abbiamo array con oggetti della stessa classe, quindi con quale query supponiamo di collegarli?

Quando corro

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

Qui sopra, usersrestituisci l' Relationoggetto ma lega la query del database dietro di esso e puoi visualizzarlo,

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

Quindi non è possibile tornare ActiveRecord::Relationda un array di oggetti che è indipendente dalla query sql.


0

Prima di tutto, questo NON è un proiettile d'argento. In base alla mia esperienza, ho scoperto che la conversione alla relazione a volte è più facile delle alternative. Cerco di utilizzare questo approccio con molta parsimonia e solo nei casi in cui l'alternativa sarebbe più complessa.

Detto questo qui è la mia soluzione, ho esteso la Arraylezione

# 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

Un esempio di utilizzo potrebbe essere array.to_activerecord_relation.update_all(status: 'finished'). Ora dove lo uso?

A volte è necessario filtrare, ActiveRecord::Relationad esempio, eliminare gli elementi non completati. In questi casi è meglio usare l'ambito elements.not_finishede lo manterrai comunque ActiveRecord::Relation.

Ma a volte questa condizione è più complessa. Estrarre tutti gli elementi che non sono finiti, che sono stati prodotti nelle ultime 4 settimane e sono stati controllati. Per evitare di creare nuovi ambiti è possibile filtrare in un array e quindi riconvertire. Tieni presente che esegui comunque una query su DB, veloce poiché ricerca per idma è comunque una query.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.