Upgrading a Padrino app to 0.15.2

I’m a big fan of Padrino . It’s always filled this “more than Sinatra, no Rails bulk” gap for me while still being batteries included. Friendly community. Easy to understand and use. Most importantly, it’s helped me get apps built. Fast. And have them run well and without issue in production.

Code Reuse

Case in point: A scientific app for a conservation NGO, chugging away happily without bugs or major issues for almost a decade ago (with a big upgrade 5 years ago) and despite traffic and time.

In fact, I had assumed there were issues with the app since I’d rolled off, and that the organization, had a dev doing necessary upgrades to infra and code all this time.

Flashback to a few weeks back when I got a slight panic mail from a manager at the org since Heroku (now Salesforce) emailed it was deleting free tier databases and dynos (disclosure: prod is paid, staging was free-tier). The manager is question was understandably concerned about the possibility of losing 10 years of scientific data (and some super interesting longitudinal tracking). We were fine, and the database automatically backs up nightly, but the infra stack was end-of-life and outdated, and both ruby, libraries like ActiveRecord and Sinatra, and javascript and css frameworks in the app were sorely in need of upgrades.

If you’re not familiar with ruby and its community, some background: Rails is the acknowledged king of ruby web frameworks. At one point, everybody built in Rails. Unlike today, where there is no clear “build it with this” answer, about 15 years ago, the entire first wave of web startups were powered by apps built in Ruby on Rails. But, Rails had its issues. Accused as slow. It’s a heavyweight framework for non-trivial but not complex apps. And even back then, DHH the inventor of Rails, was a bit problematic (now he’s gotten so bad people are calling for using other frameworks just to avoid association.). Padrino hit a sweet spot. And, since a lot of the app was backend data, and I literally could get that up in a day with Padrino’s admin interface generator , it seemed like a good way to get to MVP fast. If needed, Padrino used a lot of conventions and packages Rails did, so there was a viable upgrade path if it was necessary for their use cases. Simple.

Fast forward 10 years. Most people developing web apps have moved onto other languages (Typescript and Nest seem popular. Rails gets joked about. Even me… I use Go, Rust, and Python these days and have not touched Ruby in quite some time.).

Padrino as a framework had not really been updated in two years. Sadly, since I do think it’s good. But, which was problematic as ActiveRecord (the major ORM behind it) and Sinatra had recently moved to major new versions, breaking APIs and upgrade dependencies.

Luckily, the developers were super responsive to my plaintive whine for an upgrade and a new release, 0.15.2 came out as an early Xmas present for everyone.

Upgrading the App

The Backend

Considering the code had not been touched in nearly 5 years, the upgrading was painless. Why? While the app is reasonably complex, but it was built with a system of components and patterns that make sense as abstractions and conventions. Sinatra, ActiveRecord, a PaaS to handle infra, staging vs prod environments…

I’ve said this in other places, but… simple works. Avoid unnecessary complexity for as long as possible. In fact, your goal as a software engineer should be to find the abstraction that reduces the natural complexity in whatever you’re doing to the simplest possible development and maintenance case.

Backend, so few things needed changing it made me fall in love with ruby and padrino all over again. I love stuff that “just works”, especially in dev. ActiveRecord’s config has changed to a hash. A few minor tweaks needed to be handled but overall, nothing on the backend needed anything but a cursory change.

I upgraded all gems by killing the lock file and running bundle install. In fact, updating to ruvy 3.2 and the new bundler were by far the biggest issues here (and the fact that you need to lock bundler when pushing to x86_64 if, like me, you happen to be using one of the ARM64 Macs.).

The thing I was sweating most about was the (way) past end-of-life heroku stack we’d been using for too long. Prod and staging were effectively running the equivalent of Ubuntu 16 and had been end-of-life-d in 2018. Upgrading to 22 was a simple procedure though. A literal one click upgrade and then simply pushing the new app to the revised stack was all it took and we were running with a much faster and more secure environment. Coded all changes in staging, had the team in the US and Australia kick the tires, then duplicated the same process with prod.

The Hard Stuff

Most of the issues ended up being frontend: Javascript, CSS framework, and Excel downloads. In fact, I’d say 80% of the effort.

Javascript

One inadvertently good choice I made early on was to focus on Server Side rendering rather than building an api and using a js frontend framework like React, Vue, or Angular.

In fact, most of the app is straight up js (though, we do use the excellent jquery DataTables library). Even so, this is someplace where I wish something like Svelte has been around way back when, since 100% vanilla js would have been a better choice. Upgrading and swapping out new js components where old ones no longer existed or needed upgrades took quite a bit of time.

CSS

Padrino used Bootstrap back in 2018 for its batteries-included admin interface, so it was natural to just use that and build off of it.

Surprisingly, I had to spend quite a bit of time upgrading bootstrap classes and even interface conventions, often for what seemed like quite pedantic naming changes.

Admittedly, due to the data intensive nature of the app, it was built to be used in a web interface, not mobile, and many of Bootstrap’s conventions in versions 4 and 5 had moved to support mobile-first, but altering interface css was not how I expected to be spending my upgrade time.

Sure the app looks modern and fresh (it’s amazing how small interface conventions like fonts and shadows and corners change over time)s. I’m not sure if this is something that is bootstrap’s fault (vs. say if I’d used easier to modify utility classes like Tailwind or Bulma) but it surprised me. The work was extremely tetchy and even caused some bugs (for example a change in a css class from hide to visibility-hidden caused some initial confusion as well as changes to conventions like accordions and even missing anchors from BS3 like the .well class.

Excel downloads

It’s often joked that the world runs on spreadsheets, but it’s almost frightening how much knowledge workers lean on Excel xlsx to get things done.

There was once an “easy” way to write out to a text XML standard you could use and save as excel files and have Excel open them - as well as GSheets, Numbers, and the various OpenOffice suites.

This does not work in anything but MS Excel now (and even then, reports it as an error when opening the file.).

I have to admit this undergrowth change kinda ticked me off (since I’d advocated for csv’s way back when we designed the system) but there was an insistence on Excel for “the execs” back then.

Even though I thought this would be a major PITA to fix, a nice ruby gem named caxlsx which encapsulates a lot of the complexity in writing out xlsx files with a nice DSL was used in Rails. The trick was adapting ir for Padrino.

While a gem to integrate into Rails existed lifting the code and dependent libraries and attempting to initilize from that didn’t work. Once again, it was the friendly Padrino communit to the rescue.

jkowens pointed out after I posted up my issue in the Padrino framework github, that it was actually easier to get this running in Padrino (as it turned out) than it was in Rails. You just needed to make Tilt (the Sinatra template engine) register caxslx with a custom template handler. I ended up hardcoding this in from his guidance:

# config/initializers/tilt-axslx.rb

require 'tilt/template'
require 'axlsx'

module Tilt
  class AxlsxTemplate < Template
    self.default_mime_type = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'

    def prepare
    end

    def precompiled_preamble(locals)
      "xlsx_package = ::Axlsx::Package.new"
    end

    def precompiled_postamble(locals)
      "xlsx_package.to_stream.string"
    end

    def precompiled_template(locals)
      data.to_str
    end
  end
end

Tilt.register Tilt::AxlsxTemplate, :xlsx

though jkowens after asking if I got it working has already turned it into a gem to make it even easier for people to do their Excel downloads in Padrino. You can find it here: https://github.com/jkowens/tilt-caxlsx

In case you were wondering how to get a report going, I modified my controllers and templates like this:

# admin/controllers/reports.rb

get :species_data_dump, :provides => :xlsx do
   @species = Species.order(:name)

   render 'reports/species_data_dump'
end

and a sample report for the above looked like this:

# admin/views/reports/species_data_dump.xlsx.xslx

wb = xlsx_package.workbook

wb.add_worksheet(name: "Species Report " + Date.today.to_s ) do |sheet|

  # Intro Rows
  sheet.add_row ["Progress Species Dump for " + Date.today.to_s ]
  sheet.add_row ["There are " + @species.size.to_s + " species in the system on " + Date.today.to_s]
  sheet.add_row [""]

  # this is the header row of your spreadsheet
  sheet.add_row ["Species Name", "Analog", "Phylogenetic Study", "Common Name",
                "Ex Situ Research Needs Notes", 
                "Order", "Family", "Genus", "Species",
                "DB ID", "Progress URL"]

  # each species is a row on your spreadsheet
  @species.each do |species|
    sheet.add_row [species.name, 
                  species.analog,
                  species.has_phylogenetic_study,
                  species.common_name,
                  species.ex_situ_research_needs_notes,
                  species.order,
                  species.family,
                  species.genus,
                  species.species,
                  species.id.to_s,
                  "https://theorg.org/admin/species/show/" + species.id.to_s
                  ]
  end
end

In your view where you want to someone download this from, you then just need to provide the link to download the file: link_to "Species flat file export",url(:reports, :species_data_dump, {:format => :xlsx})

And you’ve then solved the horrible problem of Excel downloads very neatly.

There’s more you can do with the caxlsx gem, including styling and charts, so I suggest you check it out since I don’t know one app developer that hasn’t had to provide Excel downloads at some point.

The Takeaway

What is my key takeaway here? OK, besides just being happy coding in ruby again (it is really, really nice…):

Simple works. The future is unknowable. Doing the simplest thing that works, can scale, and gives you room and a viable path to grow is a grossly underappreciated strategy in an era of “hypergrowth”. Overdesign is a disease and often wasted work. Agile development and evolutionary architecture tends to achieve better outcomes over the long term. Though the key thing you need to have is foresight and advanced warning of when your current solution needs to evolve to deal with business changes, scaling, or cost containment.

If I’ve made any observation over 25 years, it’s that simply designed and understood systems are successful.

So much of building enduring IT is about dealing with extreme complexity through simple systems.

So, dig deep. Simplify. Figure out a holistic way to deal with the complexity you are probably facing in whatever you’re designing, and break it down into the simplest terms or components you can. If necessary, flip the problem around. Fight for the illuminating abstraction that is going to break your systems down into the easily communicated and easily built and maintained components. In fact, fight the tendency (I feel all services have) to add stuff into your system without thinking about the overhead and additional complexity it will cause.

Fin

Even though getting contacted on this app was a bit of a shock, I have to admit I really enjoyed wading back into ruby land and bringing the app back up to modern spec.

I just wanted to put in a good word here about doing open source and charity work. I’ve got numerous friends who often ask me how I manage to keep my coding skills up to date and acquire and learn more modern frameworks and languages. Well, the fact is, projects. And often I do those projects for not-for-profit organizations that are good causes that can’t afford to hire 10X developers or the like. So, just wanted to mention that if you are looking for a way to both keep your skills sharp (if you’re a manager), pick up new languages and frameworks, and feel good about yourself at the same time, volunteering and contributing to ngo projects or open source is a great way to do that. So, if that interests you and you can spare the time, you might get a lot out of it.

I hope the post was useful and it might have even encouraged you to try out Padrino for yourself. Or if anything in here was helpful. Happy to chat about the upgrade if you’re in a similar situation yourself. Lemme know via mail or elephant below. Always interested in how these postd travel and what works and is working for people. Feel free to mention or ping me on @awws on mastodon or email me at hola@wakatara.com .