I have two models: Conversation and Message. A message belongs_to a conversation, and a conversation has_many messages:
# conversation.rb
class Conversation < ActiveRecord::Base
has_many :messages
end
# message.rb
class Message < ActiveRecord::Base
belongs_to :conversation
end
I have an interface that is going to list all the conversations, but I also want to include the “most recent” message associated with the conversation through eager-loading.
Do I need to create a new has_one or belongs_to relationship between the conversation and the message that will track the most recent message? Or is there a way I can do this through a scope using joins/includes?
Edit: The problem is that I don’t want a SQL query to be executed for every iteration of this loop:
# controller
@conversations = Conversation.all
# view
@conversations.each do |conversation|
most_recent_message = conversation.messages.last
# ...
end
Edit 2: I also tried to do the following, unsuccessfully. The goal was to add a last_message_id column to the conversations table, allowing it to reference the last added message.
# migration
add_column :conversations, :last_message_id, :integer
# conversation.rb
belongs_to :last_message, :class_name => 'Message'
# message.rb
after_create :set_last_message_on_conversation
def set_last_message_on_conversation
self.conversation.last_message = self
self.conversation.save!
end
However, that results in a SQL error since the last_message_id is null when the conversation is created. (I was hoping that it would all take place in a transaction, and that the last_message_id would be updated before the transaction completed, but I think it’s a MySQL/MyISAM issue where transactions are not used.)
ActiveRecord::StatementInvalid: Mysql::Error: Column ‘last_message_id’ cannot be null: INSERT INTO `conversations` (`last_message_id`, `updated_at`, `created_at`) VALUES (NULL, ‘2011-05-31 16:57:11’, ‘2011-05-31 16:57:11’)
You can use another association:
Be warned that
last_messageandmessagesand considered independent associations. If you useconversation.messages, the messages will be loaded from the database. And if you change an attribute oflast_message, it won’t affectmessages.lastuntil reload.Edit: Actually, according to the log, ActiveRecord still loads all the conversations’ messages. Probably because it can’t “LIMIT 1” like it does when it’s not eager loaded.
So in terms of SQL requests, this solution seems to be equivalent to simply using
Conversation.includes(:messages). But maybe Rails doesn’t process the extra results when using thehas_oneassociation.