Today I Learned Notes to self about software development

    WSL spring issues

    I’ve been working in a Rails 5 app recently and ran into some strange issues with the rails console that I didn’t fully understand. Random queries or Ruby expressions would crash the console without allowing me to see the result. Some of these commands weren’t super urgent, I could write the query and display the results on a webpage just fine. Other times I could perform the work in a different IRB, which also worked. Eventually, after I was unable to run generators, I was annoyed enough to investigate, which was difficult.

    [4] pry(main)> Octokit.repository?("appdev-projects/photogram-final")
    /home/jello/.rvm/gems/ruby-2.7.3/gems/pry-0.10.4/lib/pry/exceptions.rb:29: warning: $SAFE will become a normal global variable in Ruby 3.0
    /home/jello/.rvm/gems/ruby-2.7.3/gems/pry-0.10.4/lib/pry/exceptions.rb:29: warning: $SAFE will become a normal global variable in Ruby 3.0
    Traceback (most recent call last):
    /home/jello/.rvm/gems/ruby-2.7.3/gems/spring-2.0.2/lib/spring/application.rb:171:in `fork': cannot load such file -- yajl (LoadError)
    /home/jello/.rvm/gems/ruby-2.7.3/gems/spring-2.0.2/lib/spring/application.rb:171:in `fork': undefined method `reject!' for nil:NilClass (NoMethodError)
    /home/jello/.rvm/gems/ruby-2.7.3/gems/spring-2.0.2/lib/spring/application.rb:171:in `fork': undefined method `[]' for nil:NilClass (NoMethodError)
    /home/jello/.rvm/gems/ruby-2.7.3/gems/spring-2.0.2/lib/spring/application.rb:171:in `fork': undefined method `reject!' for nil:NilClass (NoMethodError)
    /home/jello/.rvm/gems/ruby-2.7.3/gems/spring-2.0.2/lib/spring/application.rb:171:in `fork': undefined method `reject!' for nil:NilClass (NoMethodError)
    

    It appeared to be both a Spring issue and a Windows issue. The first few suggestions just said to stop spring with spring stop then run whatever command that was not working again. This didn’t work for me (spring would auto-start). Updating spring didn’t resolve the issue either, as I could only update so much on a Rails 5.0 app.

    The only thing that did work was disabling spring with an environment variable.

    DISABLE_SPRING=1 rails console
    

    Thanks to this SO answer.

    Rails Delegate

    It’s fairly common that I’m in a situation like this:

    class Submission < ApplicationRecord
      belongs_to :student
    end
    
    
    class Student < ApplicationRecord
      has_many :submissions
    end
    

    And I need to do something like:

    submission.student.full_name
    

    which really isn’t the worst. BUT there is a neat way you can Rails will let you call full_name directly on submission, using delegate (which I always forget about).

    class Submission < ApplicationRecord
      belongs_to :student
      delegate :full_name, to: :student
    end
    
    
    class Student < ApplicationRecord
      has_many :submissions
    end
    

    and that makes it possible to do submission.full_name

    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