Today I Learned Notes to self about software development

    Conditional Hash#merge

    I was in a situation where a key may or may not be present in a Hash (options), and I needed to do two different, but very similar things. The general if/else seemed like too much:

    if options[:uniqueness]
      if options[:scope]
        validates attribute, :uniqueness => { :case_sensitive => true, :scope => options[:scope] }
      else
        validates attribute, :uniqueness => { :case_sensitive => true }
      end
      # ...
    end
    

    So I found a nice one-liner that cleaned it up!

    if options[:uniqueness]
      uniqueness_options = {
        :case_sensitive => true,
        :scope => ( options[:scope] if options.has_key?(:scope))
      }.compact
    
      validates attribute, :uniqueness => uniqueness_options
    end
    

    Another approach was to use tap (which I need to lookup more about)

    { :a => 'animal' }.tap { |hash|
      hash[:b] = 'banana' if true
    }
    

    which maybe is better since it supports earlier Ruby version and is still very readable.

    Hash#merge and setting defaults

    When writing a method with an options Hash, how best do you specify default key/value pairs that can be safely overwritten?

    Well, if it’s just one this should be fine:

    def foo(k: 1)
      p k
    end
    
    foo({ k: 'apple'})
    

    but if you’re dealing with a double spat, Hash#merge is your friend.

    # Can also use a constant here to help document what the defaults are
    DEFAULTS = {
      fruit: 'apple',
      color: 'lavendar',
      feels: 'sleepy'
    }
    
    def blog(**options)
      options = DEFAULT.merge(options)
      # ...
    end
    

    merge works like this:

    h1 = { "a" => 100, "b" => 200 }
    h2 = { "b" => 254, "c" => 300 }
    h1.merge(h2)   #=> {"a"=>100, "b"=>254, "c"=>300}
    h1             #=> {"a"=>100, "b"=>200}
    

    prioritizing the values in the Hash argument.

    There’s also apparently Hash#reverse_merge in Rails, which handles duplicate values the other way— prioritizing the entries in the Hash calling the method.

    h1 = { "a" => 100, "b" => 200 }
    h2 = { "b" => 254, "c" => 300 }
    h1.reverse_merge(h2)   # => {"b"=>200, "c"=>300, "a"=>100} 
    

    gem cleanup

    This is a reminder to uninstall unused gems for each of the Ruby versions you have installed. Switch to the desired Ruby version and run

    gem cleanup
    

    This uninstalls unused versions of gems that are installed. I’m not sure how Ruby determines which gems are not in-use, but it’s handy because the different gem versions installed really add up. Run this command every few months. I don’t remember the last time I did this but I freed up like 3+GB of storage today just cleaning up gems.

    Rails Serialize a column

    I never used serialize before since Postgres supports :json and :jsonb columns which are better. It’s still useful that I can still store Hash data in the sqlite db text column— especially if my students, who aren’t using Postgres, want to do complicated stuff like OAuth and need to store some credentials.

    Re: Spotify API

    # in the action of the callback URL...
    spotify_user = RSpotify::User.new(request.env['omniauth.auth'])
    user = User.find_or_create_by({ :spotify_info => spotify_user.to_hash })
    

    #  spotify_info :text
    #
    class User < ApplicationRecord
      serialize(:spotify_info)
    end
    

    OAuth 2 and RSpotify

    I knew that OAuth 2 was replacing OAuth 1, but since I wasn’t actively working on projects that used OAuth 1 I didn’t really internalize it until I was helping someone use the rspotify gem recently and ran into a bunch of issues trying to follow the instructions in the README.

    The README is a trap.

    Even though it appears to have been updated semi-recently, the setup instructions are outdated. I had to reference an issue from 2016 to figure out why. I searched some other errors and found the omniauth upgrade guide which mainly clarified that the auth request needs to be a POST instead of a GET.