8

I'm having a hard time getting the updated_at column to update on a Casino model that has_many Amenities through CasinoAmenity. The casino's updated_at column gets updated properly when adding amenities, but not when removing them.

Here's my (simplified) models:

casino.rb

class Casino < ActiveRecord::Base
  has_many :amenity_casino, :dependent => :destroy
  has_many :amenities, :through => :amenity_casino
end

amenity_casino.rb

class AmenityCasino < ActiveRecord::Base
  belongs_to :casino, :touch => true
  belongs_to :amenity, :touch => true
end

amenity.rb

class Amenity < ActiveRecord::Base
  has_many :amenity_casinos, :dependent => :destroy
  has_many :casinos, :through => :amenity_casinos
end

Now to the console. Checking the initial updated_at time.

> Casino.find(5).updated_at
=> Wed, 30 Jan 2013 00:30:04 UTC +00:00

Adding an amenity to the casino.

> Casino.find(5).amenities << Amenity.first
=> [#<Amenity id: 1, name: "asdf", weight: "", created_at: "2012-11-10 02:29:14", updated_at: "2013-01-30 01:29:14", description: "asdf">]

Confirming the association was saved to the database

> Casino.find(5).amenities
=> [#<Amenity id: 1, name: "asdf", weight: "", created_at: "2012-11-10 02:29:14", updated_at: "2013-01-30 01:29:14", description: "asdf">]

We can see that the updated_at time for that casino has changed, as expected.

> Casino.find(5).updated_at
=> Wed, 30 Jan 2013 01:29:14 UTC +00:00

Now let's remove that amenity from the casino.

> Casino.find(5).amenities = []
=> []

Now making sure it hit the database fine

> Casino.find(5).amenities
=> []

Now let's check the updated_at column for the casino...

> Casino.find(5).updated_at
=> Wed, 30 Jan 2013 01:29:14 UTC +00:00

As you can see, it's the same as before we removed the amenity from the casino. I've also tried a before_destroy filter on AmenityCasino to touch the casino, but that didn't seem to do the trick.

I'd love to hear if anyone has any solutions here. If it matters, I'm using ActiveAdmin to manage everything.

Rails version is 3.2.11

tereško
  • 58,060
  • 25
  • 98
  • 150
RFC1337
  • 167
  • 1
  • 7

2 Answers2

10

First, you're missing an 's' at the end of amenity_casino in two places in your Casino model.

Should be like this:

class Casino < ActiveRecord::Base
  has_many :amenity_casinos, :dependent => :destroy
  has_many :amenities, :through => :amenity_casinos
end

That won't solve the problem here, though. Referring to this (somewhat old) SO answer, it looks like the problem is that join table entries are deleted, not destroyed, and deletes don't trigger the touch method, thus updated_at is not updated.

To get around that, you can add an :after_remove option to your association and pass a proc which calls touch on the model itself:

has_many :amenity_casinos, :dependent => :destroy
has_many :amenities, :through => :amenity_casinos,\
         :after_remove => proc { |a| a.touch }

I've tested this and it seems to work:

Casino.find(5).updated_at
#=> Wed, 30 Jan 2013 03:21:29 UTC +00:00
Casino.find(5).amenities << Amenity.first
#=> [#<Amenity id: 1, name: nil, created_at: "2013-01-30 02:48:49", updated_at: "2013-01-30 03:25:23">]
Casino.find(5).amenities
#=> [#<Amenity id: 1, name: nil, created_at: "2013-01-30 02:48:49", updated_at: "2013-01-30 03:25:23">]
Casino.find(5).updated_at
#=> Wed, 30 Jan 2013 03:25:23 UTC +00:00
Casino.find(5).amenities = []
#=> []
Casino.find(5).updated_at
#=> Wed, 30 Jan 2013 03:27:33 UTC +00:00

You can see that the timestamp has changed. You'll have to do the same thing for your Amenity model if you want to have the updated_at attribute updated on that one as well.

Community
  • 1
  • 1
Chris Salzberg
  • 27,099
  • 4
  • 75
  • 82
  • Thanks for the answer! I'll give this a shot tomorrow and let everyone know how it goes. I had a feeling it was due to a delete instead of a destroy, but I wasn't sure how to either change that behavior, or work around it. Do you think the rails core team would be interested in changing the functionality to touch on delete in this case (or change it to a destroy) in order to fulfill the "expected" result here? – RFC1337 Jan 30 '13 at 04:12
  • Yeah I thought of that too. To be honest, I don't know enough about the internals to know if there's a good reason why they did it this way. But it's worth posting an issue on the github page to see what they think. – Chris Salzberg Jan 30 '13 at 04:31
  • This worked beautifully. Thanks again! I hope it helps someone else in the future as well. – RFC1337 Jan 30 '13 at 23:19
0

Before I say this I have to note that I am not sure whether this is valid in Rails 3.2 , but I am sure it is valid in Rails 4+

Now I know this has been answered but just for a future reference, instead of

> Casino.find(5).amenities = []

you can use the destroy_all method which will destroy all the join_model records and touch the associations (if you have that option listed like in your example) and also perform the appropriate callbacks (ones for destroy) on the join_model (in the example AmenityCasino)

> Casino.find(5).amenities.destroy_all
Vlatko Ristovski
  • 111
  • 1
  • 11