Forum: Ruby on Rails What's the proper way to delete all associated children in one SQL statement, with AR 4.0 ?

1bfcb45214d4634ec265d966c9269c46?d=identicon&s=25 Savater Sebastien (Guest)
on 2013-08-07 17:58
(Received via mailing list)
Hi

Let's imagine these models, with ActiveRecord 4.0

class Parent < ActiveRecord::Base
  has_many :items
end

class Item < ActiveRecord::Base
  belongs_to :parent
end



I need to delete all items associated to one parent, without callback.
I don't need to call #destroy, but only executing the appropriate SQL
statement.

First case :

> parent.items.delete_all


In Rails 4.1 delete_all on associations would not fire callbacks. It
means
if the :dependent option is :destroy then the associated records would
be
deleted without loading and invoking callbacks.

  Item Load (0.3ms)  SELECT "items".* FROM "items" WHERE
"items"."parent_id" = ?  [["parent_id", 1]]

   (0.1ms)  begin transaction
  SQL (1.1ms)  DELETE FROM "items" WHERE "items"."id" = ?  [["id", 44]]
  SQL (0.1ms)  DELETE FROM "items" WHERE "items"."id" = ?  [["id", 43]]
  SQL (0.1ms)  DELETE FROM "items" WHERE "items"."id" = ?  [["id", 42]]
  SQL (0.1ms)  DELETE FROM "items" WHERE "items"."id" = ?  [["id", 41]]
  ...

   (80.0ms)  commit transaction



I got one delete per item. Not perfect !
Now, let's try a workaround :

> parent.items.scoped.delete_all



DEPRECATION WARNING: Model.scoped is deprecated. Please use Model.all
instead.
  SQL (0.1ms)  DELETE FROM "items" WHERE "items"."parent_id" = ?
 [["parent_id", 1]]



It works fine: only one statement.
But according to the warning, it seems to not be the proper way to do.

So, OK, let's try :

> feed.items.all.delete_all


 DEPRECATION WARNING: Relation#all is deprecated. If you want to
eager-load
a relation, you can call #load (e.g. `Post.where(published:
true).load`).

If you want to get an array of records from a relation, you can call
#to_a
(e.g. `Post.where(published: true).to_a`).
  Item Load (0.3ms)  SELECT "items".* FROM "items" WHERE
"items"."feed_id"
= ?  [["feed_id", 1]]



NoMethodError: undefined method `delete_all' for
#<Array:0x007f87ae6fee00>
from
~/.rvm/gems/ruby-2.0.0-p0@global/gems/rush-0.6.8/lib/rush/find_by.rb:16:in
`method_missing'
from (irb):24
from
~/.rvm/gems/ruby-2.0.0-p0@global/gems/railties-4.0.0/lib/rails/commands/console.rb:90:in
`start'
from
~/.rvm/gems/ruby-2.0.0-p0@global/gems/railties-4.0.0/lib/rails/commands/console.rb:9:in
`start'
from
~/.rvm/gems/ruby-2.0.0-p0@global/gems/railties-4.0.0/lib/rails/commands.rb:64:in
`<top (required)>'
from bin/rails:4:in `require'
from bin/rails:4:in `<main>'



I also tried to call #load : I got the same result as for the first case
(too many DELETE statements).
I can also do that :

> Item.where(feed: feed).delete_all



  SQL (0.2ms)  DELETE FROM "feed_items" WHERE "feed_items"."feed_id" = 1



Works fine. No warning. But let's forget the OOP.
So.. What's the proper way ?
280b78a61a968391b7e07e912be102a8?d=identicon&s=25 Robert Walker (robert4723)
on 2013-08-08 15:12
Savater Sebastien wrote in post #1118021:
> class Parent < ActiveRecord::Base
>   has_many :items
> end
>
> class Item < ActiveRecord::Base
>   belongs_to :parent
> end
>
> I need to delete all items associated to one parent, without callback.
> I don't need to call #destroy, but only executing the appropriate SQL
> statement.
>
> First case :
>
>> parent.items.delete_all
> ...
> So.. What's the proper way ?

If I understand correctly your intent is to delete all "items"
associated to "parent" but not delete the "parent" itself. If that
assumption is correct you don't have to use the association at all. Just
delete what you want directly from the Item model as follows:

Item.delete_all([ "parent_id = ?", parent.id ])
or
Item.where([ "parent_id = ?", parent.id ]).delete_all

I would also recommend using the :dependent option on your association.
It makes no sense for "items" to exist without their owning "parent"
object. In other words, parent "owns" its items.

class Parent < ActiveRecord::Base
  has_many :items, :dependent => :destroy
end

This way you won't orphan items if a parent were to be destroyed.
1bfcb45214d4634ec265d966c9269c46?d=identicon&s=25 Savater Sebastien (Guest)
on 2013-08-14 14:34
(Received via mailing list)
I knew I can do that (last example, I made a bad copy/paste of arguments
:))

Item.where(parent: parent.id).delete_all



But I hoped we can use associations, which offering a better
*readability*.
In Rails 3, you can simply make :

parent.items.delete_all



Otherwise, I already use :dependent option (with :delete_all, in my
case).
As, It's not the point here, I tried to keep my example simple.


Le jeudi 8 aot 2013 15:12:51 UTC+2, Ruby-Forum.com User a crit :
Please log in before posting. Registration is free and takes only a minute.
Existing account

NEW: Do you have a Google/GoogleMail, Yahoo or Facebook account? No registration required!
Log in with Google account | Log in with Yahoo account | Log in with Facebook account
No account? Register here.