Today I Learned Notes to self about software development

    Design Pattern: Presenter

    Philosophy

    • Views are for presentation.
    • There should be no ActiveRecord queries in views.
    • Most logic (if statements) should be excluded from views.

    Why Not Helpers?

    Helpers are better to use if you have a global formatting method that you re-use in different views.

    Things like:

    • rendering markdown
    • showing dates in a specific format
    • removing specific words from text

    Helpers are not great to overuse because they lack organization and are difficult to reuse across your app.

    Using Presenter Objects

    • create app/presenters.
    • name after model, app/presenters/post_presenter.rb.
    class PostPresenter
      def initialize(post)
        @post = post
      end
    
      def title_without_forbidden_words
        @post.title.gsub("forbidden word", "")
      end
    
      def css_color
        @post.draft? ? "orange" : "green"
      end
    end
    
    <% presenter = PostPresenter.new(post) %>
    
    <p>
      Post title: <%= presenter.title_without_forbidden_words %>
    
      <%= link_to "Read post", post, class: "w-75 p-3 text-#{presenter.css_color} border-#{presenter.css_color}" %>
    </p>
    

    This accomplishes a few things:

    • it removes logic from views
    • creates meaningful names for methods and logic
    • allows logic to more easily be reused in other views and mailers

    Rails Design Patterns: Presenter & Service Objects

    Unusual Case Statement Behavior

    TIL something weird.

    I expected this to print “Integer”, but it doesn’t:

    mystery_class = Integer
    case mystery_class
    when Integer
      p "Integer"
    else
      p "Unknown"
    end
    # => "Unknown"
    

    This was weird because a similar example appears to behave differently:

    mystery_instance = 0
    case mystery_instance
    when 0
      p "Y"
    else
      p "N"
    end
    # => "Y"
    

    Apparently this is because []case uses === under the hood](https://stackoverflow.com/a/3908411) and === has kind of silly behavior when comparing classes.

    For example:

    Array === Array # false
    0 === 0         # true
    Class === Array # true
    

    apparently case also works different with ActiveRecord classes, since they’ll use is_a? instead which might also behave differently??? 😱

    Anyway, if you want to use case with classes for ActiveRecord object you need to do this:

    mystery_class = User.last
    case mystery_class
    when User
      p "User"
    else
      p "Unknown"
    end
    # => "User"
    

    Rails Rollback to Specific Migration

    Occassionally, when working on an app with a lot of active branches that affect the database I run into an issue where I can’t rollback.

    I’ll get an error like this:

    rails aborted! ActiveRecord::UnknownMigrationVersionError:

    No migration with version number 20230620205505.

    This usually happens when I switch to another branch and I want to rollback a change I made to update/remove the migration. I assume the migration referenced in database doesn’t exist on the current branch.

    If this happens, you can roll back to the last migration on the branch with:

    rails db:migrate:down VERSION=n
    

    where n is the timestamp from the latest migration (something like 20230607135355).

    This is only applicable for non-sqlite3 databases, since the database doesn’t live in the project directory.

    Hiccup with Rails 7 Generation

    The server won’t work out of the box if you run the generate command with npm version < 7.1.

    In particular, with < 7.1, you have to add the build commands to the package.json scripts yourself.

    See this SO answer for more details.

    I had mistakenly generated an app with a npm v6.8 b/c I used n to switch NodeJS versions to debug a student assignment and apparently never switched back.

    I sure hope that hasn’t been the cause of other issues I ran into 😅

    Redirecting stderr to stdout

    Normally if you run a file from inside another script, it can be tricky to get get error message sometimes.

    For example:

    # test.rb
    
    
    
    p("{}"
    

    3.2.1 :001 > `ruby test.rb`
    test.rb: --> test.rb
    Unmatched `(', missing `)' ?          
    > 4  p("{}"                           
    test.rb:4: syntax error, unexpected end-of-input, expecting ')' (SyntaxError)
    p("{}"                                
          ^                               
                                          
     => ""                                
    3.2.1 :002 > 
    

    The error message still is displayed in the shell, but it’s missing from the return value, which is the important thing if we’re running this inside another script/app.

    The output is being displayed through stderr and not stdout.

    There’s a trick you can do to merge stderr into stdout.

    ruby test.rb 2>&1 which looks a little silly.

    but now you get the error in the return value:

    3.2.1 :004 > `ruby test.rb 2>&1`
     => "test.rb: --> test.rb\nUnmatched `(', missing `)' ?\n> 4  p(\"{}\"\ntest.rb:4: syntax error, unexpected end-of-input, expecting ')' (SyntaxError)\np(\"{}\"\n      ^\n\n" 
    

    A breakdown of how it works:

    File descriptor 1 is the standard output (stdout).

    File descriptor 2 is the standard error (stderr).

    At first, 2>1 may look like a good way to redirect stderr to stdout. However, it will actually be interpreted as “redirect stderr to a file named 1”.

    & indicates that what follows and precedes is a file descriptor, and not a filename. Thus, we use 2>&1. Consider >& to be a redirect merger operator.