{"title":"Grant ORM","description":"The ActiveRecord-style ORM included in Amber V2 web applications","section":"guides/models","version":"v2","path":"guides/models/grant","canonical_url":"https://amberframework.org/docs/v2/guides/models/grant","markdown_url":"https://amberframework.org/docs/v2/guides/models/grant.md","inherited":false,"content_markdown":"# Grant ORM\n\n> **Supported web path:** Amber CLI `2.0.6` includes Grant in every generated\n> web application and pins the reviewed V2 commit. The Grant project keeps its\n> own release lifecycle, so preserve the generated pin when following this beta.\n\nGrant is an ActiveRecord-style ORM for Crystal that provides a familiar\ninterface for database operations. It is the default model layer for the Amber\nV2 web template, with SQLite as the zero-setup database.\n\n## Where the examples go\n\n- Model declarations, columns, associations, validations, and callbacks belong\n  in one class file under `src/models/`, such as `src/models/user.cr`.\n- CRUD and query snippets run from the controller, job, service, or spec that\n  owns the operation; they are expressions, not complete source files.\n- Register database connections in a direct file under `config/`, such as\n  `config/database.cr`, because the V2 entry point requires `config/*`.\n- Run every command from the application root, beside `shard.yml`.\n\nBlocks on this page use those destinations unless a closer label says\notherwise.\n\n## Why Grant?\n\nGrant aims for feature parity with Rails 8+ ActiveRecord while leveraging Crystal's compile-time type safety:\n\n- **Familiar API**: If you know ActiveRecord, you know Grant\n- **Type Safety**: Compile-time checking eliminates many runtime errors\n- **Zero-cost Abstractions**: Performance comparable to hand-written SQL\n- **Fiber-based Concurrency**: Native async support without callback complexity\n- **Horizontal Sharding**: Built-in support for distributed databases\n\n## Feature Overview\n\n| Category | Features |\n|----------|----------|\n| **Core** | Models, columns, timestamps, CRUD operations |\n| **Associations** | belongs_to, has_one, has_many, has_many :through, polymorphic |\n| **Validations** | All standard validators, custom validations, conditional validation |\n| **Callbacks** | Full lifecycle hooks including transaction callbacks |\n| **Queries** | Fluent interface, scopes, complex conditions, eager loading |\n| **Security** | Encrypted attributes, secure tokens, signed IDs |\n| **Advanced** | Enums, serialization, dirty tracking, optimistic/pessimistic locking |\n\n## Quick Start\n\n### Define a Model\n\n**File: `src/models/user.cr` — create this model class.**\n\n```crystal\nclass User < Grant::Base\n  connection pg\n  table users\n\n  column id : Int64, primary: true\n  column email : String\n  column name : String\n  column role : String = \"user\"\n  column active : Bool = true\n\n  has_many :posts\n  has_one :profile\n\n  validates_presence_of :email, :name\n  validates_email :email\n  validate_uniqueness :email\n\n  scope :active, -> { where(active: true) }\n  scope :admins, -> { where(role: \"admin\") }\n\n  timestamps\nend\n```\n\n### Basic Operations\n\n**File: the controller, job, service, or spec that owns the user operation.**\n\n```crystal\n# Create\nuser = User.create!(email: \"alice@example.com\", name: \"Alice\")\n\n# Read\nuser = User.find(1)\nusers = User.where(active: true).order(:name).limit(10)\n\n# Update\nuser.update!(name: \"Alice Smith\")\n\n# Delete\nuser.destroy!\n```\n\n### Associations\n\n**Files: declare relationships in the matching files under `src/models/`;\nexecute the usage examples from an application operation or spec.**\n\n```crystal\n# Define relationships\nclass Post < Grant::Base\n  belongs_to :user\n  has_many :comments, dependent: :destroy\n  has_many :taggings, as: :taggable\n  has_many :tags, through: :taggings\nend\n\n# Use associations\nuser = User.find(1)\nuser.posts.each do |post|\n  puts post.title\n  puts post.comments.count\nend\n\n# Eager loading (N+1 prevention)\nposts = Post.includes(:user, :comments).where(published: true)\n```\n\n### Validations\n\n**File: `src/models/product.cr` — keep these validations inside `Product`.**\n\n```crystal\nclass Product < Grant::Base\n  column price : Float64\n  column stock : Int32\n  column sku : String\n\n  validates_presence_of :sku, :price\n  validates_numericality_of :price, greater_than: 0\n  validates_format_of :sku, with: /\\A[A-Z]{2}-\\d{4}\\z/\n  validate_uniqueness :sku\n\n  validate \"price must be reasonable\" do |product|\n    product.price < 1_000_000\n  end\nend\n```\n\n### Callbacks\n\n**File: `src/models/order.cr` — keep these callbacks and private methods inside\n`Order`.**\n\n```crystal\nclass Order < Grant::Base\n  before_create :generate_order_number\n  before_save :calculate_total\n  after_create :send_confirmation\n  after_commit :update_inventory, on: :create\n\n  private def generate_order_number\n    self.order_number = \"ORD-#{Time.utc.to_unix}-#{SecureRandom.hex(4)}\"\n  end\n\n  private def calculate_total\n    self.total = line_items.sum(&.price)\n  end\nend\n```\n\n## Database Support\n\nGrant supports multiple databases:\n\n- **PostgreSQL** (recommended): Full feature support including arrays, JSONB, UUID\n- **MySQL**: JSON columns, full-text search\n- **SQLite**: Great for development and testing\n\n**File: `config/database.cr` — create this direct config file so the generated\nV2 entry point loads it through `require \"../config/*\"`.**\n\n```crystal\n# config/database.cr\nGrant::Connections << Grant::Adapter::Pg.new(\n  name: \"primary\",\n  url: ENV[\"DATABASE_URL\"]\n)\n```\n\n## Getting Started\n\n1. [Models and Columns](basics/) - Define your data structure\n2. [Associations](associations/) - Connect related models\n3. [Validations](validations/) - Ensure data integrity\n4. [Callbacks](callbacks/) - Hook into the lifecycle\n5. [Querying](queries/) - Find and filter data\n6. [Transactions](transactions/) - Maintain data consistency\n7. [Security](security/) - Encryption, tokens, and secure IDs\n\n## Migration from Granite\n\nIf you're migrating from Granite (Amber 1.x's default ORM), Grant provides a similar API with enhanced features. See the [Migration Guide](../../../migration-guide/granite-to-grant/) for details."}