Dev & EngARTICLE

Rails gets JSON schema cache and new herb:check task to validate views

This week's Rails roundup brings a schema cache up to 22x faster on large databases, a static check for all ERB templates, and important introspection fixes in PostgreSQL and Active Storage.

Rails gets JSON schema cache and new herb:check task to validate views
Image: Bisneto Braga

O boletim semanal do Rails (This Week in Rails, publicado por Emmanuel Hayford em 18 de setembro) costuma ser tratado como nota de rodapé, mas essa edição concentra mudanças que valem revisão: um formato novo de cache de schema com ganho de performance mensurado, uma tarefa de CLI para pegar erro de template antes do deploy, e três correções de bug que mexem com integridade de dados em produção. Todas as mudanças chegaram ao branch principal do Rails nesta semana, via trabalho de 29 contribuidores, e devem aparecer na próxima release ou em algum patch subsequente.

JSON schema cache: when the file format becomes a bottleneck

Active Record has always offered two ways to serialize the schema cache: YAML (readable, slower to parse) and Marshal (fast, but binary and tied to the Ruby version that generated the file, which breaks in deploys with runtime upgrades). Now there's a third option: pointing config.active_record.schema_cache_path to a file ending in .json makes Active Record dump and load the cache in that format.

The number the post cites is concrete: in a production application with 944 tables, loading in JSON was about 22x faster than in YAML. This matters because the schema cache is read at application boot (and in every deploy worker, every job runner, every test that boots Rails from scratch). In large monoliths with hundreds of tables, that slow boot turns into minutes accumulated across the CI pipeline, not seconds.

The trade-off here is small: JSON isn't binary or tied to a Ruby version like Marshal, so you can version the file in the repository without fear of breaking between different environments, yet it's still faster to load than YAML. For a small application, with thirty tables, the millisecond difference isn't worth the attention of changing the config. The switch pays off when boot is already perceived as slow in the team's day to day.

herb:check: static lint for every ERB template in the project

The new bin/rails herb:check task compiles every HTML+ERB template in the application using the Herb parser, going through the same resolvers that view lookup uses at runtime, which means variants, locales, and engine view paths are also included in the scan. Templates that Herb rejects are listed with path and error, and the task returns a non-zero exit code when something fails.

In practice, this is a check that today only happens reactively: you only find out that an .html.erb has a syntax error when someone navigates to that specific page (or worse, when a system test accidentally covers that route). Running herb:check in CI, before deploy, turns that error into a build failure, in the same spirit as running rubocop or brakeman as a gate.

The limit is clear: Herb validates template syntax and structure, not business logic. An interpolation that renders nil without breaking, or a partial that exists but receives the wrong variable, slips through. It's a complement to system testing, not a substitute, but it's a cheap complement, because it runs in seconds and doesn't depend on a database or fixtures.

PostgreSQL: two introspection bugs that affect integrity

Two adjustments to the PostgreSQL adapter deserve the attention of anyone working with multiple schemas or covering indexes.

The first: methods like indexes, foreign_keys, check_constraints, unique_constraints, and exclusion_constraints resolved an unqualified table name by comparing against all schemas in the search path. If two different schemas had a table with the same name, Active Record returned indexes and constraints from both, a wrong and silent result. Now these methods resolve the name the same way ::regclass does in Postgres, matching what already happened with primary_keys and table_options. This is relevant to anyone using schema-based multi-tenancy (Apartment, Scenic with separate schemas, or a manual setup) who never noticed the inconsistency because table names rarely collide between tenants, but when they do collide, the data coming back was simply wrong.

The second bug is subtler and more dangerous: pg_index.indkey includes non-key columns added with INCLUDE in covering indexes, so a primary key with an INCLUDE index appeared to have extra columns, and those extra columns weren't writable during bulk upsert (upsert_all, insert_all). The fix now reads the primary key from pg_constraint.conkey, the constraint's own ordered column list, instead of inferring it from the index. Anyone using INCLUDE to optimize a covering index (a common pattern in high-read-volume tables) and doing bulk upserts on those tables was potentially losing updates on columns that Active Record mistakenly thought were part of the PK.

Active Storage: more tolerant booting for inconsistent environments

Two fixes address the same kind of pain: the Rails application getting stuck at boot because of libvips, even when the configured image processor doesn't even use libvips.

Before, an app with the ruby-vips gem installed but without the native libvips library on the system would abort initialization with a LoadError. Now this error is treated as

Translated from the Brazilian Portuguese original · Read the original