I have a Post
model, that each post belongs to a tree powered by ancestry. Each post
object is either a root
, parent
, child
or any descendant
or ancestor
thereof. I have an attribute called max_depth
on each post
object. I also have a method that needs to calculate that max_depth
for the entire tree whenever a node is added/updated/deleted.
I created a callback for the added & updated cases and that works well. The issue I am having is whenever a node is deleted. I keep getting an error.
This is my after_destroy
callback:
after_destroy :update_max_depth_on_entire_tree
The definition is:
def update_max_depth_on_entire_tree
root = self.root
root.check_or_update_max_tree_depth
root.descendants.each { |c|
c.check_or_update_max_tree_depth
}
end
def check_or_update_max_tree_depth
update_columns(max_tree_depth: last_depth)
end
def last_depth
if child_ids.empty?
return depth
else
return children.map{|c| c.last_depth}.max
end
end
Whenever I try to delete an object, I get this error:
ActiveRecord::ActiveRecordError at /posts/detecting-ramsay-is-offered-ceo-job
cannot update on a new record object
This is what the Server log looks like:
Started DELETE "/posts/detecting-ramsay-is-offered-ceo-job" for 127.0.0.1 at 2015-01-05 09:56:24 -0500
Processing by PostsController#destroy as HTML
Parameters: {"authenticity_token"=>"2xRY+A=", "id"=>"detecting-ramsay-is-offered-ceo-job"}
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = 3 ORDER BY "users"."id" ASC LIMIT 1
(2.9ms) SELECT COUNT(*) FROM "roles" INNER JOIN "users_roles" ON "roles"."id" = "users_roles"."role_id" WHERE "users_roles"."user_id" = $1 AND (((roles.name = 'admin') AND (roles.resource_type IS NULL) AND (roles.resource_id IS NULL))) [["user_id", 3]]
(2.4ms) SELECT COUNT(*) FROM "roles" INNER JOIN "users_roles" ON "roles"."id" = "users_roles"."role_id" WHERE "users_roles"."user_id" = $1 AND (((roles.name = 'editor') AND (roles.resource_type IS NULL) AND (roles.resource_id IS NULL))) [["user_id", 3]]
Post Load (0.9ms) SELECT "posts".* FROM "posts" WHERE "posts"."slug" = 'detecting-ramsay-is-offered-ceo-job' ORDER BY "posts"."id" ASC LIMIT 1
(0.2ms) BEGIN
Post Load (5.2ms) SELECT "posts".* FROM "posts" WHERE (("posts"."ancestry" ILIKE '69/%' OR "posts"."ancestry" = '69'))
Role Load (0.6ms) SELECT "roles".* FROM "roles" WHERE "roles"."resource_id" = $1 AND "roles"."resource_type" = $2 [["resource_id", 69], ["resource_type", "Post"]]
FriendlyId::Slug Load (2.9ms) SELECT "friendly_id_slugs".* FROM "friendly_id_slugs" WHERE "friendly_id_slugs"."sluggable_id" = $1 AND "friendly_id_slugs"."sluggable_type" = $2 ORDER BY "friendly_id_slugs".id DESC [["sluggable_id", 69], ["sluggable_type", "Post"]]
SQL (1.5ms) DELETE FROM "friendly_id_slugs" WHERE "friendly_id_slugs"."id" = $1 [["id", 47]]
SQL (1.2ms) DELETE FROM "posts" WHERE "posts"."id" = $1 [["id", 69]]
Post Load (0.4ms) SELECT "posts"."id" FROM "posts" WHERE "posts"."ancestry" = '69'
(0.2ms) ROLLBACK
Completed 500 Internal Server Error in 281ms
ActiveRecord::ActiveRecordError - cannot update on a new record object:
activerecord (4.1.6) lib/active_record/persistence.rb:272:in `update_columns'
() /app/models/post.rb:122:in `check_or_update_max_tree_depth'
() /app/models/post.rb:130:in `update_max_depth_on_entire_tree'
This is what my routes look like:
posts_path GET /posts(.:format) posts#index
POST /posts(.:format) posts#create
new_post_path GET /posts/new(.:format) posts#new
edit_post_path GET /posts/:id/edit(.:format) posts#edit
post_path GET /posts/:id(.:format) posts#show
PATCH /posts/:id(.:format) posts#update
PUT /posts/:id(.:format) posts#update
DELETE /posts/:id(.:format) posts#destroy
GET /posts/:id(.:format) redirect(301, /%{id})
GET /:friendly_id(.:format) posts#show
GET /posts/:friendly_id(.:format) posts#show
GET /:name(.:format) posts#show
root_path GET / posts#index
And this is what my PostController#Destroy
looks like:
def destroy
@post = Post.find(params[:id])
@post.destroy
respond_to do |format|
format.html { redirect_to posts_url, notice: 'Report was successfully destroyed.' }
format.json { head :no_content }
end
end
I believe the issue is with this portion of the code:
def check_or_update_max_tree_depth
update_columns(max_tree_depth: last_depth)
end
What the error seems to be saying is that somewhere along the chain it encounters a new record, and it can’t execute update_columns(max_tree_depth: last_depth)
on an object that is a new record.
How do I update the rest of the tree that the recently deleted node belonged to, without getting this error?