6

Ho i seguenti modelli. Gli utenti hanno UserActions e una possibile UserAction può essere una ContactAction (UserAction è un polimorfismo). Ci sono altre azioni come loginAction ecc QuindiRuby on Rails 3: Combina i risultati di più has_many o has_many_through associazioni

 
class User < AR::Base 
    has_many :contact_requests, :class_name => "ContactAction" 
    has_many :user_actions 
    has_many_polymorphs :user_actionables, :from => [:contact_actions, ...], :through => :user_actions 
end 

class UserAction < AR::Base 
belongs_to :user 
belongs_to :user_actionable, :polymorphic => true 
end 

class ContactAction < AR::Base 
belongs_to :user 
named_scope :pending, ... 
named_scope :active, ... 
end 

L'idea è che un ContactAction unisce due utenti (con altre conseguenze all'interno della app) e ha sempre un ricezione e l'invio di un finale. Allo stesso tempo, ContactAction può avere stati diversi, ad es. scaduto, in sospeso, ecc.

Posso dire @user.contact_actions.pending o @user.contact_requests.expired per elencare tutte le richieste in sospeso/scadute che un utente ha inviato o ricevuto. Funziona bene

Quello che vorrei ora è un modo per unire entrambi i tipi di ContactAction. Cioè @user.contact_actions_or_requests. Ho provato la seguente:

 
class User 

def contact_actions_or_requests 
    self.contact_actions + self.contact_requests 
end 

# or 
has_many :contact_actions_or_requests, :finder_sql => ..., :counter_sql => ... 

end 

ma tutti questi hanno il problema che non è possibile utilizzare finders o named_scopes aggiuntive rispetto all'associazione, ad esempio @user.contact_actions_or_requests.find(...) o @user.contact_actions_or_requests.expired.

Fondamentalmente, ho bisogno di un modo per esprimere un'associazione 1: n che ha due percorsi diversi. Uno è User -> ContactAction.user_id, l'altro è User -> UserAction.user_id -> UserAction.user_actionable_id -> ContactAction.id. E poi unire i risultati (ContactActions) in un'unica lista per ulteriori elaborazioni con named_scopes e/o finder.

Dal momento che ho bisogno di questa associazione in decine di posti letteralmente, sarebbe un grosso problema scrivere (e mantenere!) SQL personalizzato per ogni caso.

Preferirei risolvere questo problema in Rails, ma sono aperto anche ad altri suggerimenti (ad esempio una procedura di PostgreSQL 8.3 o qualcosa simile). La cosa importante è che alla fine, posso usare le funzioni di convenienza di Rails come con qualsiasi altra associazione e, cosa più importante, anche nidificarle.

Qualsiasi idea sarebbe molto apprezzata.

Grazie!


Per fornire una sorta-di risposta alla mia domanda:

Io probabilmente risolvere questo utilizzando una vista di database e aggiungere le associazioni appropriate a seconda delle necessità. Per quanto sopra, posso

  • utilizzare SQL in finder_sql per creare la vista,
  • nome "contact_actions_or_requests",
  • modificare la clausola SELECT per aggiungere una colonna user_id,
  • aggiungere un app /models/ContactActionsOrRequests.rb,
  • e quindi aggiungere "has_many: contact_actions_or_requests" a user.rb.

Non so come gestirò l'aggiornamento dei record ancora - questo sembra non essere possibile con una vista - ma forse questo è un primo inizio.

+0

Attualmente mi avere un caos di chiamate find_by_sql sparse per l'app. Devo ancora aggiornare questo per usare ARel e soprattutto il metodo #arel_table (vedi commento sotto). Quando lo faccio pubblicherò i miei risultati qui. – Jens

+0

Come hai affrontato questo alla fine? –

+0

Uso ancora finder_sql e counter_sql, ma ho estratto le stringhe SQL in un metodo di classe che includo poi tramite un proc. Questo è molto più pulito di prima, anche se devo ripensarci ora che ci stiamo spostando su Rails 4 e finder_sql è deprecato. – Jens

risposta

2

Il metodo che stai cercando è unire. Se hai due ActiveRecord :: Relations, r1 e r2, puoi chiamare r1.unire (r2) per ottenere un nuovo oggetto ActiveRecord :: Relation che combina i due.

Se questo funziona per voi dipende in gran parte da come sono impostati i propri ambiti e se è possibile modificarli per produrre un risultato significativo. Diamo un'occhiata ad alcuni esempi:

Supponiamo di avere un modello di pagina. Ha la created_at normale e updated_at attributi, così abbiamo potuto avere scopi come: : aggiornato -> {dove ('! Created_at = updated_at')} : not_updated -> {dove ('created_at = updated_at')}

Se si tira questo fuori del database si otterrà:

r1 = Page.updated # SELECT `pages`.* FROM `pages` WHERE (created_at != updated_at) 
r2 = Page.not_updated # SELECT `pages`.* FROM `pages` WHERE (created_at = updated_at) 
r1.merge(r2) # SELECT `pages`.* FROM `pages` WHERE (created_at != updated_at) AND (created_at = updated_at) 
=> [] 

quindi il fatto di combinare i due rapporti, ma non in modo significativo. Un altro:

r1 = Page.where(:name => "Test1") # SELECT `pages`.* FROM `pages` WHERE `pages`.`name` = 'Test1' 
r2 = Page.where(:name => "Test2") # SELECT `pages`.* FROM `pages` WHERE `pages`.`name` = 'Test2' 
r1.merge(r2) # SELECT `pages`.* FROM `pages` WHERE `pages`.`name` = 'Test2' 

Quindi, potrebbe funzionare per voi, ma forse no, a seconda della situazione.

Un altro, e raccomandato, modo di fare questo è quello di creare un nuovo ambito su di voi il modello:

class ContactAction < AR::Base 
    belongs_to :user 
    scope :pending, ... 
    scope :active, ... 
    scope :actions_and_requests, pending.active # Combine existing logic 
    scope :actions_and_requests, -> { ... } # Or, write a new scope with custom logic 
end 

che unisce i diversi tratti si desidera raccogliere in una query ...

+0

La domanda originale è stata posta quando stavamo ancora usando Rails 2.3 che non aveva ARel e quindi non permetteva di concatenare le chiamate find_by_sql. Ora, con ARel, è effettivamente possibile (usando #arel_table) concatenare le query usando "OR" (che è necessario in questo caso). La tua soluzione usa ancora il concatenamento "AND", che non è quello che mi serve ... – Jens

+0

Puoi capire la mia confusione riguardo al "continuare ad usare Rails 2.3" poiché la domanda è contrassegnata con Rails tre. L'opzione di fornire un ambito separato che combina manualmente le query è comunque valida, a meno che non vi siano dei vincoli che devi ancora specificare? –

+0

Sì. Mi dispiace per quello Stavo cercando una motivazione per eseguire effettivamente l'aggiornamento. :) Per quanto riguarda il tuo suggerimento, non 'pending.active' seleziona tutte le voci che sono in sospeso e attive (invece di" o ")? Gli ambiti concatenati - nella mia esperienza - utilizzano l'AND logico, che non funziona qui. Anche l'uso di arel_table (nel mio caso) è difficile poiché ho bisogno di utilizzare le funzioni di Postgres nella query che (afaik) non può essere espressa in ARel. Ma continuerò a provare e metterò la mia soluzione qui una volta che l'ho trovata. – Jens