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

    Rails: Discarding Jobs

    TIL the meaning of “discard” differs between ActiveJob and GoodJob.

    I was trying to use after_discard callback to handle all other exceptions that are not retried. I assumed this would work because the GoodJob Dashboard was showing “Discarded” on jobs that raised errors.

    goodjob-unhandled goodjob-discarded

    BUT even though unhandled exceptions say “Discarded” in the GoodJob dashboard, they are not considered discarded by ActiveJob’s standard, which means they don’t trigger after_discard callbacks.

    the interaction between Active Job and the Backend when exceptions are raised through the backend is very undefined.

    “discard” is an explicit action to be taken in Active Job Active Job doesn’t define what a Backend should do when a job errors Good Job reuses the term “discard” to cover: using discard_on (event: discard.activejob) using retry_on when attempts: are exceeded (event: retry_stopped.active_job) implicitly when a job error and is not retried

    bensheldon bensheldon/good_job#844 (comment)

    more stuff

    Anyway, to actually use after_discard, I added

    discard_on StandardError
    retry_on *RETRYABLE_ERRORS
    
    after_discard do |...|
      # cleanup affected records
    end
    

    (the order of these is important)

    this addressed my use case, since I only want to retry a select number of exceptions and cleanup the affected records when any other exception is raised.

    Networking: Subnetting

    Notes from Subnetting Mastery playlist.

    Seven attributes of subnetting

    1. Network ID - First IP address on each sub-net
    2. Broadcast IP - Last IP address on each sub-net
    3. First Host IP - IP address after the Network ID
    4. Last Host IP - IP address before the Broadcast ID
    5. Next Network - Network ID of the next Sub-Network
    6. Number of IP Addresses (available on sub-network)
    7. CIDR/Subnet Mask
    • Network ID and Broadcast IP are reserved and can’t be given to devices

    CIDR notation

    • identifies size of particular subnet (alternative to subnet mask)
    • examples: /24, /25, …, /27

    CIDR - Subnet Mask

    • /24 - 255.255.255.0
    • /25 - 255.255.255.128
    • /27 - 255.255.255.224

    • Where does the CIDR number come from?

    Draw Cheatsheet

    1. Start with 1, double until 128 (right to left)
    2. Subtract top from from 256
    3. Start with /32, decrement by 1 (right to left)

    128 64 32 16 8 4 2 1 (group size) 128 192 224 240 248 252 254 255 (Subnet mask) /25 /26 /27 /28 /29 /30 /31 /32 (CIDR)

    How to use:

    • Convert to from CIDR to Subnet
    • Use last octet from target IP and group size to find which block the target IP is in
      • Start from 0 and increment by group size until you exceed target IP
    • Number BEFORE target is Network ID
    • Number AFTER target is Next Network
    • IP BEFORE Next Network is Broadcast
    • IP After Network ID is FIrst Host
    • IP BEFOE Broadcast IP is Last Host
    • Group size is number of IPs

    Example:

    find 7 attributes for the following IP:

    10.1.1.37 /29
    
    • convert CIDR to subnet
      • .248
    • use last octet and group size to find which block the target IP is in (must exceed target IP)
    • number BEFORE target is Network ID
    • number AFTER target is Next Network
    • IP BEFORE Next Network is Broadcast
    • IP After Network ID is FIrst Host
    • IP BEFOE Broadcast IP is Last Host
    • Group size is number of IPs

      • 0, 8, 16, 24, 32, 40
      • host? # is between 32 and 40
      • means network ID is 10.1.1.32
      • means broadcast IP is 10.1.1.39
      • means First Host IP is 10.1.1.33
      • means Last Host IP is 10.1.1.38
      • means Next Network ID is 10.1.1.40
      • means Number of IPs is 8 (6 usable)

    Escaping in Jekyll Front Matter

    Three ways:

    1: Use Quotes

    title: "How to escape colon: in Front matter YAML"
    

    2: Use HTML entity

    title: How to escape colon&#58; in Front matter YAML
    

    3: Use multi-line title

    title: >
      How to escape colon: in Front matter YAML
    

    Ref: https://uhded.com/escape-colon-front-matter-yaml

    Converting Fonts⁚ TTC to TTF

    I like the font Iosevka but I can only download it as a TTC and often want to use it on the web, which requires a TTF.

    You can convert it with Font Forge CLI easily

    Install

    sudo apt install fontforge
    

    Usage

    fontforge -lang=ff -c 'Open($1); Generate($2);' <YOUR_FONT>.ttc <YOUR_FONT>.ttf
    

    It does output a warning, but I haven’t had issues using the font so it’s probably fine ¯\(ツ)