8

Rails 4.2.5, Mongoid 5.1.0Query aggregata condizionale MongoDB su una relazione HABTM (Mongoid, RoR)?

ho tre modelli - Mailbox, Communication, e Message.

mailbox.rb

class Mailbox 
    include Mongoid::Document 
    belongs_to :user 
    has_many :communications 
end 

communication.rb

class Communication 
    include Mongoid::Document 
    include Mongoid::Timestamps 
    include AASM 

    belongs_to :mailbox 
    has_and_belongs_to_many :messages, autosave: true 

    field :read_at,  type: DateTime 
    field :box,   type: String 
    field :touched_at, type: DateTime 
    field :import_thread_id, type: Integer 
    scope :inbox, -> { where(:box => 'inbox') } 
end 

message.rb

class Message 
    include Mongoid::Document 
    include Mongoid::Timestamps 

    attr_accessor :communication_id 

    has_and_belongs_to_many :communications, autosave: true 
    belongs_to :from_user, class_name: 'User' 
    belongs_to :to_user, class_name: 'User' 

    field :subject, type: String 
    field :body, type: String 
    field :sent_at, type: DateTime 
end 

sto usando la gemma di autenticazione devise, che dà accesso alla current_user aiutante, che punti a l'utente corrente registrati nel

ho costruito una query per un controller che ha soddisfatto le seguenti condizioni:. Get 's mailbox, la cui communication' il current_user s vengono filtrati dal campo box, dove box == 'inbox'. E 'stato costruito come questo (e sta lavorando):

current_user.mailbox.communications.where(:box => 'inbox')

il mio problema arrises quando cerco di costruire su questa query. Desidero concatenare le query in modo tale da ottenere solo messages il cui messaggio last non proviene da current_user. Sono a conoscenza del metodo .last, che restituisce il record più recente. Sono venuto con la seguente query, ma non riesco a capire che cosa avrebbe bisogno di essere regolato in modo da farlo funzionare:

current_user.mailbox.communications.where(:box => 'inbox').where(:messages.last.from_user => {'$ne' => current_user})

Questa interrogazione produce il seguente risultato: undefined method 'from_user' for #<Origin::Key:0x007fd2295ff6d8>

Sono attualmente in grado di realizzare questo facendo quanto segue, che so essere molto inefficiente e vuole cambiare immediatamente:

mb = current_user.mailbox.communications.inbox

comms = mb.reject {|c| c.messages.last.from_user == current_user}

desidero spostare questa logica da rubino alla query di database vero e proprio. Grazie in anticipo a chiunque mi aiuti con questo, e per favore fatemi sapere se più informazioni sono utili qui.

+0

Non penso che ActiveRecord possa farlo per voi - la condizione basata su un aggregato (ultimo) è probabilmente troppo complessa. Potrebbe essere necessario ricorrere a SQL raw. – PJSCopeland

+0

C'è un errore? Si scrive .where (: messages.last.from_user => {'$ ne' => current_user}) '(** la condizione è sul commento **) ma in' current_user.mailbox.communications.reject {| c | c.last.from_user == current_user} '(** la condizione è in comunicazione **) –

+0

@PJSCopeland, mongo non è un database SQL –

risposta

0

Ok, quindi quello che sta succedendo qui è un po 'disordinato, e ha a che fare con quanto sia in grado di fare realmente il mongolo quando si fanno le associazioni.

In particolare, come vengono costruite le query quando "attraversa" tra due associazioni.

Nel caso della prima query:

current_user.mailbox.communications.where(:box => 'inbox') 

Che figata con mongoid, perché in realtà solo desugars in realtà 2 chiamate DB:

  1. Prendi la cassetta postale corrente per l'utente
  2. Mongolo crea un criterio direttamente contro la raccolta di comunicazioni, con una dichiarazione where che dice: usa l'id della casella postale dall'elemento 1 e filtra in box = posta in arrivo.

Ora, quando si arriva a vostra query successiva,

current_user.mailbox.communications.where(:box => 'inbox').where(:messages.last.from_user => {'$ne' => current_user}) 

è quando Mongoid comincia ad essere confuso.

Ecco il problema principale: Quando si utilizza "dove" si sta interrogando la raccolta in cui ci si trova. Non attraverserai le associazioni.

What the where (: messages.last.from_user => {'$ ne' => current_user}) in realtà sta facendo non controllando l'associazione dei messaggi. Ciò che Mongoid sta effettivamente facendo è cercare il documento di comunicazione per una proprietà che avrebbe un percorso JSON simile a: comunicazione ['messaggi'] ['ultimo'] ['from_user'].

Ora che sai perché, puoi ottenere ciò che desideri, ma richiederà un po 'più di sudore rispetto all'equivalente lavoro ActiveRecord.

Qui c'è di più del modo in cui si può arrivare a ciò che si vuole:

user_id = current_user.id 
communication_ids = current_user.mailbox.communications.where(:box => 'inbox').pluck(:_id) 
# We're going to need to work around the fact there is no 'group by' in 
# Mongoid, so there's really no way to get the 'last' entry in a set 
messages_for_communications = Messages.where(:communications_ids => {"$in" => communications_ids}).pluck(
    [:_id, :communications_ids, :from_user_id, :sent_at] 
) 
# Now that we've got a hash, we need to expand it per-communication, 
# And we will throw out communications that don't involve the user 
messages_with_communication_ids = messages_for_communications.flat_map do |mesg| 
    message_set = [] 
    mesg["communications_ids"].each do |c_id| 
    if communication_ids.include?(c_id) 
     message_set << ({:id => mesg["_id"], 
     :communication_id => c_id, 
     :from_user => mesg["from_user_id"], 
     :sent_at => mesg["sent_at"]}) 
    end 
    message_set 
end 
# Group by communication_id 
grouped_messages = messages_with_communication_ids.group_by { |msg| mesg[:communication_id] } 
communications_and_message_ids = {} 
grouped_messages.each_pair do |k,v| 
    sorted_messages = v.sort_by { |msg| msg[:sent_at] } 
    if sorted_messages.last[:from_user] != user_id 
    communications_and_message_ids[k] = sorted_messages.last[:id] 
    end 
end 
# This is now a hash of {:communication_id => :last_message_id} 
communications_and_message_ids 

Non sono sicuro che il mio codice è 100% (probabilmente è necessario controllare i nomi dei campi nei documenti per assicurarsi che io sto cercando tra quelli giusti), ma penso che tu abbia il modello generale.