...

A Little About Me...

I'm a web application developer living in the mountains of Western North Carolina. Neovim is my editor, and I <3 mechanical keyboards, my current daily is a Nyquist Rev3. Ruby is my language of choice, although I consider myself a full-stack polyglot developer. I like to try different craft beers, and I prefer IPAs, the hoppier the better! Citra hops are my favorite hops so much in fact I named my rescue pup Citra. If I'm not behind a keeb I'm typically outdoors hiking, MTN biking, taking pictures, or just enjoying country life.

28 December 2023

My Winter Deep Dive Into Home Automation & Networking

As winter rolls in, my house projects and hobbies have mostly shifted indoors. I’ve become fascinated with IoT and home automation. I mean, I have dabbled in IoT and home automation for quite some time with hue light bulbs, nest thermostat, nest audio, robot vacuum, and a few other “smart home” devices managed via the Google Home app. I had some routines setup in Goggle Home that were helpful, such as saying goodnight and it would turn off all of the lights and lock the back door; however, their routines were still lacking conditions. I was super stoked when Google announced they were adding scripting to Google Home app for creating conditional automations. As soon as it released, I quickly setup an automation for turning on the backyard string lights when my girlfriend or I arrive at home after sundown. We tested this out over a couple weeks and we were quickly let down. It was not reliable at all, even with letting the Google Home app always track our iPhone location. I knew there had to be a better way and if you are a tinkerer there definitely is. It’s a very popular open source project called Home Assistant. Over the past couple months I have gone down the rabbit hole of Home Assistant, learning about why local control is better, adding local controlled devices to our home, and weaning myself off of home cloud services. You can run Home Assistant on all types of hardware including a raspberry pi. I ended up buying a Home Assistant Yellow from Nabu Casa for my install since it supports on going development, and has several nice features that you wouldn’t get just running it on a raspberry pi. After getting Home Assistant setup and the integrations needed for my devices installed, I setup the same automation I had tried with Google Home. This automation now works reliably turning on the string lights in the backyard when either of us arrive home after sundown. The best part of this Home Assistant automation is it not only uses GPS for home/away detection, but I also have it setup to use the main home WiFi to detect if a device is home or not. Now I’m totally hooked. I’ve added several Shelly devices that are all configured locally and don’t need to connect to a cloud service. Shelly relays are great for making regular light switches or outlets smart without changing the switch or plug. I also purchased a ratgdo for full local control of my garage door opener after the whole chamberlain/myq debacle. It works great and gives more features, like being able to open the garage door to a certain percentage. I’ve even been able to give back to the Home Assistant community already opening a pull request to fix a small issue with an integration that I’m using for our Mila air purifier.

Adding all these new devices to our network then led to a deep dive into networking. Lots of IoT devices on a regular home network can not only cause performance issues, but there are also some privacy and security concerns to be aware of. After a ton of reading I decided it was out with Google WiFi and in with tp-link Omada. I needed something that was more robust than a typical consumer home router so I could setup a vpn and vlans. I also wanted it to have an integration for Home Assistant to allow for device tracking, this is what led me to Omada. We now have 3 networks in our house: main, IoT, and guest. I still may add a 4th called NoT for devices that just need to be on the network, but don’t need internet access. All of the separate vlans all have their own WiFi ssid with passwords. Our main vlan now only has devices like our laptops and phones. The guest and IoT vlans are walled off from each other and from the main vlan, yet things like airplay and casting still work across vlans via mdns. I bought a tld .house domain for the house with sub domains for each of the vlans. DNS for our network is now handled by a raspberry pi running pi-hole for ad blocking. On that same raspberry pi I’m running core-dns for local device name resolution from my omada router. This requires using a custom core-dns plugin. I also configured a VPN so we can securely get on our home network when not at home. All of the network gear is connected to UPSs so it will stay powered up if we lose power. In case the main cable internet goes out I added an LTE modem that my router will fail over to automagically.

I know this is only the beginning, there is so much more to dive into and I have all kinds of ideas for things I’d like to automate. I’ll try to do more posts about my setup over the next few months.

23 October 2023

Deploying My Blog to Github Pages with Github Actions

In a previous post I talked about how I was deploying my blog to Github Pages with Travis CI. Since Github Actions is free for open source projects I’m able to use it to deploy my blog. I use the deploy pages Github Action and their starter workflow for Jekyll with the addition of html-proofer that runs on the HTML generated by Jekyll. Add a new post to _posts, commit it to main, git push and GH actions will build my site with Jekyll, run the generated HTML through html-proofer, and deploy it to Github Pages. Here’s my actions workflow.

11 November 2022

New error reporting in rails 7

While working through a bug where we were getting duplicate errors reported by sidekiq in honeybadger I learned about a new feature that was released in rails 7, but wasn’t documented. Have you ever written some code like this example below where you want to rescue an exception and still report to your error reporting service like honeybadger or sentry?

begin
  do_something
rescue SomethingIsBroken => error
  MyErrorReportingService.notify(error)
end

Well a new feature was added in rails called Error Reporting, and now we have a simple consistent way of doing this in rails.

Rails.error.handle(SomethingIsBroken) do
  do_something
end

The code above will handle the error if do_something raises an error and rails will automagically send it to the reporting service you are using in your app. To create a subscriber for the error reporting you just create an intializer like this. As of now honeybadger and sentry both create a subscriber for you so you can skip this if you use either of them.

# config/initializers/error_subscriber.rb
class ErrorSubscriber
  def report(error, handled:, severity:, context:, source: nil)
    MyErrorReportingService.report_error(error, context: context, handled: handled, level: severity)
  end
end

Rails.error.subscribe(ErrorSubscriber.new)

Thats it, now you can use the consistent Rails.error.handle block when handling errors in your app and rails will make sure it gets sent to the subscribed error reporting service.

It will be in the docs for the next release of rails also, for now check out the rails error reporting docs in rails edge guides.

18 September 2022

Preloading associations with ActiveRecord::Associations::Preloader

The other day I was working to reduce the number of N+1 queries for different pages in a rails app. I found I needed to preload some associations for an object similar to current_user, so for this example we will just use current user. The current_user helper method is defined in the ApplicationController all other controllers that inherit from ApplicationController and their views have access to current_user. Most of the time we don’t need associations to be preloaded when working with current_user, but sometimes we do. Let’s say we want to get all of the current_user’s posts and all of the comments associated with each of the posts. If we try to override current_user to use includes it won’t work, we’ll get a no method error for includes on the instance of User.

# Will raise no method error since .includes is a class methtod
class UserPostsController < ApplicationController
  def index
    @posts = current_user.posts
  end
  
  private

  def current_user
    super.includes(posts: :comments)
  end
end

A better way I found is to use ActiveRecord::Associations::Preloader. Here we can pass records and the associations to preload for the records that were passed. Now there will only be a total of 3 queries for this page, one for the user, one for the user’s posts, and one for the post’s comments.

class UserPostsController < ApplicationController
  def index
    @posts = current_user.posts
  end

  private

  def current_user
    super.tap do |user|
      ActiveRecord::AssociationsPreloader.new(
        records: Array(user),
        associations: { posts: :comments }
      ).call
    end
  end
end