{"title":"Granite to Grant Migration","description":"Move an existing Granite model layer to the Grant version pinned by Amber CLI 2.0.6","section":"migration-guide","version":"v2","path":"migration-guide/granite-to-grant","canonical_url":"https://amberframework.org/docs/v2/migration-guide/granite-to-grant","markdown_url":"https://amberframework.org/docs/v2/migration-guide/granite-to-grant.md","inherited":false,"content_markdown":"# Migrating from Granite to Grant\n\nGrant is the default model layer in new Amber CLI `2.0.6` web applications.\nThat does not make an ORM replacement part of the Amber 1-to-2 framework\nupgrade. First prove that the existing application can run on Amber\n`2.0.0-beta.5` with its current persistence stack. Start this guide only when\nmoving to Grant is an explicit second decision.\n\n## Establish the safety boundary\n\nBefore editing a model:\n\n1. Record the current Crystal, Amber, Granite, driver, and database versions.\n2. Run the complete test suite and compile the application binary.\n3. Back up the database and restore that backup into a disposable environment.\n4. Capture representative reads, writes, validations, associations,\n   transactions, callbacks, and error behavior.\n5. Choose one low-risk model boundary for the first migration.\n\nDo not run two migration systems against the same schema without one explicit\nowner. Amber CLI uses Micrate SQL under `db/migrations/`; keep the application's\nexisting migration history and decide where new versions will be recorded\nbefore applying anything.\n\n## Pin Grant and one driver\n\n**File: `shard.yml` — add the same reviewed Grant source used by a generated\nAmber CLI `2.0.6` application plus the application's database driver.**\n\n```yaml\ndependencies:\n  grant:\n    github: crimson-knight/grant\n    commit: 2665a978b43ac608c68cde9243821f8f8f053372\n  pg:\n    github: will/crystal-pg\n    version: 0.30.0\n```\n\nThe example uses PostgreSQL. Use the SQLite or MySQL dependency from a freshly\ngenerated `2.0.6` app when that is the database being migrated. Do not add all\nthree drivers.\n\n## Register the Grant connection\n\n**File: `config/database.cr` — register a connection loaded by the app's\nexisting `require \"../config/*\"` entry point.**\n\n```crystal\nrequire \"grant\"\nrequire \"grant/adapter/pg\"\n\nGrant::Connections << Grant::Adapter::Pg.new(\n  name: \"primary\",\n  url: ENV[\"DATABASE_URL\"]? || Amber.settings.database_url\n)\n```\n\nUse `Grant::Adapter::Sqlite` with `require \"grant/adapter/sqlite\"` or\n`Grant::Adapter::Mysql` with `require \"grant/adapter/mysql\"` for those drivers.\n\n## Translate one model without changing its table\n\n**Existing Granite file: `src/models/user.cr`.**\n\n```crystal\nclass User < Granite::Base\n  connection pg\n  table users\n\n  column id : Int64, primary: true\n  column email : String\n  column name : String?\n  column admin : Bool = false\n  column created_at : Time?\n  column updated_at : Time?\nend\n```\n\n**Grant replacement: `src/models/user.cr`.**\n\n```crystal\nclass User < Grant::Base\n  connection primary\n  table users\n\n  column id : Int64, primary: true\n  column email : String\n  column name : String?\n  column admin : Bool = false\n\n  timestamps\nend\n```\n\nKeep `connection primary` and `table users` explicit during a migration. This\nmatches the supported generator and prevents an inference change from silently\npointing at another connection or table. `timestamps` maps the conventional\n`created_at` and `updated_at` columns; verify their exact database types before\nremoving the previous declarations.\n\n## Preserve schema before changing behavior\n\nAn ORM migration does not inherently require a database schema migration. If\nthe existing table already matches the Grant columns, first make the new model\nread and write the existing schema. Add Micrate SQL only for an intentional\nschema change.\n\nWrite a focused spec against the restored disposable database:\n\n```crystal\nuser = User.new\nuser.email = \"migration@example.com\"\nuser.admin = false\nuser.save.should be_true\n\npersisted = User.find(user.id)\npersisted.should_not be_nil\npersisted.not_nil!.email.should eq(\"migration@example.com\")\n```\n\nThen prove update and destroy, required and nullable values, unique constraints,\ntimestamps, and the error paths used by the application.\n\n## Translate application operations deliberately\n\nDo not perform a global search-and-replace. Convert one behavior at a time and\nkeep a spec beside it.\n\n```crystal\n# Collection\nusers = User.all.to_a\n\n# Primary-key lookup\nuser = User.find(params[:id])\n\n# Typed assignment and persistence\nuser = User.new\nuser.email = schema.email.not_nil!\nuser.name = schema.name\nuser.save\n\n# Delete\nuser.destroy\n```\n\nFor filtering, associations, validations, callbacks, transactions, and\nsecurity APIs, follow the matching [Grant guides](../guides/models/grant/) and\nverify the behavior against the pinned commit. Do not assume a similarly named\nGranite method has identical return types, callback order, transaction scope,\nor error semantics.\n\n## Decide whether the ORMs may coexist\n\nCoexistence can be useful for a staged migration, but it is not automatic.\nBefore running Granite and Grant together, prove:\n\n- their connection pools do not compete for lifecycle ownership;\n- only one migration system advances the schema;\n- a transaction does not falsely imply atomicity across different pools;\n- callbacks and validations are not executed twice;\n- two classes writing one table agree on types, defaults, timestamps, and\n  optimistic-locking behavior;\n- application code names which ORM owns each model.\n\nIf those conditions are not testable, migrate in a maintenance window or a\nseparate deployment rather than carrying two active writers.\n\n## Completion gates\n\nFor every migrated model, keep evidence for:\n\n- schema compatibility and reversible migration SQL when schema changed;\n- representative create, read, update, and destroy operations;\n- nullable and required fields on new records;\n- validations and database constraints;\n- associations and query counts;\n- callback order and external side effects;\n- transaction rollback behavior;\n- production-shaped performance for critical queries.\n\nOnly remove Granite after no application file, job, task, or maintenance script\nrequires it and a restored production backup passes the Grant-backed suite."}