Welcome to My Blog.

Here, you will find posts, links, and more about code (primarily Ruby), business (bootstrapped SaaS), and a little of everything in between.

Ruby Map With Index

Ruby has a built-in helper on Enumerable called - each_with_index

# An array of fruits
fruits = ["Apple", "Banana", "Cherry", "Date"]

# Using each_with_index to print each fruit with its index
fruits.each_with_index do |fruit, index|
  puts "#{index}: #{fruit}"
end

Unfortunately, there is no equivalent for the Enumerable#map.

['a','b'].map_with_index {|item,index|} # => undefined method map_with_index'`

There are easy ways to work around this, but the cleanest is by chaining with_index to .map

fruits = ["Apple", "Banana", "Cherry", "Date"]

result = fruits.map.with_index do |fruit, index|
  "#{index}: #{fruit}"  # Format the output with index
end

Why? My guess is to keep the number of methods smaller over time. Technically each_with_index could be deprecated since Enumerable#each.with_index is available. This way, we do not need a _with_index for everything on Enermable.

(1..100)
   .select
   .with_index {|n, index| (n % 2 == 0) && (index % 5 == 0)}

# [6, 16, 26, 36, 46, 56, 66, 76, 86, 96]
#

AeroSpace Tile Manager

In the never-ending pursuit of optimal App/Window/Space management on my computer, I recently switched to AeroSpace

AeroSpace bills itself as:

an i3-like tiling window manager for macOS

If you are like me and have never used i3, you may be asking, what is this?

Honestly, it is hard to describe, but in a nutshell, it has replaced my usage of three different applications:

  1. OS X Spaces (grouping of windows/projects/etc)
  2. Magnet - window spacing
  3. Alt+Tab - intelligent, quick app switching

These three apps work as expected (and I have nothing but praise for Magnet, which always did its job), but I find I work in a much more consistent and repeatable environment and spend far less time navigating between apps.

I recommend checking out this video for a detailed overview.

#

Turbo Streams -- append_all

While adding revisions (auto-save and version history) to PhrontPage, I needed a way to add a new element to the page. Using turbo_stream#append, turbo_stream#update, etc. would work, but only if there is an element on the page with a known dom id. Eventually, I would like this item (a toast component) to be more generally available, so I did not want to need to have anything hardcoded on the page.

In addition to append, update, etc., there are matching methods append_all, update_all, etc., that allow targeting one or more items with more flexible query selectors.

Append to the targets in the dom identified with targets either the content passed in or a rendering result determined by the rendering keyword arguments, the content in the block, or the rendering of the content as a record append_all

In my case, the Toast is added to the page, displays, and then removes itself.

Hattip to Matt Swanson for highlighting the returning component for the turbo_stream response.

  def render_success_toast
    component = Admin::Toast::Component.new(text: "Your changes have been saved")
    render turbo_stream: turbo_stream.append_all("body", component)
  end

toast.gif

#

Kudos for the super helpful error message:

constraints

I have a constraint on the route, and I was passing "posts" instead of "posts.

delete "/orphans/:type/:uid", to: "orphans#delete", as: "delete_orphaned_items", constraints: {type: /(post|page)/}
#

Introduction to Minitest Mocks

Test doubles likes mocks and stubs can help us with isolating code under test with the rest of the system. Here’s how to mock in Minitest.

#

Returning from a JavaScript Loop with Some

I am working on the Revision1 functionality for PhrontPage (autosave + history). After getting the main pieces in place, I realized that you likely do not want revisions on new content until you reach a short minimal threshold (starting at 30 characters). So I added a quick check for the length of the markdown field, but that felt dirty hardcoding a specific field.

Next, I added a target(s) to my Stimulus controller. Now, any field with the target applied can be evaluated—there are no hardcoded field names, etc.

However, I could not for my life see why the following function was not returning true.

hasFieldsWithoutMinimalLength() {
  this.minimalLegthFieldTargets.forEach(field => {
    if (field.value.length < this.minimalLengthValue) {
      return true
    }
  })
  return false
}

In the golden age of programming (6 months or so ago), this would have resulted in a Google search or two, scrolling through Stack Overflow, or perhaps the X/Twitter hail mary. However, today, I can ask Claude via SuperMaven

(me) The function that starts on line #37, hasFieldsWithoutMinimalLength appears to iterate fine, but the return in line 40 is not stopping the function from returning false

And you get the following:

(Claude) You're correct in identifying the issue with the hasFieldsWithoutMinimalLength function. The problem is that the return true statement is inside the forEach loop, which doesn't actually stop the function's execution or return from the outer function. Instead, it only returns from the callback function passed to forEach.

I know there are folks generating whole applications, etc., but for me, this is a sweet spot. I enjoy writing code, the journey, and the act of creating2. Having AI there to point out my obvious or sometimes subtle bugs is an amazing boost to my productivity.

For those curious, here is the fix and why.

hasFieldsWithoutMinimalLength() {
  return this.minimalLegthFieldTargets.some(field =>
    field.value.length < this.minimalLengthValue
  )
}

The some() method of Array instances tests whether at least one element in the array passes the test implemented by the provided function. It returns true if, in the array, it finds an element for which the provided function returns true; otherwise, it returns false. It doesn't modify the array.

  1. Technically, I am babysitting an Elastic Search migration that has me rethinking my career choices

  2. No shade. You do you and find what/how/etc you enjoy. Shipping is awesome, and if generating code and ignoring it all makes that happen, go for it.

#

It hurts my feelings when one flaky system test causes the whole bar to turn red.

flakey.png

#

When a HTTP Post Becomes a Patch

I was implementing the previews in PhrontPage.

Part of this involves grabbing the current form and sending the data via HTTPost (using requestjs-rails) to the server and then letting turbo_stream do its thing and update the screen.

const form = new FormData(formElement)
const response = await post(this.urlValue, {
   body: form,
   responseKind: "turbo-stream"
})

Full source in preview_controller.js

This was wired up to a controller like this:

post "/previews", to: "previews#show"

When working with new Posts or Pages, everything worked as expected.

However, once I tried to preview an existing Post or Page, I started getting 404 errors. Thankfully, it did not take me long to spot the error.

I believe it was Rails 4 when the switch was made to use the Patch verb for updates. Browsers do not typically support Patch. To get around this, Rails (and other frameworks) add a hidden field called _method.

patch.png

The browser declares a post request on the form. However, when Rails spots the '_method` parameter, it knows to look for the matching route. In my case, this did not exist at the time.

There are some simple fixes for this:

  1. Remove the _method from my FormData
  2. Supply a different route to handle existing Posts and Pages
  3. Update the route to work for both Post and Patch

I went with option #3. For the preview feature, it does not matter if the content already exists. I need to take what you have on the form and send it to the server.

The updated route looks like this:

match "/previews", to: "previews#show", via: [:patch, :post]
#

Rails Direct Uploads to A Custom Folder

One of my must-have features for PhrontPage was drag-and-drop direct uploads to a plain Markdown editor. I have always liked this functionality on GitHub issues. Rails ships with Trix support for dropping files on an editor, but that is not how I want to write.

I had bookmarked this example by Jeremy Smith a while ago, and it was a great help to get this feature implemented.

However, after a bit of testing, I quickly found my R2 bucket to be a bit messy and wanted to, at a minimum, direct all my blog uploads to a single folder. Surprisingly, there is not a built-in way to do this.

I created a custom ActiveStorage Service that derives from the S3Service and provides an option to append a folder to all the uploads that go through the service.

require "active_storage/service/s3_service"
module ActiveStorage
  class Service
    class S3WithPrefixService < S3Service

      def path_for(key)
        "uploads/#{key}"
      end

      def object_for(key)
        bucket.object(path_for(key))
      end
      
    end
  end
end

The full PhrontPage implements can be seen here, with code to pull the folder from an ENV variable and handle any extra "/". The one below is just the basics to get started.

Once added to your project, all you have to do is add it to your storage.yml file, and you should be all set.

#