Today I Learned Notes to self about software development

    CSS Print Breakpoints

    I ran into an issue creating a printable cheatsheet with code blocks. Sometimes code blocks would get cut off on different pages, which made the cheatsheet hard to read.

    I was informed that you can prevent this behavior using the CSS page-break-inside property.

    Simply adding:

    <style>
      @media print {
        .code-block, .row {
          page-break-inside: avoid;
        }
      }
    </style>
    

    to my HTML prevented the page breaks from happening where I didn’t want them 🙂

    Using a path with `git stash`

    Oftentimes when I’m experimenting in a project I make a bunch of changes and then want to keep some of them (without committing) but undo the rest. I would try to stage the changes that I didn’t want and them stash them like this:

    git add path/to/undesired/changes
    git stash
    

    But this would stash everything, including the changes I wanted to keep.

    I tried being more specific with stash bu providing a path, but no dice.

    $ git stash path/to/undesired/changes
    fatal: unknown subcommand: path/to/undesired/changes
    

    I think in an older version of Git this used to work, but I found the updated command today: git stash push _path_ --keep-index

    $ git stash push path/to/undesired/changes --keep-index
    Saved working directory and index state WIP on _branch_name_: 32dbc82 .
    

    Thanks to this post.

    Ruby Keyword Arguments

    I rarely define methods that use them so I often forget. There are a few different ways to define keyword arguments in a method.

    A method can be defined with positional arguments, keyword arguments (which are defined and called using the : syntax) or have no arguments at all.

    class Calculator
      # Positional arguments
      def add(num1, num2)
        return num1 + num2 # Explicit return
      end
    
      # Keyword arguments
      def multiply(num1:, num2:)
        num1 * num2 # Implicit return
      end
    end
    

    You can also now pass the arguments in whatever order with keyword arguments.

    c = Calculator.new
    c.multiply(num2: 3, num1: 9)
    # => 27
    

    Which brings us to first way to define keyword arguments, by adding the : to the end.

    Approach #1

    def hello(message:)
      puts "hello " + message
    end
    
    hello # => ArgumentError: missing keyword: message
    hello(message: 'world') # => 'hello world'
    

    You can also define default values for these keywords like this:

    def hello(message: "everyone")
      puts "hello " + message
    end
    
    hello # => 'hello everyone'
    hello(message: 'world') # => 'hello everyone'
    

    Just having the : at the end with no default value always looked incorrect to me (which is probably why I forget about it), since it feels pretty rare for Ruby to determine the “type” of something by the ending character instead of the starting character, but it’s probably a hold over from C or something.

    Approach #2

    def grab_bag(**keyword_arguments)
      p keyword_arguments
    end
    
    grab_bag(letter: "m", some_number: 31)
    # => {:letter=>"m", :some_number=>31}
    

    If you want to accept keyword arguments, in principle you should always use def foo(k: default) or def foo(k:) or def foo(**kwargs)

    Ruby 3 Changes

    In Ruby 3.0, you must explicitly specify which arguments are keywords (using the double splat) instead of relying on Ruby’s implicit last-argument-is-a-hash thing.

    In Ruby 2, keyword arguments can be treated as the last positional Hash argument and a last positional Hash argument can be treated as keyword arguments.

    Basically, in situations like this

    def one_keyword(keyword: 1)
      p keyword
    end
    
    hash = { keyword: 42 }
    

    and you want to pass hash as an argument to one_keyword, you can’t do this anymore:

    one_keyword(hash)
    # => wrong number of arguments (given 1, expected 0) (ArgumentError)
    

    In Ruby 3+ this will throw an Argument error. Now, the syntax is to pass the argument with the double splat like this:

    one_keyword(**hash)
    => 42
    

    The other situation when you have a method with both types of arguments:

    def mixed_arguments(positional, **keywords)
      p positional
    end
    

    If positional is supposed to be a Hash, you have to explicitly declare it as such.

    This won’t work:

    mixed_arguments(word: 42)
    

    This will work:

    mixed_arguments({word: 42})
    

    More reading

    Adding "breakpoints" to help you debug a Ruby program

    Sometimes when you are debugging a Ruby script, you want to do more than just print variables to debug. It’d be nice to pause the program at a particular moment and actually interact with all the variables that have been defined up until that point. Turns out, you can do this by adding breakpoints to your script.

    a breakpoint is an intentional stopping or pausing place in a program, put in place for debugging purposes. — Wikipedia

    Breakpoints can be added to your Ruby script using the pry library, which is a part of the standard Ruby library, but isn’t automatically loaded. You can load the library with require "pry".

    Pry basics

    Let’s say we have a program like this:

    list_of_people = [
      { :name => "James", :age => 16 },
      { :name => "Yolanda", :age => 26 },
      { :name => "Mel", :age => 15 }
    ]
    
    p "Enter an age and we'll tell you if we know a person who is that old:"
    
    age_to_find = gets.chomp
    
    list_of_people.each do |person|
      if person.fetch(:age) == age_to_find
        p "Found it!"
    	end
    end
    

    Even when I enter an age that I know should be found, like 16, my program doesn’t print "Found it!" like I expect.

    program-not-doing-what-i-want.png

    Using pry, I can use binding.pry to add a breakpoint before the if statement so I can interact more with the variables that were created.

    binding.pry will pause the runtime of the program and open an IRB console

    list_of_people = [
      { :name => "James", :age => 16 },
      { :name => "Yolanda", :age => 26 },
      { :name => "Mel", :age => 15 }
    ]
    
    p "Enter an age and we'll tell you if we know a person who is that old:"
    
    age_to_find = gets.chomp
    
    list_of_people.each do |person|
    	binding.pry
      if person.fetch(:age) == age_to_find
        p "Found it!"
    	end
    end
    

    Now when you run the program it doesn’t complete—

    adding-binding-dot-pry.png

    You can use an IRB console to interact with the code that has run so far.

    Now I can access variables like age_to_find and person that were defined before the breakpoint to see why the condition for my if statement never evaluates to true.

    debugging-with-pry.gif

    Ah, the ol’ forget-to-convert-a-String-into-an-Integer error. Gets me everytime 🤦. Now I know to convert age_to_find to an Integer.

    You can type exit when you’re done to un-pause the programs execution.

    Ruby on AWS Lambda

    I recently was looking into how to use Ruby with AWS Lambda for *reasons*, and it took a lot of time! I finally figured out how to do it and even test locally with containers.

    I already wrote a bunch of stuff up in this repository, which the README will be updated with any nice resources I find as I continue learning.