Ruby destroy is not working? Or objects still present?

783 views Asked by At

A plan has many plan_dates. This is a trivial example from my console

a = Plan.create
a.plan_dates.create( ddate: Date.today)
a.plan_dates.create( ddate: Date.today + 1.days )
a.plan_dates.create( ddate: Date.today + 2.days )
a.plan_dates.count
# => 3
a.plan_dates.each { |pd| puts pd.ddate }
# => 2015-06-06 00:00:00 UTC
# => 2015-06-07 00:00:00 UTC
# => 2015-06-08 00:00:00 UTC

When I destroy a plan_date, count keeps track of it, but each does not:

a.plan_dates.find_by_ddate(Date.today.to_datetime).destroy
a.plan_dates.count
# => 2
a.plan_dates.each { |pd| puts pd.ddate }
# => 2015-06-06 00:00:00 UTC
# => 2015-06-07 00:00:00 UTC
# => 2015-06-08 00:00:00 UTC
a.plan_dates[0].ddate
# => Sat, 06 Jun 2015 00:00:00 UTC +00:00 
a.plan_dates[1].ddate
# => Sun, 07 Jun 2015 00:00:00 UTC +00:00 
a.plan_dates[2].ddate
# => Mon, 08 Jun 2015 00:00:00 UTC +00:00 

I get that Ruby removes records from the db but freezes objects, so they're still there, although incidentally:

a.plan_dates.each { |pd| puts pd.frozen? }
# => false
# => false
# => false

I would have expected it to be true for the first pd that I destroyed. Just like how:

a.destroy
a.frozen?
# => true

What's the method to use to iterate over only the existing records? Something like each_non_frozen. Also, how is the object actually deleted from the array? I call methods with specific plan_dates like a.plan_date[0], and I'd either want to see nil or Sun, 07 Jun 2015 00:00:00 UTC +00:00 returned.

1

There are 1 answers

1
twonegatives On BEST ANSWER

First, let me explain the behaviour with the array preserving destroyed elements. This case is possible due to rails caching mechanism.

a.plan_dates.find_by_ddate(Date.today.to_datetime).destroy
# DELETE FROM "plan_dates" WHERE ...
a.plan_dates.count
# SELECT COUNT(*) FROM "plan_dates"  WHERE ...
a.plan_dates.each { |pd| puts pd.ddate }

As you see, the first two rows initiate SQL queries. But the last one does not! It uses the cached array from the previous request to plan_dates.each. More on that can be found at controlling caching (3.1) section of reference: ActiveRecord Associations.

Here is how you force your array to fetch data from the database again:

a.plan_dates(true).each { |pd| puts pd.ddate }
# => 2015-06-07 00:00:00 UTC
# => 2015-06-08 00:00:00 UTC

# another way, with the same effect:
a.plan_dates.reload.each { |pd| puts pd.ddate }

As of freezing objects, Rails does freeze array elements which manually received destroy method call, but has no idea of what's going on when you call that on completely different object:

a.plan_dates.find_by_ddate(Date.today.to_datetime).destroy
# you have initiated SQL SELECT here!
# the result is not contained in initial array

This would work as you expect called with finders of Enumerable or Array rather then ActiveRecord:

a.plan_dates.find{|i| i.ddate == Date.today}.destroy
a.plan_dates.find{|i| i.ddate == Date.today}.frozen? # => true