Built with Ruby Native
Equipe is the software that runs equestrian competitions: entries, start lists, and live scoring for show jumping and dressage. It is a Rails product with iOS and Android apps on top, used at events across Europe, with nearly a million downloads across the two stores. The team is small and Rails-focused.
“The app has never felt so good on Android. Love this.”
Jon Stenqvist
Ruby Native Customer
Equipe's apps were fine. They had been in the stores since 2013, they shipped monthly, and bugs got fixed. Which meant maintaining a Swift codebase and a Kotlin codebase on top of their Rails app. For a while, I owned the mobile work so nobody at Equipe had to open Xcode or Android Studio.
That works. It also means the apps only move when someone who writes Swift and Kotlin has time, and that someone was me.
The apps weren't broken. They were separate: two codebases and a set of custom bridge components, none of which written in the language the team writes every day.
In June, Jon asked the question directly:
When do you think it is time to move to Ruby Native?
Two months earlier I had told Jon that Ruby Native would suit Equipe, with one exception: it couldn't put an image in the nav bar. I treated that as cosmetic.
It wasn't.
What worries me most is the branding. The riders are my sales staff. I want them to see the Equipe logo on all pages, as before.
Equipe's users are competitors and organizers at live events, and the app is Equipe's face in front of them all day. A generic nav bar was a business objection, not a styling preference.
So nav bar branding got built. So did iPad support, which surfaced the same way: Equipe's app already supported iPad, so Ruby Native had to before the migration could deploy.
This is what a design partner is for! Jon was migrating a real app with a decade of requirements attached, so every gap he hit was a gap the next person would hit too. Both features shipped to every Ruby Native customer, not to a branch.
A greenfield app asks a framework for very little. An app that already exists asks for a whole lot more.
Replacing both the Xcode and Android Studio projects took:
config/ruby_native.yml to define the native shell: colors, tabs, linked domainsGemfile additionNo data migration. Years of registered devices and a working push pipeline carried over untouched, still sending through the same code that always sent them. So did the checks scattered through their views asking "are we in the app right now?", because Ruby Native requests answer them the same way.
Equipe's Rails views are ERB, but a great deal of the app is React: 86 components rendering inside those views, including the nav bar menus. Jon calls it "my special Turbo/React mix."
Ruby Native left all of it alone. Equipe's menus moved to Ruby Native's React components, and that was the whole change: a component swap inside their existing JSX. Nothing about their frontend architecture had to change.
I wrote the initial integration branch and the gem features it needed. Jon did the rest: upgrading the gem, deploying, triggering builds, testing both platforms on real devices, and releasing to the app stores.
He is not a mobile developer. And he never opened Xcode or Android Studio once.
It wasn't friction-free. A decade-old app surfaces edge cases a new one never will, and Jon found his share.
This is like a Pandora's box. I have been thinking, every time, this is the last thing.
He asked about migrating in June. The apps went to the stores in September, with nav bar branding and iPad support built along the way.
The apps in the stores are the Rails app now. The config file describing their shell sits in the same repo as everything else, and shipping a change to either platform is shipping Rails code.
Before officially launching, Jon asked about the user journey:
How can we do the transition from the old app to the new app without breaking the app for our users?
Nobody had to be cut over. Equipe's new app replaced the old one in the stores, users update on their own schedule, and the old build keeps working for everyone who hasn't.
The maintenance retainer ended. Nobody is shipping Swift or Kotlin anymore, so there's nothing left for it to maintain.
Equipe pays for Ruby Native Business instead. It costs less per year than the retainer cost per quarter, and Jon runs the releases himself now.
I'm very happy to go the Ruby Native direction.
If your app's value is in native surfaces, custom camera work, complex offline sync, heavy background processing, Ruby Native will fight you. Equipe's apps are a Rails product delivered to a phone, which is the case it is built for.
And a migration is not totally free. If your native app is stable, unattended, and nobody minds owning it, staying put is a legitimate answer. The case for moving is strongest when the native codebase is a tax: a language nobody on the team writes, a release cycle separate from your deploys, and a person who has to be available for it.
What shipped
One screen, three places, rendered from the same ERB. On the web it is their site; on iOS and Android the chrome around it is native, with a real nav bar, a real tab bar, and the branding Jon asked for.
The Equipe logo sits on every screen, on both platforms. This is the feature Jon's objection created: nav bar branding did not exist in Ruby Native before their migration needed it.
Shows, Notifications, Search, and Me are native tab bars on both platforms, configured in config/ruby_native.yml rather than built twice.
Equipe's existing push pipeline was left untouched. A 30-line adapter routes Ruby Native's token registration into the device records they already had.
Their app already supported iPad, so Ruby Native had to before the migration could deploy. Another gap a real app surfaced and a greenfield one never would.
Your turn
Equipe's apps are their Rails app now: same views, same repo, same deploy. If you already have a Hotwire Native app, the migration path is a config file and an adapter, not a rewrite.
Keep exploring