views:

46

answers:

3

In my Rails app, I have a class with a has_many relationship. For the sake of efficiency, I want to do some direct SQL to update many rows in the database at once, and then I want to mark the has_many relationship as no longer valid. If later code accesses the has_many relationship, I want it to reload the data. But I obviously want to skip the SQL unless it is necessary.

So for example:

class Student
  has_many courses # may have a :condition clause or some such

  def some_method
    # For some reason, we want to change all courses in here, not
    # just a single row.
    ActiveRecord::Base.connection.execute("UPDATE courses SET location = #{new_location}")
    # Not sure if we'll later do anything with self.courses, so I need to invalidate
    # that relationship.  Could do self.courses.reload right here, but I don't want to
    # do the SQL if it isn't necessary; the cache only lasts until the end of the
    # current page request.
  end
end

I may well be missing something rather obvious. Some hypothetical self.courses.invalidate method.

+1  A: 

Expansion of what I couldn't say in 140 characters... You can avoid needing to invalidate relations by using some of Rails' convenience methods that are attached to an association. Let's say, for example, that I have a User and a Project model:

class Project < ActiveRecord::Base
  # Backing table has :id, :user_id, :name, and :description columns
end

class User < ActiveRecord::Base
  has_many :projects
end

user = User.first # Assuming you have some users already

# Creates a new project named "Some Project" with description "Some Description"
# and sets the project's user_id to user.id. Also handles in-memory user.projects
# updating.
user.projects.create(:name => "Some Project, :description => "Some Description")
David Ackerman
Definitely true, but if I'm updating, not creating, data, I end up iterating through the elements in the associating and calling save on each of them. With, say, 50 associated rows, that's 50 SQL statements, which is what I'm trying to get away from. Still, your example is the more common case so although it doesn't solve my problem, +1.
ChrisInEdmonton
A: 

You should have a look at the build in update_attribute methods which update via SQL but have the advantage of changing the local attributes.

Toby Hede
But does this not just update one or many columns on a single row? That's not going to help me. Perhaps I am reading things incorrectly, though, and there's some way to call update_attribute across all rows.
ChrisInEdmonton
+1  A: 

Not in their public API, but you could try the reset method on the AssociationCollection class.

Watching the log:

s = Student.first
s.courses       # Hits db
s.courses       # Hits cache 
s.courses.reset # No db
s.courses       # Hits db
tvongaza
This does exactly what I need. Thank you!
ChrisInEdmonton