Conversation
BREAKING CHANGE: Ruby >= 3.0 / Rails >= 6 are now required.
Rails 5.2 cannot run on Ruby 3, so these produced no CI jobs once Ruby 2.7 was dropped. Removes the two appraise blocks and their generated gemfiles.
Creates: resource = MyResource.build(params) resource.data # unsaved model, attributes applied resource.data.valid? # inspect before committing to anything resource.save # persists that same instance Updates: resource = MyResource.find(params) resource.assign_attributes(params) resource.data.changed # dirty tracking works resource.update # or in one call resource.update(params) Attributes are assigned once, before the persistence hooks fire. BREAKING CHANGE: around_persistence hooks receive the assigned model instead of the attributes hash. Move attribute-hash modifications to before_attributes, or set values on the model. Custom create/update overrides that should receive a pre-assigned model must accept an assigned_model: keyword. See UPGRADING.md
# [2.0.0-beta.1](v1.12.2...v2.0.0-beta.1) (2026-07-30) ### Features * drop Ruby 2.7 and Rails 5.2 support ([e905ddb](e905ddb)) * the model you inspect is the model that saves ([#465](#465)) ([a905fff](a905fff)) ### BREAKING CHANGES * around_persistence hooks receive the assigned model instead of the attributes hash. Move attribute-hash modifications to before_attributes, or set values on the model. Custom create/update overrides that should receive a pre-assigned model must accept an assigned_model: keyword. See UPGRADING.md * Ruby >= 3.0 / Rails >= 6 are now required.
# Conflicts: # CHANGELOG.md # lib/graphiti/resource/persistence.rb # lib/graphiti/version.rb
# [2.0.0-beta.2](v2.0.0-beta.1...v2.0.0-beta.2) (2026-07-30) ### Features * add Resource.wrap as an easy way to use graphiti serialization on models fetched via other means (and not via graphiti's finders) ([#513](#513)) ([fcd19e2](fcd19e2)) * deprecate mutating attributes in around_persistence hooks ([#514](#514)) [skip ci] ([8410ab0](8410ab0))
…signatures This way create and update overrides keep their 1.x signatures and we're not breaking existing setups. This was an oversight in the original commit.
# [2.0.0-beta.3](v2.0.0-beta.2...v2.0.0-beta.3) (2026-07-31) ### Features * carry the assigned model on the resource, not through override signatures ([8ad848d](8ad848d))
graphiti_spec_helpers gem is retired. Graphiti is its only home now, at Graphiti::SpecHelpers. BREAKING CHANGE: remove graphiti_spec_helpers from your Gemfile. Prefer Graphiti::SpecHelpers and "graphiti/spec_helpers/rspec"; the old namespace and require paths still resolve, warn, and are removed in 3.0.
Brings over Graphiti::Rails, the generators and the rake tasks. Rails is still detected at runtime, so railties does not become a dependency. Controllers now opt in with Graphiti::Rails::Controller rather than Graphiti attaching itself to every ActionController, which graphiti-rails#52 has been open about since 2020. Exception handling is left for the rescue_registry decision. BREAKING CHANGE: remove graphiti-rails from your Gemfile. Controllers serving Graphiti resources must `include Graphiti::Rails::Controller` — previously every controller received it whether it wanted it or not. Graphiti::Responders is now Graphiti::Rails::Responders.
Loose requirements let a newer minor satisfy them, so rails-7-1 resolved to 7.2 and rails-8-0 to 8.1. sqlite3 pins now match what each Rails adapter demands at require time, and the CI matrix uses explicit rows — an exclude naming a gemfile that does not exist is silently ignored, which had left four dead rows.
Ruby 3.0 and 3.1 are both past end of life, and 3.2 is what Rails 8 requires. Rails 6.1 and 7.0 do not support Ruby 3.2. Apps that cannot move stay on the 1.x branch. BREAKING CHANGE: Ruby >= 3.2 and Rails >= 7.1 are now required.
graphiti-rails had moved to rescue_registry while core stayed on graphiti_errors, so both loaded in every Rails app and each registered its own handlers. Only graphiti_errors' serializers survive, as Graphiti::ErrorSerializers. ConflictRequest gets a registered handler for the first time, and reports a conflict rather than code "bad_request" beside a 409. BREAKING CHANGE: graphiti_errors is no longer a dependency and must be removed from the Gemfile, along with any `include GraphitiErrors`. GraphitiErrors::Validation::Serializer is now Graphiti::ErrorSerializers::Validation, and GraphitiErrors.enable!/disable! becomes handle_request_exceptions. 409 responses now report code "conflict" and title "Conflict Error".
Adds shims for names that had no replacement. Requiring "graphiti-rails", "graphiti_errors" or "graphiti/responders" raised LoadError, and `include Graphiti::Rails` did nothing at all. Both work again and warn. Also fixes deprecation messages that pointed at gems that no longer exist. BREAKING CHANGE: nothing removed, everything warns and goes away in 3.0. Except `include GraphitiErrors`, which now raises, as rescue_registry replaced it, so there's nothing to point it at.
Its serializers and ExceptionHandler both worked without Rails, and the guide framed the whole change as a Rails concern. The serializers move and keep working; RescueRegistry::ExceptionHandler replaces the payload builder, with the note that it needs rack required first or it raises NameError.
It was the only mixin row missing its include.
Moves the docs to within this repository so docs + changes can ship together. New docs setup is versioned, and the old 1.x docs will be archived and available via the version menu. New docs site is built with docusaurus, and the concepts and prose have been greatly simplified and reorganized.
The docs dropped the /guides prefix, so the URLs printed by Unlinkable, SideloadParamsError, SideloadAssignError and InvalidLink all 404'd. resource_spec asserts on one of those messages verbatim, so it moves too.
…on guide and UPGRADING.md
A belongs_to sent only a link in 1.x, and to learn the id took a second request. That id is the foreign key already on the parent, so rendering it loads nothing extra (which was not the case with #168 which caused #185 to revert it) A belongs_to that cannot use its foreign key still loads the association, so it stays opt-in behind always_include_resource_ids, as does has_many. That covers a scope or params block, a base_scope, a polymorphic or remote target, and a custom primary_key. always_include_resource_ids_by_default now sets this per resource, replacing the monkey patch #167 has been recommending since 2020. Closes #167
Overriding an inherited belongs_to had no effect on the payload. The subclass gets its own serializer class, but it inherits the parent every relationship block, and each block closes over the sideload it was built for. Application skipped any name the serializer already answered to, so the override kept rendering through the parent sideload: the wrong resource, and with linkage now derived from the foreign key, the wrong type and id. Serializers record which sideload each generated block was built for, so a redeclared relationship is told apart from one already applied. A block the application wrote by hand has no such record and is still left alone.
The public entry points (Scope#resolve, Scope#resolve_sideloads, Sideload#resolve, PolymorphicBelongsTo#resolve) now share a uniform shape: branch on concurrency, then delegate to the public future_* method or a private sync_* method
# [2.0.0-beta.12](v2.0.0-beta.11...v2.0.0-beta.12) (2026-08-30) ### Bug Fixes * compare each concurrency path against itself in the table to better illustrate the performance wins ([d0f0e92](d0f0e92)) ### Features * add schema rake tasks, so checking a schema no longer means running the suite ([6f4783d](6f4783d)) * declare Graphiti's client errors in rescue_responses ([e369824](e369824)) * mark deprecated settings in the generated ApplicationResource ([52b0642](52b0642)) * take error text from locale keys ([d01fd06](d01fd06)), closes [#216](#216)
…le file The file now lists each code with the title it renders, the exceptions that raise it, and a detail for the 500 fallback, which had none.
Graphiti.context[:namespace] was technically private API, but it was given publicly as a solution, which makes it practically public, so the old key has a deprecation around it now.
…bers A thread local is fiber scoped in MRI, so context, transaction hooks and debug chunks were lost in any fiber a request spawned, and a sideload resolving inside one would wait on the bounded pool from a pool thread. Point rescue registry at develop branch with fiber fix until 1.1 is shipped Closes #540.
?include=positions.employee.positions&filter[positions.rank]=1 returned positions 1 and 2 where ?include=positions returned 1.
?include=positions.classification rendered the employee's own classification as data: nil.
# [2.0.0-beta.13](v2.0.0-beta.12...v2.0.0-beta.13) (2026-08-31) ### Bug Fixes * compare the endpoint id to the payload id as strings ([62051d7](62051d7)) * evaluate writable guards under the action being performed ([d56b86a](d56b86a)) * **generators:** stop injecting a routes host into config/application.rb ([db92d06](db92d06)) * keep a belongs_to linkage when another include shares its name ([2c9c7c7](2c9c7c7)) * keep deep filters narrow when an include path repeats ([833a10b](833a10b)) * keep request state in fiber storage so it survives into child fibers ([6569935](6569935)) * only register the jsonapi mime type when it is missing ([88401ab](88401ab)) * with_options accepts the predicate backed relationship options ([71ab441](71ab441)) ### Features * write every error code Graphiti renders into the generated locale file ([15f19c1](15f19c1)) ### Performance Improvements * record latest performance stats ([7380ef9](7380ef9))
# Conflicts: # CHANGELOG.md # lib/graphiti/version.rb
Graphiti stopped calling them in 2018 and reaches the adapter through #save.
Contributor
|
🎉 This PR is included in version 2.0.0 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
|
Awesome! 💯 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Graphiti 2.0
Graphiti 2.0 collapses the satellite-gem constellation into one gem, makes persistence behave the way you always assumed it did, and ships with a rebuilt documentation site. There are some important breaking changes that improve ergonomics, but nearly everything from 1.x still runs under 2.0 with deprecation warnings, so most apps can upgrade incrementally. Upgrade path here: https://graphiti.dev/upgrading/
One gem instead of four
graphiti-rails,graphiti_spec_helpers, andgraphiti_errorsare now part of graphiti itself. Remove them from your Gemfile after upgradinggraphitiand everything keeps working. Rails integration is now opt-in per controller withinclude Graphiti::Rails::Controller, instead of every controller silently receiving it (#535). Exception handling is built onrescue_registry(#526), and spec helpers live atGraphiti::SpecHelperswith a new set of RSpec matchers for asserting resource attributes and relationships (graphiti-api/graphiti_spec_helpers#14).This collapse was primarily motivated by the fact that 80-90% of graphiti users are using it with rails, and collapsing the surface area makes maintenance and understanding easier.
Persistence that matches your mental model
The model you inspect is the model that saves (#465, docs). Hooks and overrides see the same assigned model instance that gets persisted, instead of a parallel attributes hash that could drift from reality.
around_persistencehooks receive that model, writable guards now receive the model and attribute name so authorization can consider the actual record (#511), and the resource carries its assigned model rather than threading it through override signatures.Writable guards evaluate under the action being performed on every path, so
writable: proc { current_action == :create }answers the same whether or not you inspected the model first.Serialization wins
belongs_torenders relationship resource ids by default, solving an issue that had been open since 2019 (Always include id and type for belongs_to relationships #167), withbelongs_to_resource_ids_by_defaultand a per-relationshipresource_idsoption to control it. The default comes without a performance cost, since linkage renders from foreign keys already on the record.Resource.wrapserializes models you fetched yourself, so you can use graphiti's serialization without going through its finders (feat: add Resource.wrap as an easy way to use graphiti serialization on models fetched via other means #513)."data": nullholes in included (Smarter response resource squashing #476).?include=positions.classificationused to blank the employee's own classification.?include=positions.employee.positions&filter[positions.rank]=1returned every position where the shallow path correctly returned one.Concurrency
Concurrent sideload resolution is more reliable, with fixes for deadlocks and swallowed errors and new stress specs covering behavior under load. Request state now lives in fiber storage rather than thread locals, so context, transaction hooks and debug chunks survive into fibers a request spawns. That makes graphiti work under fiber based servers such as falcon (#540), and closes a case where a sideload resolving inside a fiber would wait on the bounded pool from a pool thread.
ActiveSupport::CurrentAttributesare carried onto pool threads, so a sideload resolving concurrently still seesCurrent.userand anything else your app sets per request. Previously that state was left behind on the request thread, and a resource reading it during a concurrent sideload saw nothing.Performance
Serialization is faster and allocates less, with the biggest gains on simple requests. Every release records its object allocations so changes are visible over time, and
rake performance:currentreports them locally.Guardrails and tooling
rake graphiti:auditscans your resources for latent issues.rails g graphiti:localewrites every error code graphiti renders into a locale file, ready to customize or translate.MissingRelationshipMethodinstead of failing obscurely.--controllername, so it no longer overwrites a controller you already have (ResourceGenerator overwrites existing controller #528).context_namespaceis nowcurrent_action, which says what it actually is.A documentation site that lives with the code
The docs site was rebuilt, absorbed into this repository, and now publishes automatically as new versions are cut. Runtime error messages link to the new docs, and there's a dedicated 2.0 upgrade guide. Behavior and documentation updates can ship at the same time!
Modern platform
Requires Ruby 3.2+ and Rails 7.1+. Tested against Ruby 3.2 through 4.0 and Rails 7.1 through 8.1, on a rebuilt CI matrix and release pipeline. Apps on older Rubies and Rails stay supported on the 1.x maintenance branch.
Breaking changes
The upgrade guide walks through each of these with migration steps. The short version:
include Graphiti::Rails::Controller. Under graphiti-rails, every controller received the integration automatically (Include Graphiti stuff only in api #535).around_persistencehooks receive the assigned model instead of the attributes hash (feat!: the model you inspect is the model that saves #465). Move attribute-hash edits tobefore_attributes, or set values on the model. Customcreate/updateoverrides keep their 1.x signatures and need no changes.include GraphitiErrorsraises instead of warning, because rescue_registry replaced it and there is nothing left to point it at.GraphitiErrors.enable!/disable!becomeshandle_request_exceptions, and 409 responses now report code "conflict" and title "Conflict Error".belongs_torelationships render resource linkage by default, so those relationship objects gain adatakey in payloads (Always include id and type for belongs_to relationships #167). Setbelongs_to_resource_ids_by_default = falsefor 1.x output.allow_nilanddeny_emptyfold into oneblanks:filter option, and the pagination settings are named after the page params they control.Everything else from 1.x resolves with a deprecation warning and keeps working until 3.0.
Closes
belongs_torenders resource linkage by default, from foreign keys already on the record so no extra queriesGraphitiSpecHelpers::Sugar#520 - generated specs call the helpers by their full names, and theSugaraliases are deprecatedregister_exceptionbehaves as documented--controllerlets a resource point at a controller without overwriting itRespondersmodule if responders gem is present #530 - superseded, Rails integration is now explicit opt-in rather than auto-includeinclude Graphiti::Rails::Controller(transferred from graphiti-rails#52)includedalways renders when the client asked for it, so resolving the proxy first no longer drops sideloadsdetail, and every code's text is overridable from a locale keyRelated work from the satellite gems
These PRs live in the now-absorbed repos and shaped what shipped here:
Graphiti::Rails::RakeHelpersGraphiti::SpecHelpers::Matchers--controller, which names the controller outright rather than deriving it from.graphiticfg.yml