Ruby library
All notable changes to the Habarico Ruby gem are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Releases are cut through the CI next flow: merges to next publish beta
pre-releases, and the ## [0.56.0] — 2026-08-03 section below is stamped with the
version and date when a release is finalized.
[7.2.0] — 2026-10-19
Changed
- The gem is published to rubygems.org, betas as pre-release versions, and no longer to
geminabox.eluvia.dev;
eluvia-baseis resolved from rubygems.org too. Drop the geminaboxsourceblock forhabaricofrom yourGemfile.
Fixed
meta.schemaof a record (list, detail, create and update responses) is emitted in snake_case like the rest of the response and the schema endpoints, instead of camelCase. The keys inside it that the host declares itself, such as an action'sview_parameters, now reach the client exactly as declared (object_id, notobjectId).@eluvia/habaricoreads both forms. (HAB-1022)
[1.0.0] — 2026-10-10
Added
-
Habarico::FileValidations- derives themax_size/acceptfield-schema options for anyfile_attrfrom thecontent_type:/size:validations a host declares on its underlying attachment with the optionalactive_storage_validationsgem (not a dependency of this gem - a host that does not use it simply declares none, and both options stay unset). Not upload-specific: chunked uploads live entirely inhabarico_enterprise/uploads, but everyfile_attrnow getsmax_size/acceptin its schema, with or without that gem installed.habarico_enterprise.uploads, when installed, additionally folds its project-wide upload size ceiling intomax_sizefor a field a chunked-upload provider serves. (HAB-966) -
Habarico::FieldReference#max_size/#accept- overrides afile_attr's derived upload limits by hand, the same wayresize_width/icons_keyalready do for their own options. A bare symbol field declaration has no such override; only aFieldReferencedoes. (HAB-966) -
default_page_limitandmax_page_limitin the resource DSL - the page size of a list endpoint and the largest one a request may ask for, per resource. The same two knobs configure every resource at once asHabarico::Config.default_page_limit/.max_page_limit; unset, both defer toeluvia-base(itsset_paginationdefault of 20 andEluvia::Base::Config.max_page_limit, which is also where the ceiling can be removed withnil). New page: Pagination. (HAB-946) -
Extension points that let the paid gem register itself without this gem referencing it (HAB-812):
Config.search_highlighter_class— class buildingmeta.highlights,nilby default;Types.register(list_name, *values)withTypes::EXTENSIBLE— adds values toFIELD_TYPE,FIELD_FORMAT_TYPE,BOX_TYPEandACTION_TYPEand re-declares the enum on every schema class built from them. The other lists stay frozen: they mirror a closed contract enum;Habarico::ExtensibleEnum— theextensible_enum_attrdeclaration that records a schema class as a consumer of such a list.RoutingService.register_resource_route_block— route blocks drawn inside each resource's own route block, which is what the aggregate endpoint needs (the existingregister_route_blockonly reaches the admin-site root). The preferences registry already existed and is unchanged.
-
coloron menu items and menu groups (MenuSchema,MenuGroupSchema), settable from the DSL viaHabarico::MenuItem#colorand validated againstTypes::COLOR_TYPE. Python and the JS client already had it (HAB-732). -
Habarico::Typesmodule holding the shared enum value lists (COLOR_TYPE,VIEW_MODE_TYPE,FIELD_FORMAT_TYPE, …), each named after its contract enum. Schema classes reference these instead of repeating the values inline, which is what used to let the same enum drift apart between classes (HAB-732).
Changed
-
The default save action (
SaveAction, emitted as thebottom_actionsof create, edit and action views) now defaults to theprimarycolour with theCheckicon instead ofsuccesswithSave, matching the frontend library's own default form-action button (HAB-956). -
BREAKING: The
limitquery parameter of a list endpoint is now bounded and validated, with theeluvia-base >= 3.39.0requirement this brings in (HAB-946). Alimitabove the maximum (1000 unless configured), a non-numeric one and a negative one are rejected with422 Unprocessable Entity, the error attached to thelimitfield - until now?limit=99999999reached Kaminari unchecked.?limit=0no longer raisesZeroDivisionError: it is a deliberate meta-only request, answering with themetablock and no records, as it already does in the Python library. A missing or blanklimitstill falls back to the default page size. (HAB-946) -
indexresolves its pagination inset_list_paginationinstead of an inlinebefore_actionlambda, so the callback reads like the others and the resolved limits can be overridden by a host controller. (HAB-946) -
BREAKING: The
order_byquery parameter of a list endpoint is now strictly validated, by the sameeluvia-base >= 3.39.0(HAB-915). An entry has to be exactlyfield:direction: a third segment is no longer dropped silently (name:asc:desc), and a direction is case-sensitive, soname:ASCis refused instead of read as ascending - the Python backend has always refused both. The wording and the keys of those two errors come fromeluvia-basenow, a malformed entry onorder_byand a bad direction onorder_by__<field>, which is whyhabarico.errors.order_by.invalid_valueand.unknown_directionare gone from the locale files along with the checks that used them. An integration sending anything butfield:asc/field:descneeds correcting; the frontend only ever sends that form. (HAB-915) -
BREAKING:
default_sortingis no longer'created_at:asc'by default - a resource that declares none is ordered by the primary key alone. No column is assumed to exist on the model: the old default made a model withoutcreated_atanswer422on every list request, and made every other resource carry a sort key it had never asked for. A resource that wants the old ordering declaresdefault_sorting 'created_at:asc'. A list view with nothing to sort by publishes nodefault_sortingin its schema now, rather than a descriptor with no field name. (HAB-915) -
BREAKING: The ordering of a list is broken on the primary key rather than on
created_at. Records that tie on the column being sorted by therefore come back in a different order than before - and in a stable one, since the primary key is unique where a timestamp is not, so a page no longer repeats one record and skips another. A resource that wantscreated_atin the ordering names it indefault_sorting;list_order_by_tiebreakeris the controller hook behind the choice. (HAB-915) -
A resource declaring
force_sorting trueno longer refuses anorder_byit is about to ignore: the parameter is not read at all there, so a request carrying one is served with the forced ordering instead of answering422. (HAB-915) -
The box schema generator emits an unset
cardasnilinstead of coercing it tofalse.cardis three-state in the contract now, wherefalsemeans "never a card" - keeping the coercion would have stripped the card off every labelled box. Setcard: falseexplicitly to render a box card-less with its label as a plain section heading. (HAB-827) -
Search::SearchMatcher::COMBINING_MARKmoved fromSearchHighlighterinto the free matcher that needs it, so no free code reaches into the paid layer. (HAB-812) -
The gemspec no longer sets
allowed_push_host, so the MIT gem can be published to rubygems.org, and itssource_code_uri/changelog_uripoint at the public repository. (HAB-812) -
BREAKING: Moved
Habarico::Serializer,Habarico::MetaSerializer, andHabarico::RecordSerializerintolib/habarico/serializers/and renamed them toHabarico::Serializers::Serializer,Habarico::Serializers::MetaSerializer, andHabarico::Serializers::RecordSerializer(HAB-614). -
BREAKING: Habarico no longer depends on
eluvia-auth.Habarico::ControllerandHabarico::ApplicationControllernow call anauthenticate_requesthook (Habarico::Authenticatable) instead of hardcodingEluvia::Auth::AuthHandler#authenticate. Host apps must implementauthenticate_request(andcurrent_user) in their ownApplicationController— e.g.def authenticate_request; authenticate(['public']); endto keep usingeluvia-auth— or point the newconfig.authenticate_methodsetting at an existing no-argument auth method (e.g.config.authenticate_method = :authenticate_user!) to skip the wrapper entirely. Habarico's own schema/search endpoints can inherit a host app's authentication automatically via the newconfig.base_controller_classsetting (HAB-830). -
BREAKING:
Habarico::ReflectionHelperreflection methods no longer carry a vendor name and are named after the base module they test, matching theiris_active_model_*/is_active_record_*siblings:is_eluvia_model_class?→is_attributes_model_class?,is_eluvia_model_instance?→is_attributes_model_instance?. Host apps calling either method must rename the call; behaviour is unchanged (HAB-806). -
BREAKING: Parameter schemas now use discriminator-first word order to match the contract, together with their
*Generatorclasses and DSL generator attributes:ViewListParameters→ListViewParameters,ViewDetailParameters→DetailViewParameters,ActionResourceParameters→ResourceActionParameters,ActionSaveParameters→SaveActionParameters,ActionResourceEndpointParameters→ResourceActionEndpointParameters,ActionViewParameters→ViewActionParameters,ActionLinkParameters→LinkActionParameters,ActionViewEndpointParameters→ViewActionEndpointParameters(HAB-732, HAB-708). -
BREAKING: Renamed the remaining schema classes to their contract counterparts:
EndpointMethodSchema→MethodSchema,EndpointSchema→EndpointsSchema,EnumItemSchema→EnumType,ConfirmDialogSchema→ConfirmDialogProperties,ListBoxSortableParameters→SortableParameters,KeyValueType→KeyValue,MenuViewChoice→ActionViewChoice,ActionDescriptorMenuViewParameters→ViewActionDescriptorParameters, andChoicesOptions/ImageOptions/AiOptions/RelationOptions/PhoneOptions/NumberFormatOptions→ChoicesFieldOptions/ImageFieldOptions/AiFieldOptions/RelationFieldOptions/PhoneFieldOptions/NumberFieldOptions(HAB-732). -
BREAKING:
LocalResourceSchemanow emits its endpoint map under theendpointskey instead ofendpoint, as the contract has always described; the Python library switches in 6.0.0 under HAB-730 (HAB-732). -
BREAKING:
FieldOptionsnow emits the number formatting options under thenumberkey instead ofnumber_format, matching the contract (HAB-732). -
BREAKING:
ViewEndpointParametersnow matches the contract: it carriespathand no longer carriesrecord_pk.pathis set only for action views — standard views are served from the standard resource path, which the frontend derives on its own.record_pkbelongs to the action and menu endpoint parameters, which target a record from elsewhere; on a view endpoint it was never read by any client. The path itself was already implemented inViewEndpointParametersGenerator#generate_path, it was simply never wired into the generated model (HAB-732). -
BREAKING:
FieldOptions.targetis now an enum overLinkTargetType(self/blank) instead of an untyped string, matchingLinkActionParameters.targetand the contract. The value is stored without the leading underscore — passblank, not_blank; the frontend prepends it when rendering (HAB-732). -
MenuSchema.view/.linknow reference the sharedViewActionParameters/LinkActionParameters; the contract has a single schema serving both menu items and actions (HAB-732).
Removed
-
BREAKING: The paid features moved out of this gem into
habarico-enterprise(HAB-812):- change history —
habarico/contrib/paper_trailno longer exists; install the paid gem andrequire "habarico_enterprise/paper_trail"instead (theHabarico::Contrib::PaperTrail::*constants are nowHabaricoEnterprise::PaperTrail::*; translation keys keep their names); - the cross-resource spotlight search —
GET /search,SearchService,SearchHighlighter,SearchResourceSchemaand its generator, and theglobal_fulltext_search_enabledDSL option and schema key. Per-resource search (search_fields) stays free and unchanged; a list response simply carries nometa.highlightswithout the paid gem; - aggregations and the dashboard — the
/{resource}/aggregateendpoint and its schema route,AggregateService,AggregateSchema/AggregateFieldSchemaand their generator,AggregateFieldDefinition, theaggregate_enabled/aggregate_max_buckets/aggregate_fields/aggregate_schema_generator/dashboard_enabledDSL options,validate_aggregate_fields!and the two schema keys. This matches the Python split, where aggregations and the dashboard are paid; - chunked/direct uploads —
habarico/contrib/uploadsno longer exists (it only ever shipped in the 0.57.0 betas). Install the paid gem andrequire "habarico_enterprise/uploads"instead; theHabarico::Contrib::Uploads::*constants are nowHabaricoEnterprise::Uploads::*,Habarico::UploadOptionsisHabaricoEnterprise::UploadOptions, the S3 endpoints are drawn by the routing service instead of being mounted by hand (drop thepost "uploads/…"lines fromconfig/routes.rb), and the tusd helper scripts move toscripts/in thehabarico-enterpriserepository. Base64 file/image uploads stay free and unchanged, as does the{filename:, content:, upload_key:}permit spec on file fields — a free-only install simply emits nooptions.uploadblock. This matches the Python split, where chunked uploads are paid.
User preferences stay free in full, including the
preferences.dashboardkey and the*.savedQueriespattern — same as Python, where both live in the freecontrib.preferences. - change history —
-
BREAKING: Duplicate
MenuViewParameters,MenuLinkParametersandMenuViewEndpointParametersschema classes - use the sharedViewActionParameters,LinkActionParametersandViewActionEndpointParametersinstead (HAB-732). -
BREAKING:
ActionResourceSchema- the action schema routes now return aLocalResourceSchema, which is what the contract describes for them (HAB-732).
Fixed
-
A list accepts the ordering its own schema asks the frontend for. The list view schema publishes
default_sorting- the resource's or the view's - and the frontend puts exactly that, or the primary key for a list with no sorting of its own, intoorder_byon the first request; the whitelist refused all three unless the field happened to be declared infieldstoo. A resource not listingcreated_atamong its fields therefore answered422(order_by__created_at- "Unknown field name.") on the very first load of its list, sincecreated_at:ascis the defaultdefault_sorting.sorting_fieldsstill bounds what a client may choose, but no longer refuses what the backend orders by regardless. (HAB-915) -
A list endpoint of a model without a
created_atcolumn can be listed at all.created_at:ascwas appended to the ordering of every list request, hardcoded and not configurable, so such a resource answered422(order_by__created_at- "Does not exist.") on everyGET- or a database error, on a model whose relation has noorder_2. Ties are now broken on the primary key instead, which is unique and which every model has. (HAB-915) -
A request sorting by the column the ordering falls back to kept the fallback's direction, not its own: the fallback was concatenated onto the ordering before it was parsed into a hash keyed by field name, so
?order_by=created_at:descanswered ascending. The tiebreaker is appended after the ordering is resolved now and leaves a field it already names alone. (HAB-915) -
A list can be sorted by everything its schema advertises as
sort_by, not only by a declared field that is also a column of the model: the lower bound of a range field, a column reached over an association declared in a list view (author__name), asort_bywritten on aFieldReferenceby hand, and a virtual column registered withorder_attr. The frontend sorts by exactly thesort_byit is given, so those columns answered422on their own sort link. (HAB-915) -
Every unknown field name in one
order_byis reported in a single422, the way the Python backend answers, instead of only the first one. (HAB-915) -
?order_by=(empty) answered500. It is read as "not supplied" now and falls back todefault_sorting. Note the difference from the Python backend, where an emptyorder_bymeans "do not sort at all": a Kaminari scope always needs an ordering. (HAB-915) -
A
default_sortingwith a stray third segment (placed_at:asc:desc) described itself to the frontend with the direction of the last segment while the backend ordered by the second one. Both read the second one now. (HAB-915) -
Schemas::APP_DENSITYacceptsstandardinstead ofnormalas the middle UI density, matching the value the frontend has sent sincenode@6.7.0.PUTonpreferences.layout.app.densityanswered422for the only value the frontend produces; anormalstored by an older frontend now fails validation on the next read, is deleted, and the registered default takes its place. (HAB-939) -
The built-in
:searchtoolbar filter now generates its schema withformat: 'search'instead ofnil, so the frontend renders it with the dedicatedSearchField/SearchInputcomponent instead of falling back to the generic text input. (HAB-814) -
README documented
link_target: '_blank'forLinkActionandLinkMenuItem, contradicting theself/blankenum onLinkActionParameters.target. The frontend prepends the underscore itself (window.open(url, "_" + target)), so the documented value produced a__blankwindow name; the examples now useblank(HAB-732). -
README described the field-level
targetas a "target resource name for nested/related fields", conflating it with a box'starget. It is the link target of a clickable field, matchingFieldOptions.targetin the contract (HAB-732). -
GlobalResourceSchemareferenced the non-existentHabarico::BaseResourceActionSchemaandHabarico::BaseViewSchema, andMenuViewParametersreferenced a non-existentHabarico::MenuEndpointParameters; all three now resolve (HAB-732). -
Bumped minimum
eluvia-basegemspec dependency from>= 3.34.0to>= 3.37.0; earlier versions lackEluvia::Serializers::FieldsetSerializer, which Habarico requires, causing host apps to fail to boot (HAB-892).
Security
- BREAKING: Denying
can_readnow really denies read, matching the Python backend:showrequirescan_read(previously fetching by ID bypassed it entirely), andindexwithoutcan_readis forbidden outright instead of silently degrading into anid__in-only mode. The implicitid__infallback becomes an explicit opt-in - the newallow_read_by_idDSL option (defaultfalse) re-opens ID-based reads for a resource without read permission:showby ID and theid__in-onlyindexmode. Migration: a resource that other services resolve references against without list access (ReferenceFieldlabel resolution viaid__in) must now setallow_read_by_id true, otherwise those lookups return 403. Additionally,set_record(backingshow/update/destroy) now appliespolicy_scope, likeindexalready did, instead of onlydefault_filter- a record outside the Pundit scope (a different tenant, a soft-deleted row, ...) can no longer be read or mutated by guessing its ID, regardless ofallow_read_by_id. (HAB-899) (HAB-899) - User preferences are now size-limited. A single preference value is capped by
Habarico::Contrib::Preferences.max_value_bytes— a hash resolved by the key, or pattern, a preference was registered under, falling back to its"default"entry (16 KB by default, 32 KB forpreferences.dashboardand*.savedQueries) — and the number of preferences one user may hold bymax_keys_per_user(2000). Previously neither was bounded: the registered JSON Schemas carry nomaxLength/maxItems, a saved query'sfilteris a free-form object, and the pattern keys match an unbounded set of names, so any authenticated user could fill thehabarico_user_preferencestable with arbitrarily large rows, in arbitrary numbers. Both refusals are422. The count is checked only when a new key is created, so a user already past the limit can still overwrite or shrink what they stored, and stored values are never re-checked on read. A key registered by the host app takes the"default"entry unless it passes its ownmax_bytes:; anilentry in the hash, or anilmax_keys_per_user, turns that limit off.max_value_bytesis read when a preference is registered, likedefaults— set it beforerequire "habarico/contrib/preferences". The defaults sit an order of magnitude above what the frontend writes, but an app that stores unusually large preferences should raise them. Found in the HAB-894 security audit. (HAB-916)
[0.56.0] — 2026-08-03
Added
- Query param
fieldsfor selecting which top-level fields (andmeta/meta.schema: repr/schema/tags/trigger_action, name/label/pk_field_name/actions) are returned in serialized responses, with__all__/__default__wildcards, matching the Python implementation (HAB-614). - Nested
fieldsgrammar (relation(subfield,...)) now parses for relation fields too, laying the groundwork for future nested relation serialization; covered by parity tests against the Python behavior (HAB-621). - Box-level
toolbar_actionson any box (BoxSchema), rendered in the box's local toolbar (box header / card header area); on a tab/wizard container they appear on the last step (HAB-622). bottom_actionson any box (BoxSchema), rendering action buttons below the box fields - similar to form submit buttons at the view level (HAB-700).list_actionsslot on list box and list view (ListBoxParameters,ViewListParameters/ListViewSchemaParameters) for utility actions (export, print, …) rendered next to the filter bar, separate from row and toolbar actions (HAB-700).list_toolbar_actionsonListBox, emitted astoolbar_actionsinside thelistparams - the resource list's own table toolbar (e.g. create), separate from the box-leveltoolbar_actionswhich belongs to the box root (HAB-700).collapsibleflag on boxes (open/closed) making any box collapsible with a default expanded/collapsed state (HAB-622).TabBoxParameters(show_toolbar_navigation) andWizardBoxParameters(show_toolbar_navigation,validate_steps) for tab and wizard boxes, plus a newWizardBoxbox type (HAB-622).- Configurable
inlineon array box (ArrayBoxParameters), defaulting to inline unless the box contains nested boxes (HAB-622). single_click_actionanddouble_click_actionon list box and list view (ListBoxParameters,ViewListParameters), each anActionDescriptor; a plain action name is normalized into a descriptor (HAB-613).keep_linked_parenton action view parameters (ActionDescriptor.viewandActionSchema.view), settable from the DSL viaview_keep_linked_parent(HAB-613).sidepanelpresentation mode for action/menu views (modeenum) (HAB-613).modeon the view schema (GlobalViewSchema/ openapiBaseViewSchema) advertising a view's default presentation mode (redirect/dialog/sidepanel) for triggers that open it without their own mode; set from the DSL viaview_mode(HAB-613).- Typed fulltext search:
search_fieldsDSL andFieldResolver(HAB-600), result highlighting viaSearchHighlighter(HAB-601), and a Habarico-ownedSearchMatcherreplacing the eluvia-base filter (HAB-602). - Optional PaperTrail change-history contrib module (HAB-498).
- Optional user-preferences contrib module (
habarico/contrib/preferences): per-user, JSON-Schema-validated key/value store withuser-preferencesREST endpoints, a preference registry (exact + pattern keys), built-in base preferences, and an install generator - parity with the Pythonhabarico.contrib.preferencesapp (HAB-706).
Changed
- Renamed detail view
form_actionstobottom_actions(DetailViewSchemaParameters) (HAB-622). ListBox: the box-leveltoolbar_actionsnow targets the box root (box / card header), consistent with all other box types; the table's own toolbar is set via the newlist_toolbar_actionsinput, which is emitted astoolbar_actionsinside thelistparams (HAB-700).
Fixed
- Custom (non-standard)
canpermission callables are now invoked with a consistent(permission, context)argument order in the list/global schema, matching the documented API and the per-record check; previously the two were swapped without a record, hiding custom-permission actions from the list/global schema (HAB-603). - Object-level action meta (
ObjectResourceSchema#actions) no longer differs depending on whether it was returned by the list or the detail/update endpoint; record-bound actions likedeleteare now always included regardless ofshow_in_detail, fixing row actions disappearing after an edit-modal save.
Removed
show_save_button/show_next_buttonon tab item box (andTabItemBoxParameters); tab and wizard navigation is now controlled by the container'sshow_toolbar_navigation/validate_steps(HAB-622).- Filter
is_in_toolbarflag; filters now expose onlyis_always_on(always visible) (HAB-613). keep_linked_parentfromListBoxParameters; it now lives on the action view parameters instead (HAB-613).
[0.52.0] - 2026-07-09
Added
- Support for nested field options in
fieldOptions(HAB-571).
[0.51.0] - 2026-06-26
Added
- Type metadata for
any_attr(HAB-564). icononEnumItemSchema, plustimeandczech-id-card-numberfield types (HAB-478).
Fixed
- Default filter no longer overridden by request filters (VD-2651).
- Per-record permission handling (VD-2625).
Changed
- Removed the built-in Czech ID card format (HAB-478).
- Updated documentation (HAB-483).
[0.50.1] - 2026-06-18
Changed
- Use the latest
v1of ci-toolbox.
[0.50.0] - 2026-06-10
Added
- Polymorphic field/box support (HAB-519).
- Gallery box (HAB-481).
- Object box type (HAB-480).
- Save action type (HAB-479).
- Trigger action and search endpoint (HAB-482).
- Extended
FieldOptions/BoxOptions,ListBoxParameters,ActionDescriptorandFieldDescriptor(HAB-475, HAB-477). Gemfile.lockand a pinned Ruby version.
Fixed
- Search moved to a dedicated service (HAB-482).
- Type fixes for field descriptors (HAB-475).
[0.49.4] - 2026-05-29
Fixed
NoMethodErrorby replacingview.fieldswithview.boxes(VD-2615).
[0.49.3] - 2026-05-22
Fixed
- Validation fix (VD-2587).
[0.49.2] - 2026-05-22
Fixed
- Validation fix (VD-2587).
[0.49.1] - 2026-05-22
Fixed
- Filter controller fix (VD-2587).
[0.49.0] - 2026-05-14
Added
- Validation of the
order byparameter (HAB-486). - Error raised for missing filters (HAB-486).
- Unified boxes and fields into a single polymorphic
boxescollection (HAB-255). - Documented rich text syntax in the README (HAB-495).
[0.48.0] - 2026-04-20
Baseline for this changelog; see Git history for earlier releases.