5

Sto trasferendo un'applicazione Rails a Rails 4.2. Questa app Rails contiene un codice SQL manuale piuttosto complesso nelle associazioni - in parte a causa dell'ottimizzazione del database (ad es. Sottoselezioni anziché JOINs), in parte a causa di un'alternativa non fattibile al momento della scrittura (Rails 3.0), in parte sicuramente a causa della mancanza di conoscenza (Spero, almeno, che sia facile da risolvere).Il porting complicato ha_molte relazioni con Rails> 4.1 (senza finder_sql)

Esempio: una classe InternalMessage. I messaggi possono essere inviati tra utenti (Destinatari di un messaggio interno e "cancellazioni" di messaggi, sono memorizzati in InternalMessagesRecipients, poiché possono esserci diversi) e possono essere letti, replicati, inoltrati e cancellati. L'associazione si presenta così:

class User < AR::Base 
    has_many :internal_messages, 
     :finder_sql => "SELECT DISTINCT(internal_messages.id), internal_messages.* FROM internal_messages " + 
      ' LEFT JOIN internal_messages_recipients ON internal_messages.id=internal_messages_recipients.internal_message_id' + 
      ' WHERE internal_messages.sender_id = #{id} OR internal_messages_recipients.recipient_id = #{id}', 
     :counter_sql => 'SELECT count(DISTINCT(internal_messages.id)) FROM internal_messages ' + 
      ' LEFT JOIN internal_messages_recipients ON internal_messages.id=internal_messages_recipients.internal_message_id' + 
      ' WHERE internal_messages.sender_id = #{id} OR internal_messages_recipients.recipient_id = #{id}' 
    # ... 
end 

La parte fondamentale è la "O" clausola alla fine - con questa associazione voglio ottenere sia messaggi ricevuti e inviati, che sono uniti con la tabella utente separatamente:

has_many :sent_messages, -> { where(:sender_deleted_at => nil) }, :class_name => 'InternalMessage', :foreign_key => 'sender_id' #, :include => :sender 
    has_many :internal_messages_recipients, :foreign_key => 'recipient_id' 
    has_many :rcvd_messages, :through => :internal_messages_recipients, :source => :internal_message, :class_name => 'InternalMessage' 

poiché un InternalMessage potrebbe avere più destinatari (e può anche essere inviato al mittente stesso).

Q: Come posso trasferire questo finder_sql a una definizione di Rails 4.2 has_many?

risposta

4

Aggiornamento

ho imparato qualche tempo fa che questo non ha senso. Una relazione has_many deve avere connessioni injective almeno in una direzione, quindi un "OR" in una clausola SQL non ha senso. In che modo l'operazione CREATE deve decidere quale condizione soddisfare per creare un nuovo record? Questa relazione viene letta solo per definizione e quindi non è una relazione has_many.

In questo caso, un metodo di classe semplice (o ambito) sarebbe la risposta giusta anziché has_many. Per concatenare i risultati di diverse query usare qualcosa come

def internal_messages 
    InternalMessage.where(id: sent_message_ids + received_message_ids) 
end 

per mantenere il chainable oggetto risultante (cioè @user.internal_messages.by_date etc.)

+0

Ovviamente, questo è inferiore se si desidera la memoizzazione dei risultati, in particolar modo lavorano con il 'reload'. C'è un modo per ottenere binari per mantenere queste relazioni senza la chiave esterna? – duane

3

Passare il proc che contiene la stringa SQL come ambito.

has_many :internal_messages, -> { proc { "SELECT DISTINCT(internal_messages.id), internal_messages.* FROM internal_messages " + 
     ' LEFT JOIN internal_messages_recipients ON internal_messages.id=internal_messages_recipients.internal_message_id' + 
     ' WHERE internal_messages.sender_id = #{id} OR internal_messages_recipients.recipient_id = #{id}' } } 
+0

OK, grazie. Che ne dici di counter_sql? Non richiesto più? Con Rails 3 ho usato per ottenere errori SQL quando non si utilizza counter_sql a causa di DISTINCT. – Jens

+1

Non l'ho mai fatto da solo ma [questa risposta StackOverflow] (http://stackoverflow.com/questions/22988321/replacement-for-has-many-counter-sql-in-rails-4-1) dice che puoi definire il tuo metodo di conteggio all'interno di un blocco assegnato a has_many. Quindi immagino che tu possa fare qualcosa come 'def count proxy_association.owner.class.count_by_sql (" YOUR SQL HERE "); fine' nel blocco. Aggiornamento – tyamagu2

+4

: Rails 4.2.5.1, ruby ​​2.3.1p112 'has_many: talk, -> {proc {" SOME SQL "}}' NoMethodError: metodo non definito 'except' per # Dimitri