Today I Learned Notes to self about software development

    Better Rspec tests with shared examples

    TIL about shared examples in Rspec!

    They’re primarily good for reducing duplicate code.

    They work well with subject and let.

    This is a made up example:

    describe "Todos" do
      context 'when on the edit page' do
        subject { Todo.create(title: "Day 1", body: "Hello, World!") }
        let(:path) { edit_todo_path(subject) }
    
        it "displays form" do
          visit path
    
          expect(page).to have_css("form")
        end
        
        it "redirects after submitting update form" do
          visit path
    
          click_on "Update #{subject.class}"
    
          expect(page.current_url).not_to be path
        end
      end
    end
    

    You could write very similar tests for other models.

    With shared examples you could do something like this:

    shared_examples_for "Editing a resource" do
      it "displays form" do
        visit path
        expect(page).to have_css("form")
      end
    
      it "redirects after submitting update form" do
        visit path
        click_on "Update #{subject.class}"
        expect(page.current_url).not_to be path
      end
    end
    
    describe "Todos" do
      context 'when on the edit page' do
        subject { Todo.create(title: "Day 1", body: "Hello, World!") }
        let(:path) { edit_todo_path(subject) }
    
        it_behaves_like "Editing a resource"
      end
    end
    

    Then in the future when you need a test that does something similar, you can make/reference a a shared example group.

    DRY with dry_config gem

    I was curious about how you could make gems have configuration settings (like from an initializer). I finally had a use case when writing appdev_support.

    My goal was to be able to configure the gem like this:

    # config/initializers/appdev_support.rb
    AppdevSupport.config do |config|
      config.action_dispatch = true;
      config.active_record   = true;
    end
    

    and have these settings determine which class of method overrides should be loaded.

    Previously, I was defining attr_writers like this

    module AppdevSupport
      class << self
        attr_writer :active_record, :action_dispatch
    
        def action_dispatch
          @action_dispatch || true
        end
    
        def active_record
          @active_record || true
        end
      end
      # ...
    end
    

    Note to self, I still don’t really understand what class << self does or how it works (I think it makes a Singleton somehow?) but with that change I could dynamically load files by calling a class method:

       def self.init
        if @active_record
          load "appdev_support/active_record/delegation.rb"
          load "appdev_support/active_record/attribute_methods.rb"
          load "appdev_support/active_record/relation/to_s.rb"
        end
        if @action_dispatch
          load "appdev_support/action_dispatch/request/session/fetch.rb"
          load "appdev_support/action_dispatch/request/session/store.rb"
          load "appdev_support/action_dispatch/cookies/cookie_jar/fetch.rb"
          load "appdev_support/action_dispatch/cookies/cookie_jar/store.rb"
        end
      end
    

    I wanted to make adding additional config settings easier to do and more seemless (like not having to run AppdevSupport.init if you just wanted the defaults).

    TIL about the dry-configurable gem, which has been around for a while!

    It really cleans up the “defining an instance variable/attr_writer” process and makes it a lot easier to define nested structures too!

    require 'dry-configurable'
    
    module App
      extend Dry::Configurable
    
      setting :api_url
      setting :repository, reader: true do
        # Can pass a default value
        setting :type, default: :local
        setting :encryption do
          setting :cipher, default: 'aes-256-cbc'
        end
      end
    end
    
    App.config.api_url = 'https://jelani.dev'
    App.config.api_url # => 'https://jelani.dev'
    

    This let me start to refactor the gem:

    module AppdevSupport
      extend Dry::Configurable
      setting :active_record,   default: true
      setting :action_dispatch, default: true
      setting :pryrc,           default: :minimal
      # ...
    end
    

    While this was more concise and flexible, I still wasn’t able to get the defaults to load without calling AppdevSupport.init like before.

    In searching for answers I found a post that taught me more responsible ways to define Monkeypatches and I incorperated some of those techniques into the gem as well.

    Amending Git Commits

    So one way or another, you hecked up and now GitHub says one user authored the commit while a different user committed the commit.

    git-commit-by-two-users.png

    How to fix?

    The only way I know is to amend or rebase, which will change commit Hashes and mess up other users commit History.

    If you don’t care about that, proceed.

    Change Committer

    Pass GIT_COMMITTER_EMAIL and GIT_COMMITTER_NAME to amend with email and GitHub username respectively.

    GIT_COMMITTER_EMAIL=joemama@example.com GIT_COMMITTER_NAME=joemama git commit --amend --no-edit
    

    Change Author

    Pass GIT_AUTHOR_EMAIL and GIT_AUTHOR_NAME to amend with email and namerespectively.

    GIT_AUTHOR_EMAIL=joemama@example.com GIT_AUTHOR_NAME=joemama git commit --amend --no-edit
    

    You can always find out the name by running git show ... on the commit that they made:

    commit b65f3160e773ff2a18cc6e4993c278d50cedea94
    Author: Joe Mama <joemama@example.com>
    Date:   Thu Mar 2 11:55:40 2023 -0600
    
        Joe Mama's commit message 😎
    

    Change Date

    Useful if you editing several commits at once and want the origninal dates to not get updated to the current time.

    Prepend GIT_COMMITTER_DATE env variable to your git ammend --no-edit command

    GIT_COMMITTER_DATE="Wed Mar 29 20:32:01 2023 -0600" git commit --amend --no-edit --date="Wed Mar 29 20:32 2023 -0600"
    

    --date will set the author date, which should also be the same as the committer date.

    Amending a commit that’s not the most recent commit

    You’ll need to rebase, git rebase -i HEAD~3 to edit the third most recent commit.

    pick 93ef79f Dynamically resize textarea
    pick cca2dd2 Display full titles on menu link hover
    pick a372bc3 Use JS autosize for query field
    

    You’ll want to choose edit for the commit you want to amend.

    Note, if you only amend the author or committer for a past commit, all dates will be updated to current time.

    If you want to keep the existing dates for these commits, it’s helpful to keep another Terminal tab open with the current git log and choose edit for each commit. Amend the author or committer for the desired commit and edit the date for the rest to be what the were before.

    Reset primary key sequence SQLite3

    I was trying to reset the primary key sequence in a development Rails environment and realized the usual:

    ActiveRecord::Base.connection.reset_pk_sequence!('users')
    

    did not work. (I think that is a postgres exclusive method)

    I did some digging and found that executing this raw SQL did work:

    ActiveRecord::Base.connection.execute("UPDATE SQLITE_SEQUENCE SET SEQ=0 WHERE NAME='table_name'")
    

    I learned from this post.

    Moving hidden files in the command line

    I was attempting to move all files from a subfolder into the root folder in the command line and ran:

    mv subfolder/* .
    

    but to my surprise, files beginning with ., like .gitignore, did not move.

    It turns out hidden files are exlcuded by default I guess?

    If you want to move dotfiles, you can do this instead:

    mv subfolder/{,.}* .
    

    See this answer.