1

Learning how to create foreign keys at the database, I had these two models:

class Bar < ApplicationRecord
  has_many :foos
end
# and
class Foo < ApplicationRecord
  belongs_to :bar
end

Then, I created the following foreign key:

add_foreign_key :foos, :bars, on_delete: :cascade

Therefore, if I delete a bar it should also delete the foos that referenced it (and it really did). But I wanted to create a test for it, so I did this:

foo = create(:foo) # a factory that creates a foo with a bar parent
bar = foo.bar
bar.destroy
expect(foo).to_not be_persisted

And surprisingly it didn't work. And then instead of expect(foo).to_not be_persisted I tried this just to be sure:

expect(Foo.all.size).to eq 0
expect(Foo.count).to eq 0
expect(Foo.all).to_not include foo

And it worked! So my question is: Why be_persisted? didn't work? Shouldn't it verify if foo is actually saved at the database?

Rafael Costa
  • 1,291
  • 2
  • 13
  • 30
  • 1
    your reference in the `foos` table is actually stating that 'when I delete a `foo` object cascade it to delete the referenced `bar` object - **not** the other way around. Deleting the `bar` object will not delete it's parent `foo` object. – David Dec 27 '16 at 10:05
  • Actually, what you said is not true.. :P [here](http://api.rubyonrails.org/v4.2.0/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_foreign_key) is how it works. – Rafael Costa Dec 27 '16 at 11:19
  • Erm actually it is - where in that the link you gave does it indicate that the cascade works the other way? You should google for `sql foreign key cascade delete` - for example http://stackoverflow.com/questions/13444859/sql-on-delete-cascade-which-way-does-the-deletion-occur - the behaviour you are experiencing also indicates that what I'm stating is true. – David Dec 27 '16 at 11:48
  • [Here](http://www.mysqltutorial.org/mysql-on-delete-cascade/) man, rooms have a FK to buildings, then if you delete a building you delete the rooms related to it. If you look at the other [link](http://api.rubyonrails.org/v4.2.0/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_foreign_key) I sent you, you will see that I am creating the FK on the foos table pointing to bars. Therefore, if a delete a bar I should also cascade a delete to foos. And ultimately I know what I did is right 'cause I saw it working in the database. – Rafael Costa Dec 27 '16 at 14:33
  • Exactly the point I was making ... you delete the building (the parent entity) and the child entities (the rooms) are deleted too... that's what the cascading is doing ... it does not work the other way around ... Deleting a child entity does not delete the parent. Could you write out the exact steps you did to show 'this working in the database' - I am interested. – David Dec 27 '16 at 14:40
  • Oh! I see.. you understood it wrongly. Foo in my case is the room, and bar is the building. – Rafael Costa Dec 27 '16 at 14:48
  • 1
    hmm i see ... then shouldn't foo be a child of bar then ? so rather than `foo.bar` ... shouldn't it be `bar.foo` - what does your actual models look like? – David Dec 27 '16 at 14:52
  • 2
    I see you edited the question, it does make more sense now ... ok in addition, you need to be aware that by using SQL cascade rules ActiveRecord won't be aware of it ... if you had specified in the model itself something like `has_many: :bars, dependent: :destroy` instead of using the FK cascade rule .. then active record would have cascaded the delete and been aware of it. Try something like `expect(Bar.find(bar.id)).to be_nil` instead - or maybe `bar.reload` may give you a nil object too. – David Dec 27 '16 at 15:33
  • Hey @David, I realize it was kind of confusing and I was confused as well. So, I reviewed the code and re-wrote the question. However, I think your last comment pretty much answers it. If you create an answer with it, I will accept. – Rafael Costa Dec 27 '16 at 19:28

1 Answers1

1

You need to be aware that by using SQL cascade rules ActiveRecord won't be aware of it.

If you had specified in the model itself something like

has_many: :foos, dependent: :destroy

instead of using an SQL FK cascade rule .. then ActiveRecord would have cascaded the delete and been aware of it.

In your rspec test after doing bar.destroy do something like expect(Foo.find(foo.id)).to be_nil instead to test the existence of the child record - or maybe foo.reload may give you a nil object too.

David
  • 3,510
  • 3
  • 21
  • 39