渴望加载有很多关系的最新项目

时间:2011-05-31 00:36:14

标签: ruby-on-rails ruby-on-rails-3 has-many eager-loading

我有两个模型:ConversationMessage。消息属于对话,对话有很多消息:

# conversation.rb
class Conversation < ActiveRecord::Base
  has_many :messages
end

# message.rb
class Message < ActiveRecord::Base
  belongs_to :conversation
end

我有一个界面可以列出所有会话,但我也希望通过预先加载来包含与会话相关联的“最新”消息。

我是否需要在会话和将跟踪最新消息的消息之间创建新的has_one或belongs_to关系?或者有没有办法通过使用连接/包含的范围来实现这一点?

编辑:问题是我不希望在此循环的每次迭代中执行SQL查询:

# controller
@conversations = Conversation.all

# view
@conversations.each do |conversation|
  most_recent_message = conversation.messages.last
  # ...
end

编辑2:我还尝试执行以下操作,但未成功。目标是在会话表中添加last_message_id列,以便引用最后添加的消息。

# 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

但是,这会导致SQL错误,因为创建对话时last_message_id为null。 (我希望它会在一个事务中发生,并且last_message_id将在事务完成之前更新,但我认为这是一个没有使用事务的MySQL / MyISAM问题。)

  

ActiveRecord :: StatementInvalid:Mysql :: Error:列'last_message_id'不能为null:INSERT INTO`forversations`(`last_message_id`,`updated_at`,`created_at`)VALUES(NULL,'2011-05-31 16 :57:11','2011-05-31 16:57:11')

2 个答案:

答案 0 :(得分:1)

您可以使用其他关联:

# conversation.rb
class Conversation < ActiveRecord::Base
  has_many :messages
  has_one :last_message, :class_name => "Message", :order => "created_at DESC"
end

# controller
@conversations = Conversation.includes(:last_message)

# view
@conversations.each do |conversation|
  most_recent_message = conversation.last_messages
  # ...
end

警告last_messagemessages并考虑独立协会。如果使用conversation.messages,则将从数据库加载消息。如果您更改last_message的属性,则在重新加载之前不会影响messages.last

编辑:实际上,根据日志,ActiveRecord仍然会加载所有会话的消息。可能是因为它不能像它没有急切加载那样“限制1”。

因此,就SQL请求而言,此解决方案似乎等同于简单地使用Conversation.includes(:messages)。但是,使用has_one关联时,Rails可能不会处理额外的结果。

答案 1 :(得分:0)

在什么意义上你的意思是懒惰?只是在加载对话时没有加载最新的消息?试试这个:

class Conversation < ActiveRecord::Base
  has_many :messages, :order => "created_at"
end

然后你可以打电话

@conversation.messages.last

正在执行SQL,如

SELECT "messages".* FROM "messages" WHERE ("messages".conversation_id = 129) ORDER BY created_at DESC LIMIT 1

当你实际要求最后一条消息时。

重要:如果您在操作执行期间之前已经调用了@ conversation.messages,它可能已经加载了所有消息,因此在您调用@时可能无法再次访问数据库conversation.messages.last。因此,如果在@ conversation.messages和@ conversation.messages.last的调用之间添加了一条消息,那么您将无法获得真正的最后一个消息。但我想这可能还好吗?