Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to touch a HABTM relation

If you have 2 models, Video and Category and they have a "has_and_belongs_to_many" relation with each other, how do you perform a touch to invalidate the cache when one of them changes?

You can't put "touch" on them like you can with a one-to-many relation. Now when i change a category name, the videos that belong to that category don't know about the change until i invalidate the cache. My View Templates show the name of the Category for each Video.

like image 457
Drazen Avatar asked May 30 '13 00:05

Drazen


2 Answers

On the model that you want to update you can do something like this:

class Video < ActiveRecord::Base
  has_and_belongs_to_many :categories,
                          after_add: :touch_updated_at,
                          after_remove: :touch_updated_at

  def touch_updated_at(category)
    self.touch if persisted?
  end

end

Now, whenever a category is added to or removed from a video, the video's updated_at timestamp will get updated. You can do the same thing on the category class if you want categories to update when videos are added to or removed from them.

like image 60
nates Avatar answered Nov 05 '22 19:11

nates


Touch can be used only in two case.

On a record

category = Category.first
category.touch

On a belongs_to relation

belongs_to :category, :touch => true

So if you want to use it on a HABTM relation, I'm afraid that you'll have to do it manually. It could be something like:

class Category
  before_save :touch_videos

  def touch_videos
    videos.touch
  end
end

class Video
  def self.touch
    update_attributes(:updated_at => Time.now)
    # or
    each { |video| video.touch } # Make a proper touch
  end
end

Be aware that if you want to allow Video to touch categories as well, you'll have to find a way to avoid "circular" updates.

each & find_each

If you prefer each over update_attributes, use find_each to load records by batch. This will avoid loading all records at the same time in memory and potentially crash the application. If you don't have 10k records, you probably won't see any difference, but as your table grows, it will get slower or even break if you use each.

find_each(batch_size: 2000) { |video| video.touch }
like image 14
basgys Avatar answered Nov 05 '22 19:11

basgys