In development — designed and built in-house
- Ruby 3.3
- Rails 8
- PostgreSQL
- Hotwire
- Pundit
- Chartkick
The problem
Breeders keep their records on paper or in a notes app, and the records they most need — which pair produced which bird, how many rounds a nest box has run, what a bird's ancestry looks like — are the ones a flat list cannot answer. The data also arrives in Arabic, on a phone, from people who are not going to tolerate a form that argues with them.
Modelled the way a loft works
A farm holds barns, a barn holds nest boxes, a nest box holds a pair, and a pair runs breeding rounds that move through laying, inspection, hatching and banding. Birds exist independently of pairs, because a bird is bought, sold and recorded long before it is matched with anything. Ancestry is held on the bird itself, so a pedigree is a walk up the records that are already there rather than a separate document to maintain.
Arabic search that actually matches
Arabic names are typed inconsistently by nature: three spellings of alef, two endings that sound the same, optional diacritics, decorative letter stretching. A plain search misses most of what the user meant. Both the stored value and the typed query are normalised inside PostgreSQL before they are compared, so a name entered one way is found when it is typed another.
Rules enforced twice
Every invariant that matters is stated in the application and again in the database. A nest box can hold one male at a time, held as a partial unique index rather than a check-then-write that two requests can both pass. A pedigree cannot loop back on itself, checked by walking the ancestry with a bounded depth so a corrupt record cannot turn a page load into an infinite climb. Validations catch these early and give a usable error on the right field; the constraints make sure they are true regardless.
Built for the phone
The interface is right-to-left throughout and laid out for a narrow screen first — the records are entered in the barn, standing up, not at a desk afterwards. Charts summarise hatch rates and activity per farm. Access is scoped to the farms a user owns, and an unauthorised request is answered as not-found rather than forbidden, so the application does not confirm that someone else's record exists.
What is not done
Stated plainly because it is part of the picture: the automated test suite has not been written yet, and the deployment configuration is still the generated default. The data model, the constraints and the interface are the finished parts.