Getting back on the rails
Today I’m starting a blog. Finally. I’ve been thinking about it for a while and decided that it was going to be phase 2 of my latest Rails journey. Phase 1 was to build a new Rails app, just the way I like it. Phase 2 is to take stock of what I built and write about it.
I’ve been writing software for a few decades now, for the web since 2009. I learned Ruby and Ruby on Rails, using Textmate on a brand-new Macbook Air. Discovering Ruby, and then Rails, was an awesome experience. Having coded in FORTRAN, Pascal, C, C++ and a few other languages, it was so refreshing to be able to express not only logic, but code structure as well, in a way that more closely resembled spoken language. Write down your thoughts on what it is supposed to do, and you are halfway there!
Since then I’ve worked for about a dozen clients and employers. Each one unique. Sometimes a good match, sometimes not. The best experiences are where the organization, the people, the product, the processes, and the code are all what I would call ‘likable’. Unfortunately, only one of those is relatively easy to determine before you sign on the dotted line, and that’s the product. We generally get a good idea of what the work is about when we interview for and negotiate a contract. Everything else usually turns out to be a black box, that only reveals its innards slowly, and sometimes painfully.
So when my last stint ended a bit earlier than expected, and I felt my skills as a full-stack Rails developer had eroded a bit, I figured it might be good to treat myself to some R&R: re-tooling and re-schooling, to get back on the rails, so to speak.
When I wrote my first businessplan over a decade ago, my optimistic self thought it would be possible to create one Rails app per week. Since then I’ve learned that when starting from scratch, that is really only possible for the most basic of websites, typically supporting one specific use case. Even with all the awesome gems out there, choosing the right ones, configuring them, and tying them together thoughtfully is still time consuming.
I also found that setting up the hosting can take its time. Even with tools like Heroku or Render that make it a lot easier, it takes up a good chunk of time. And things can quickly get costly too. A larger host because it runs out of memory, a second host for redundancy, a larger Postgres database, a Redis database for background jobs, a third party plug-in for log aggregation, bug tracking, etc.
So here is what I wanted to build to prove there’s a better way:
- One web app that can run multiple, entirely different websites
- Ruby on Rails, Postgres, ruby gems, plus file storage on S3
- No need for third-party plugins like bug tracking and log aggregation, but also put nothing in the way of adding them if/when needed
- Clear separation of data between websites, and clear admin roles All this with the goal of making it possible to add a new website within a week, keep the cost down through shared hosting and by avoiding third-party plugins, and have lean processes and likable code that make it a joy to work on it.
Here are a few other considerations:
- use the latest Rails (7.0.x)
- lightweight javascript, so no webpacker, no React. Instead use Hotwire to support rendering on the server with fluent page updates provided by Turbo/Turbo-Frame/Turbo-Stream.
- user authentication with Devise, with different namespaces for system admins, site admins and regular users
- user authorization with Pundit. It controls which actions are allowed by a user, and what data can be accessed by a user.
- background jobs with Que. Sidekiq may be the natural choice, but it does require Redis. There are a few options if we want to avoid that, including DelayedJob. However, Que is easy to use, fast and has several other likable features.
- styling based on bootstrap, version 5.3 to be precise. Implemented in a way to not force bootstrap on all of the sites that might run on the same platform
- admin pages via ActiveAdmin, with separate namespaces for system admins and site admins
And then use some of the many gems that make coding easier, faster and likable:
- guard, with an extended Guardfile to test not too much, and not too little
- rubocop, using the default settings as much as possible
- overcommit, to check a few more things when committing or pushing
- bundle-audit and brakeman to help maintain a secure solution
- rspec for testing, but no controller, view or helper specs. Main focus is on model specs and request specs.
- integration tests based on turnip. It’s like cucumber, but integrated with rSpec.
- FactoryBot to build/create objects
- PaperTrail for record versioning where needed
With that, the overall setup I need to develop and run this include:
- local development on a Macbook Pro with RubyMine and TextMate
- version control with git
- repository hosting and CI testing on Github
- web hosting on Render (render.com)
- a Postgres database
- background jobs with Que (with the jobs table in Postgres)
- a few S3 buckets to store uploaded files
- domain management/DNS on Namecheap (with some legacy on GoDaddy)
I’ve added a few features that make management of the app with multiple sites a lot easier, cheaper, and likable:
- catching and recording exceptions. It’s easy to do, and it negates, at least for a while, the need for a bug tracker
- logging of key events. It’s quicker to check them in the app than to check the logs, thus, at least for a while, this negates the need for a log aggregator
- using a Rails model and view to monitor the database stats (from pg_stat_statements). No need to open a database console for that
- a simple content management system, allowing to create markup and styles for new pages, or override existing ones
- admin pages for system admins and site admins
- logging of page visits, as a first step on the way to web analytics
This blog is running on this very same system. I got it running within a day or two, so that’s cool. Now the plan is to highlight some interesting (and likable) aspects of this system in a series of blog posts.