2 votes

ActiveRelation qui nécessite des jointures imbriquées utilisant des scopes

Je suis nouveau sur les rails. Je m'éclate. L'API de requête me pose cependant quelques quelques problèmes. J'ai zoomé et fait beaucoup de choses très rapidement, mais c'est la première fois que je passe des heures à essayer de la comprendre. Ce n'est pas comme tout ce que j'ai utilisé auparavant - SQL normal, ou Hibernate, ou autre.

Le modèle que j'ai est assez simple.

  • Un PrivateMessage a plusieurs destinataires
  • Un destinataire a un destinataire (qui est de classe Utilisateur)
    • le destinataire a également des champs pour "is_read" et "is_deleted".

Mon objectif est de construire une requête qui trouve tous les fichiers non lus et non supprimés. pour un utilisateur donné. Pour ce faire, nous devons joindre 'private_messages' à 'recipients'... et ensuite 'recipients' à à 'users'.

Voici le code du modèle d'utilisateur correspondant :

has_many :sent_messages, :class_name => 'PrivateMessage', :foreign_key => 'sender_id'
has_many :recipient_of_messages, :class_name => 'Recipient', :foreign_key => 'receiver_id'

scope :by_id, lambda { |id| where(:id => id) } 

Mon modèle de destinataire a le code pertinent suivant :

belongs_to :receiver, :class_name => 'User', :foreign_key => "receiver_id"
belongs_to :private_message

scope :unread, where(:is_read => false).where(:is_deleted => false)
scope :by_receiver_id, lambda { |id| Recipient.joins(:receiver).merge(User.by_id(id)) }
scope :unread_by_receiver_id, lambda { |id| unread.by_receiver_id(id) }

Lorsqu'elle est testée de manière isolée, elle fonctionne à 100%.

Cependant, lorsque je code les requêtes par message privé, je rencontre des problèmes.

belongs_to :sender, :class_name => 'User'
has_many :recipients, :class_name => 'Recipient'

scope :sorted, order("private_messages.created_at desc")
scope :non_deleted, where(:is_deleted_by_sender => false)
scope :non_deleted_by_sender_id, lambda { |id| sorted.non_deleted.joins(:sender).merge(User.by_id(id)) }

# this scope does not work
scope :non_deleted_by_receiver_id, lambda { |id| sorted.joins(:recipients).merge(Recipient.by_receiver_id(id)) }
scope :newest, sorted.limit(3)

# this scope does not work either
scope :newest_unread_by_receiver_id, lambda { |id| newest.joins(:recipients).merge(Recipient.unread_by_receiver_id(id)) }

Lorsque j'essaie d'utiliser "newest_unread_by_receiver_id" ou "non_deleted_by_receiver_id", j'obtiens l'exception suivante :

ActiveRecord::ConfigurationError: Association named 'receiver' was not found; perhaps you misspelled it?

Cela n'a pas beaucoup de sens pour moi... parce que si le nom était mal orthographié mal, pourquoi n'échoue-t-il pas lorsque je teste l'isolation ?

Quelqu'un peut-il m'aider ? Celui-ci me rend fou. Dans des moments comme ça, j'ai juste envie de programmer en sql complet ou en Hibernate QL pour pouvoir en finir avec ça :(

Si j'aborde le problème de manière totalement erronée, j'apprécierais que vous me le fassiez savoir également. J'avais l'impression que l'utilisation des scopes et d'ActiveRelation était la voie à suivre dans Rails 3.1.

Merci

2voto

Heikki Points 7416

J'utiliserais probablement quelque chose comme ça. J'ai gardé les scopes séparés pour plus de clarté.

Modèles (renommé PrivateMessage -> Message et Recipient -> MessageCopy) :

class User < ActiveRecord::Base
  has_many :sent_messages, :class_name => "Message", :foreign_key => :sender_id
  has_many :sent_message_copies, :through => :sent_messages, :source => :message_copies
  has_many :received_messages, :through => :received_message_copies, :source => :message
  has_many :received_message_copies, :class_name => "MessageCopy", :foreign_key => :recipient_id
end

class Message < ActiveRecord::Base
  belongs_to :sender, :class_name => "User"
  has_many :message_copies
  has_many :recipients, :through => :message_copies
end

class MessageCopy < ActiveRecord::Base
  belongs_to :message
  belongs_to :recipient, :class_name => "User"
  scope :unread, where(:read => false)
  scope :undeleted, where(:deleted => false)
  scope :sent_to, lambda { |recipient| where(:recipient_id => recipient.id) }
end

Schéma (les migrations auraient pris trop de place ici) :

ActiveRecord::Schema.define(:version => 20110503061008) do
  create_table "message_copies", :force => true do |t|
    t.boolean  "read",         :default => false
    t.boolean  "deleted",      :default => false
    t.integer  "message_id"
    t.integer  "recipient_id"
  end
  create_table "messages", :force => true do |t|
    t.string   "title"
    t.integer  "sender_id"
  end
  create_table "users", :force => true do |t|
    t.string   "name"
  end
end

--edit

Exemple de requête utilisant des jointures et retournant des messages

Message.joins(:message_copies).where(:message_copies => {:read => false, :deleted => false, :recipient_id => 3})

Portée du message réutilisant des portées sur un autre modèle

scope :non_deleted_by_recipient, lambda { |recipient|
  joins(:message_copies).merge(MessageCopy.unread.undeleted.sent_to(recipient))
}

--edit2

Ce Railscast présente de beaux exemples de joints et de portées :

1voto

KimJongIl Points 406

Bien que vous sembliez avoir trouvé une réponse, je voudrais vous montrer comment j'ai procédé dans mon application :

message table:
id, sender_id, recipient_id, conversation_id, sender_deleted_at, recipient_deleted_at, title, body, (whatever you like)

conversation table:
id, sender_id, recipient_id, conversation_id, sender_deleted_at, etc.

class User < ActiveRecord::Base
  has_many :messages
  has_many :conversations
  has_many :sent_messages, :class_name => "Message", :foreign_key => "sender_id", 
                           :conditions => "sender_deleted_at IS NULL", :dependent => :destroy, :order => "created_at DESC"
  has_many :recieved_messages, :class_name => "Message", :foreign_key => "recipient_id", 
                            :conditions => "recipient_deleted_at IS NULL", :dependent => :destroy, :order => "created_at DESC"
  has_many :created_conversations, :class_name => "Conversation", :foreign_key => "sender_id"
  has_many :recieved_conversations, :class_name => "Conversation", :foreign_key => "recipient_id"
end

class Message < ActiveRecord::Base
  belongs_to :sender, :class_name => "User", :foreign_key => "sender_id"
  belongs_to :recipient, :class_name => "User", :foreign_key => "recipient_id"
  belongs_to :conversation

  before_create :assign_conversation
  after_create  :save_recipient, :set_replied_to, :send_receipt_reminder
end

class Conversation < ActiveRecord::Base
  belongs_to :sender, :class_name => "User", :foreign_key => "sender_id"
  belongs_to :recipient, :class_name => "User", :foreign_key => "recipient_id"

  has_many :messages

  scope :conversations_for_user, lambda {|user| {:conditions => ["sender_id = :user OR   recipient_id = :user", :user => user] }}
end

De cette façon, vous pouvez récupérer à peu près tout et cela vous permet également d'afficher les messages sous forme de conversations. Vous pouvez récupérer les messages non lus dans la conversation en cours, vous pouvez récupérer tous les messages pour une conversation ou un utilisateur donné, etc. etc.

De plus, vous n'avez qu'un seul enregistrement pour chaque message, ce qui semble être une bonne solution. Je peux aussi vous donner les méthodes supplémentaires au cas où vous ne voudriez pas les écrire vous-même.

Salutations Stefano

PS : ne faites pas de copier-coller de mon code, il peut contenir des fautes d'orthographe. Je n'ai pas eu le temps de vérifier, désolé.

0voto

Rob DiCiuccio Points 106

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X