Rails HABTM after after_add callback before saving primary object

I have two ActiveRecord models related to HABTM with eachother. When I add AccessUnit through a form that allows you to add zones by checking the checkboxes, I get an exception that AccessUnitUpdaterJob cannot be queued because the transferred access module cannot be serialized (due to lack of identifier). When manually saving the main object the problem has been fixed, but of course, this is a workaround, not a correct fix.

TL; DR; it seems that the after_add callback is triggered before the main object is saved. I'm really not sure if this is a bug in Rails or the expected behavior. I am using Rails 5.

The exact error I am encountering is:

ActiveJob::SerializationError in AccessUnitsController#create

Unable to serialize AccessUnit without an id. (Maybe you forgot to call save?)

Here is some code so you can see the context of the problem:

class AccessUnit < ApplicationRecord
  has_and_belongs_to_many :zones, after_add: :schedule_access_unit_update_after_zone_added_or_removed, after_remove: :schedule_access_unit_update_after_zone_added_or_removed

  def schedule_access_unit_update_after_zone_added_or_removed(zone)
    # self.save adding this line solves it but isn't a proper solution
    puts "Access unit #{name} added or removed to zone #{zone.name}"

    # error is thrown on this line
    AccessUnitUpdaterJob.perform_later self
  end
end

class Zone < ApplicationRecord
  has_and_belongs_to_many :access_units
end
+4
source share
1 answer

From my point of view, this is not a mistake. Everything works as expected. You can create a complex graph of objects before saving this graph. At this point in the creation, you can add objects to the association. This is the moment you want to run this callback because it says after_add, not after_save.

For instance:

@post.tags.build name: "ruby" # <= now you add the objects
@post.tags.build name: "rails" # <= now you add the objects
@post.save! # <= now it is to late, for this callback, you added already multiple objects

Perhaps with a callback before_addthis makes sense:

class Post
   has_many :tags, before_add: :check_state

   def check_state(_tag)
     if self.published?
        raise CantAddFurthorTags, "Can't add tags to a published Post"
     end
   end
end

@post = Post.new
@post.tags.build name: "ruby" 
@post.published = true
@post.tags.build name: "rails" # <= you wan't to fire the before_add callback now, to know that you can't add this new object 
@post.save! # <= and not here, where you can't determine which object caused the error

You can read a little about this callback in The Rails 4 Way.

. , after_save. 2 : . . .

+2

Source: https://habr.com/ru/post/1661110/


All Articles