views:

91

answers:

2

In my current rails app I'm pulling a lot of data from the database into memory so that I can start a long-running background task that I don't want to hit the database every 5 seconds.

This is all working great but I have some inelegant code that I'd like to get rid of if possible.

For an example, I have the situation where I have a User model and that User model can link to other User models in the form of Contacts, which in turn can link back to the first User model an so on. This is modelled as follows (thanks to Milan Novota for his earlier help): -

class User < ActiveRecord::Base
  has_many :contact_records, :foreign_key => :owner_id
  has_many :contacts, :through => :contact_records, :class_name => "User"
end

class ContactRecord < ActiveRecord::Base
  belongs_to :owner, :class_name => "User"
  belongs_to :user
end

So now I can type

user_a_contacts = user_a.contact_records
=> [user_b, user_c]

and get all the contacts for user a. Now let's say I want to get user b's contacts via user a's (can't think why I would but it's only an example!)

user_b_contacts = user_a_contacts.first.contact_records
=> [user_a, user_c]

Now I can get user a's name (in a convoluted, roundabout, never-use-in-the-real-world kind of way)

user_a_name = user_b_contacts.first.name
=> "Jamie"

So far, so good. Now I change user a's name

user_a.name = "Sandy"
=> "Sandy"

If accessing user a's name again as before from the database I get

user_a_name = user_b_contacts.first.name
=> "Sandy"

However, if I've eager loaded and kept this value in memory I get

user_a_name = user_b_contacts.first.name
=> "Jamie"

but doing the following gives me the right answer

user_a.name
=> "Sandy"

Fairly obviously, the eager-loading is creating different User objects for the original users and those eager-loaded from the ContactRecord objects.

So (finally!) my question is this: -

At the moment, I have bodged around this by using

@users.each.detect{|user| user == user_b_contacts.first}

(where @users is the eager-loaded list of User objects). Is there a way that I can update the eager-loaded User objects instead?

Please note: -

a) I am eager-loading using the following

User.find(:all, :include => [ {:contact_records => :contacts] } ])

b) Obviously this is not the actual code that I am working with but there would have been even more explanation and exposition in order to show that code and I think I've bored you enough! Trust me that the real example I am using DOES require this structure in order to work correctly, as bizarre as it might look! ;)

c) I may have introduced typos and errors when reconstructing the example but please ignore those. All I need to know is whether it is possible to reload the eager-loded Users without hitting the database.

Many thanks in advance!

UPDATE: As mentioned below, the model data will not be saved to the database until the very end of the process run so reloading will not work as the data in the database won't have changed.

+1  A: 

OK. I'm going to condense your question into a new question and answer that one. Forgive me if I've misunderstood and this is not what you are asking.

Question

If user is an ActiveRecord model where I have loaded an association contacts ahead of time with User.first(:include => :contacts), how do I refresh the contacts association when it has changed in the database?

Answer

You can invalidate the association cache in two ways that I know of. First you can do:

user.reload

which will reload the entire user model from scratch. A slightly less drastic way to do this is to do

user.clear_association_cache

which will not reload all of user, but will clear the cached associations.

Either way, the next time you do user.contacts it will load them afresh from the database.

Daniel Lucraft
A: 

Many thanks for your reply, Daniel. Whilst you've not quite got the right question you have made me realise that I missed out a crucial point! The database record will not have been updated as I'm not doing a save! at any point.

The gist of what I'm doing is pulling model data out into instance variables in order to speed up a background process. As such, the data that is loaded the first time round will not be saved back to the database until the very end of process run.

What I was hoping for was a way that you could specify when eager-loading that the value of one column relates to another model already loaded.

For instance,

User.find(:all, :include => [ {:contact_records => (:contacts as User)] } ])

or similar.

As far as I am aware there is no such syntax in Rails, however I'm hoping that I'm wrong! :)

Hope this makes it a bit clearer and thanks again for the reply.

Urf