Journal

My first open source gem: facturae-rb

I started the project “facturae-rb” a few weeks ago with a very clear motivation: I’ve spent years working with invoices, billing formats, and documentation systems in Rails teams, and I wanted a clean, reusable gem to handle the Spanish Facturae XML format.

Why I started

I saw it as a chance to cleanly apply patterns I’ve been thinking about lately (separation of concerns, repository/finder patterns, clean architecture) but at gem-library scale. It is worth noting that Facturae format will be mandatory for all invoices in Spain from 2026.

How I built it

I kicked off the repo with the usual structure: a facturae.gemspec, lib/, spec/, CI workflows. I set up RuboCop, RSpec, basic CI via GitHub Actions. I committed early a README with “Work in progress” so users know it’s not yet production-hardened. GitHub

Then I defined the domain model:

  • Facturee::Invoice (or similar) as a root object.
  • LineItem, Tax, Party (seller/buyer) classes.
  • A Serializer class (or module) that converts the model into the correct Facturae XML structure.ç
  • A Validator/Schema checker for mandatory fields, versioning of the Facturae spec.
  • A Repository module to persist or load invoices (e.g., from XML, to XML, maybe to DB).

Key technical decisions & trade-offs

I decided not to build heavy UI or CLI on top of the gem yet: keep the scope narrow. That means “just the core library” for now.

I opted for plain Ruby, no Rails dependencies. The gem must work in any Ruby project. That simplifies usage but means I have to implement things (e.g., I/O, file loading) manually rather than using Rails’s ActiveRecord or ActiveModel.

For the XML generation, I evaluated different approaches: builder, Nokogiri::XML, plain string templates. I chose (for flexibility) Nokogiri with builder style, to maintain strong structure and allow easy tweaks. The trade-off is slightly more code than a naive template.

Versioning: Facturae has different versions (3.2.1, 3.3, etc). I added support for version switching via configuration, but not all versions are yet fully implemented. That’s one of the “almost finished” parts.

I designed the gem using a “Context/Configuration” object allowing users to set defaults (seller default data, tax rates, formatting options). This reduces boilerplate in app usage, but adds another layer of abstraction.

I kept the repository pattern in mind: I created modules like Facturae::Repository::XMLLoader and …::XMLWriter. I didn’t go full heavy domain‐driven architecture, but enough to separate concerns.

What remains / “almost done”

Review and implement full support for all fields in the latest Facturae schema (there are many optional fields, some rarely used by typical businesses).

Add more sample XML files to the spec/fixtures/ folder, covering edge cases: e.g., multiple taxes, discounts, expeditions.

Improve error handling: when mandatory fields are missing or invalid, raise meaningful errors; at the moment some errors are generic.

Polish the documentation / API reference: how to plug into a Rails app, how to extend for custom fields, examples of real-world usage.

Consider publishing the gem to RubyGems, with versioning and changelog (I have a CHANGELOG.md but no release yet).

Add optional features like CSV import of line items, or integration with ActiveModel for Rails users.

Reflections from building it

Well, I’m reminded how fun it is to build a “library” for developers. You think about the API, the ergonomics for a person like you, not just the functionality. That differs from building end-user features, and that’s what I enjoy the most.

Writing clear specs early pays off: when I changed the internal model I could rerun tests and be confident I didn’t break the XML output, yay!

Even though this is “just a gem” (maybe not even that much), I treated it like a small product: domain modelling, OOP, versioning, documentation and thinking in long term maintenance.

Kept the surface area small: no rails magic, just plain Ruby; that helps with gem reuse, but some convenience is lost (for example, built‐in validations would have been easy in Rails, but not worth the hassle for a gem).

The hardest part: dealing with the multiplicity of optional fields and the variety of real-world invoicing practices. The modeling is… well, not easy at first. Real World(TM) businesses often have weird combinations of taxes, discounts, shipping, retentions, whatever the Gods of Taxes want them to do. Ensuring the library is flexible but keeps the spec’s integrity is tricky. Also signatures are a bit (no, fucking really) boring to implement.

What’s next (if I ever finish it)

This gem may stay lightweight, or evolve into a richer ecosystem (gem plus CLI plus Rails engine). Either way, finishing this piece now will let me reuse it elsewhere and reduce duplication in future projects.