Today I Learned Notes to self about software development

    Rails: Association Callbacks

    TIL has_many associations have life cycle events you can define callbacks on.

    Available callbacks:

    • before_add
    • after_add
    • before_remove
    • after_remove

    They are added on the association (like with dependent: :destroy or counter_cache: true).

    class Author < ApplicationRecord
      has_many :books, before_add: :check_limit
    
      private
        def check_limit(_book)
          if books.count >= 5
            errors.add(:base, "Cannot add more than 5 books for this author")
            throw(:abort)
          end
        end
    end
    

    If a before_add callback throws :abort, the object does not get added to the collection.

    If a before_remove callback throws :abort, the object does not get removed from the collection.

    You can make the value an Array to run multiple:

    class Author < ApplicationRecord
      has_many :books, before_add: [:check_limit, :calculate_shipping_charges]
      # ...
    end
    

    Rails passes the object being added or removed to the callback for you to use.

    class Author < ApplicationRecord
      has_many :books, before_add: [:check_limit, :calculate_shipping_charges]
    
      # book will be the new book being created
      def calculate_shipping_charges(book)
        weight_in_pounds = book.weight_in_pounds || 1
        shipping_charges = weight_in_pounds * 2
    
        shipping_charges
      end
    end
    

    These callbacks are called only when the associated objects are added or removed through the association collection.

    # Triggers `before_add` callback
    author.books << book
    author.books = [book, book2]
    
    # Does not trigger the `before_add` callback
    book.update(author_id: 1)
    

    This makes it more niche than regular model callbacks but it’s still pretty neat.


    Rails guide