2

My application is on Rails 2.

I have 2 models, joined by a has_and_belongs_to_many relation : Message and Conversation. So I have 3 tables, messages, conversations and conversations_messages.

Is it possible to load in one unique query both objects Message and Conversation, without using the :include option ? The :include option is impossible because my query depends of some external params.

Example :

I can do a query from Message to have a couple of (Message,Conversation) per line

messages = Message.find(:all,
  :select => "messages.*, conversations.*",
  :conditions => some_conditions_depending_of_external_variables
  :group => "messages.id"
)

The resulting table looks like :

`messages`.id | `messages`.field | `conversations`.id | `conversations`.field
--------------+------------------+--------------------+------------------
1             | ...              | 10                 | ...
--------------+------------------+--------------------+------------------
2             | ...              | 15                 | ...
--------------+------------------+--------------------+------------------
3             | ...              | 10                 | ...
--------------+------------------+--------------------+------------------
4             | ...              | 20                 | ...

After this query, my variable messages contains a set of AR Messages elements, but the conversations fields are not loaded as an AR Conversation.

I want to be able to call a message.linked_conversation to have the conversation of the same line, as an AR object.

Is there any method to do this without re-loading the objects ?

pierallard
  • 3,326
  • 3
  • 21
  • 48
  • Not quite what you are looking for, but to do it in two queries without the use of `includes` and reloading of messages: http://stackoverflow.com/questions/12561688/how-to-eager-load-association-for-an-array-of-model-records – lulalala Jul 29 '13 at 08:48

1 Answers1

1

It is possible.

You have to manually do something that :include does internally. It means integrating with AR internal API, which is not something you normally wish to do - it can break at any time when you're upgrading Rails.

This means you should:

  1. instantiate Conversation objects manually from query result
  2. attach loaded Conversation objects to the association in Message objects

Altough I suggest splitting into two queries, so that the you load Conversation objects through a second query - based on the conversation ids you get in the first query. It is better for performance in most cases. After that you just need attach loaded objects into association of Message objects.

To give you a rough example of the logic:

# complex query to load messages and conversation ids
messages = Message.find(...)
# parse the loaded conversation ids out of resulting Message objects
con_ids = messages.map {...}
# load conversation objects through 2nd query
conversations = Conversations.find(con_ids)
# group conversations by message id
con_by_message = conversations.group_by(&:message_id)
# attach conversations into associations of Message objects
messages.each {|m|
  m.conversations.target = con_by_message[m.id] || []
}

This is something that worked for has_many associations in Rails. I can't tell you if setting association.target is enough for habtm associations - you should digg up that in the source...

jurglic
  • 3,599
  • 1
  • 17
  • 13
  • Sure, that's what I'm doing actually. But I'm afraid about some Rails treatment on big datas (like group_by)... This is why I was looking for some function of ActiveRecord, which can instanciate an already-existing object. `new` can not work (it will be a new_record) ; `create` either (will create an other objet)... – pierallard Jul 29 '13 at 09:52
  • Try looking at what Rails is doing... https://github.com/rails/rails/blob/2-3-stable/activerecord/lib/active_record/base.rb If you could do something that #find_by_sql does: connection.select_all(sanitize_sql(sql), "#{name} Load").collect! { |record| instantiate(record) } – jurglic Jul 29 '13 at 10:04