ActiveRelation, который требует вложенных объединений с использованием областей - PullRequest
1 голос
/ 03 мая 2011

Я новичок в рельсах.Я на взводе.API запросов, однако, доставляет мне некоторые проблемы.Я очень быстро масштабирую и делаю много вещей, но впервые я потратил часы, пытаясь понять это.Это не то, что я использовал раньше - обычный SQL, Hibernate или что-то еще.

У меня довольно простая модель.

  • В PrivateMessage много получателей
  • Получатель имеет получателя (который относится к классу User)
    • получатель также имеет поля для 'is_read' и 'is_deleted'

Моя цель -построить запрос, который находит все непрочитанные и не удаленные личные сообщения для данного пользователя.Для этого нам нужно присоединить «private_messages» к «получателям» ..., а затем «получателей» к «пользователям».

Вот соответствующий код модели пользователя:

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) } 

Модель моего получателя имеет следующий соответствующий код:

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) }

При тестировании в отдельности это работает на 100%.

Однако, когда я кодирую запросы личных сообщений, я сталкиваюсь с проблемами.

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)) }

Когда я пытаюсь использовать 'newest_unread_by_receiver_id' или 'non_deleted_by_receiver_id', я получаю следующее исключение:

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

Это не имеет большого смысла для меня ... потому что еслиимя было написано неправильно, почему оно не дает сбой, когда я проверяю его изоляцию?

Может кто-нибудь помочь мне, пожалуйста?Этот сводит меня с ума.В такие моменты я просто хочу программировать в полном sql или Hibernate QL, чтобы я мог просто покончить с этим: (

Если я просто неправильно подхожу к проблеме, то я был бы признателен, если бывы просто дайте мне знать об этом. У меня сложилось впечатление, что использование областей и ActiveRelation было способом продвижения вперед в Rails 3.1.

Спасибо

Ответы [ 3 ]

1 голос
/ 03 мая 2011

Я бы, наверное, использовал что-то вроде этого. Я держал прицелы отдельно для ясности.

Модели (переименовано в PrivateMessage -> Сообщение и получатель -> 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

Схема (миграция заняла бы здесь слишком много места):

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

- редактирование

Пример запроса с использованием объединений, возвращающих сообщения

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

Объем сообщения, повторно использующий области действия на другой модели

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

- edit2

В этом Railscast есть хорошие примеры как объединений, так и областей действия:

0 голосов
/ 17 апреля 2012
0 голосов
/ 03 мая 2011

Несмотря на то, что вы, кажется, нашли ответ, я хотел бы показать вам, как я это сделал в моем приложении:

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

Таким образом, вы можете получать практически все, что угодно, а также отображать сообщения в виде разговоров. Вы можете получить непрочитанные сообщения в текущем разговоре, вы можете получить все сообщения для данного разговора или пользователя и т. Д. И т. Д.

Также у вас есть только одна запись для каждого сообщения, что кажется хорошим решением. Я также могу дать вам дополнительные методы на случай, если вы не захотите написать их самостоятельно.

С уважением Stefano

PS: не просто копируйте и вставляйте мой код, у меня могут быть некоторые орфографические ошибки. Извините, у меня не было времени на доказательство.

...