Model Generator
The model generator creates CQL ORM models for your Azu application. Models represent database tables and provide an object-oriented interface for database operations.
Overview
azu generate model <name> [field:type] [options]
Basic Usage
Generate a Simple Model
# Generate a basic model
azu generate model user
# Generate with fields
azu generate model user name:string email:string age:integer
# Generate with relationships
azu generate model post title:string content:text user:references
Generate with Validations
# Generate model with common validations
azu generate model user name:string email:string --validations
# Generate with timestamps
azu generate model post title:string content:text --timestamps
# Generate with UUID primary key
azu generate model user name:string email:string --uuid
Command Options
--validations
Add common validations
false
--timestamps
Add created_at/updated_at fields
false
--uuid
Use UUID as primary key
false
--skip-tests
Don't generate test files
false
--skip-migration
Don't generate migration file
false
--force
Overwrite existing files
false
Field Types
string
String
VARCHAR(255)
Short text field
text
String
TEXT
Long text field
integer
Int32
INTEGER
32-bit integer
bigint
Int64
BIGINT
64-bit integer
float
Float64
FLOAT
Floating point number
decimal
BigDecimal
DECIMAL
Precise decimal number
boolean
Bool
BOOLEAN
True/false value
date
Date
DATE
Date only
time
Time
TIMESTAMP
Date and time
json
JSON::Any
JSON/JSONB
JSON data
uuid
UUID
UUID
UUID field
references
Int64
BIGINT
Foreign key reference
Generated Files
Model File
# src/models/user.cr
require "cql"
class User < CQL::Model
table :users
column id : Int64, primary: true, auto: true
column name : String
column email : String
column age : Int32?
column created_at : Time
column updated_at : Time
validates :name, presence: true
validates :email, presence: true, format: /\A[^@\s]+@[^@\s]+\z/
validates :age, numericality: { greater_than: 0 }, allow_nil: true
def self.find_by_email(email : String)
where(email: email).first
end
def full_name
"#{name} (#{email})"
end
end
Migration File
# db/migrations/20231201000001_create_users.cr
class CreateUsers < CQL::Migration
def up
create_table :users do |t|
t.string :name, null: false
t.string :email, null: false, unique: true
t.integer :age
t.timestamps
end
add_index :users, :email
end
def down
drop_table :users
end
end
Test File
# spec/models/user_spec.cr
require "../spec_helper"
describe User do
describe "validations" do
it "is valid with valid attributes" do
user = User.new(name: "John Doe", email: "john@example.com")
user.valid?.should be_true
end
it "is invalid without name" do
user = User.new(email: "john@example.com")
user.valid?.should be_false
user.errors[:name].should contain("can't be blank")
end
it "is invalid with invalid email" do
user = User.new(name: "John Doe", email: "invalid-email")
user.valid?.should be_false
user.errors[:email].should contain("is invalid")
end
end
describe ".find_by_email" do
it "finds user by email" do
user = User.create!(name: "John Doe", email: "john@example.com")
found = User.find_by_email("john@example.com")
found.should eq(user)
end
end
end
Examples
User Model
# Generate user model with validations
azu generate model user name:string email:string age:integer --validations --timestamps
Generated Model:
# src/models/user.cr
require "cql"
class User
include CQL::Model(Int64)
db_context AppDB, :users
column id : Int64, primary: true, auto: true
column name : String
column email : String
column age : Int32?
column created_at : Time
column updated_at : Time
validates :name, presence: true
validates :email, presence: true, format: /\A[^@\s]+@[^@\s]+\z/
validates :age, numericality: { greater_than: 0 }, allow_nil: true
def self.find_by_email(email : String)
where(email: email).first
end
def full_name
"#{name} (#{email})"
end
end
Post Model with Relationships
# Generate post model with user relationship
azu generate model post title:string content:text user:references --timestamps
Generated Model:
# src/models/post.cr
require "cql"
class Post
include CQL::Model(Int63)
db_context AppDB, :posts
column id : Int64, primary: true, auto: true
column title : String
column content : String
column user_id : Int64
column created_at : Time
column updated_at : Time
belongs_to :user, User
has_many :comments, Comment
validates :title, presence: true
validates :content, presence: true
validates :user_id, presence: true
def self.published
where(published: true)
end
def published?
published == true
end
end
Category Model with UUID
# Generate category model with UUID primary key
azu generate model category name:string description:text --uuid
Generated Model:
# src/models/category.cr
require "cql"
class Category
include CQL::Model(UUID)
db_context AppDB, :categories
column id : UUID, primary: true, auto: true
column name : String
column description : String
validates :name, presence: true, uniqueness: true
def self.find_by_name(name : String)
where(name: name).first
end
end
Relationships
Belongs To
# Generate model with belongs_to relationship
azu generate model comment content:text post:references user:references
# src/models/comment.cr
class Comment
include CQL::ActiveRecord::Model(Int32)
db_context AppDB, :comments
column id : Int64, primary: true, auto: true
column content : String
column post_id : Int64
column user_id : Int64
belongs_to :post, Post
belongs_to :user, User
validates :content, presence: true
validates :post_id, presence: true
validates :user_id, presence: true
end
Has Many
# src/models/post.cr
class Post
include CQL::ActiveRecord::Model(Int32)
db_context AppDB, :posts
# ... other code ...
has_many :comments, Comment
has_many :tags, through: :post_tags
def comment_count
comments.count
end
end
Many to Many
# Generate join table model
azu generate model post_tag post:references tag:references
# src/models/post_tag.cr
class PostTag
include CQL::ActiveRecord::Model(Int32)
db_context AppDB, :post_tags
column id : Int64, primary: true, auto: true
column post_id : Int64
column tag_id : Int64
belongs_to :post, Post
belongs_to :tag, Tag
validates :post_id, presence: true
validates :tag_id, presence: true
end
Validations
Common Validations
# Generate with validations
azu generate model user name:string email:string --validations
Generated Validations:
class User
include CQL::ActiveRecord::Model(Int32)
db_context AppDB, :users
# ... columns ...
validates :name, presence: true
validates :email, presence: true, format: /\A[^@\s]+@[^@\s]+\z/
validates :age, numericality: { greater_than: 0 }, allow_nil: true
end
Custom Validations
# src/models/user.cr
class User
include CQL::ActiveRecord::Model(Int32)
db_context AppDB, :users
# ... columns and basic validations ...
validate :email_domain
private def email_domain
return unless email.present?
domain = email.split("@").last
unless ["example.com", "company.com"].includes?(domain)
errors.add(:email, "must be from allowed domain")
end
end
end
Scopes and Class Methods
Generated Scopes
# src/models/user.cr
class User
include CQL::ActiveRecord::Model(Int32)
db_context AppDB, users
# ... columns and validations ...
# Generated scopes
scope :active, -> { where(active: true) }
scope :admins, -> { where(role: "admin") }
scope :recent, -> { order(created_at: :desc) }
# Custom scopes
def self.find_by_email(email : String)
where(email: email).first
end
def self.search(query : String)
where("name ILIKE ? OR email ILIKE ?", "%#{query}%", "%#{query}%")
end
end
Instance Methods
# src/models/user.cr
class User
include CQL::ActiveRecord::Model(Int32)
db_context AppDB, :users
# ... columns and validations ...
def full_name
"#{first_name} #{last_name}".strip
end
def admin?
role == "admin"
end
def active?
active == true
end
def posts_count
posts.count
end
end
Advanced Usage
Polymorphic Associations
# Generate polymorphic model
azu generate model like user:references likeable:references{polymorphic}
# src/models/like.cr
class Like
include CQL::ActiveRecord::Model(Int32)
db_context AppDB, :likes
column id : Int64, primary: true, auto: true
column user_id : Int64
column likeable_id : Int64
column likeable_type : String
belongs_to :user, User
belongs_to :likeable, polymorphic: true
validates :user_id, presence: true
validates :likeable_id, presence: true
validates :likeable_type, presence: true
end
STI (Single Table Inheritance)
# Generate base model
azu generate model vehicle type:string make:string model:string
# src/models/vehicle.cr
class Vehicle < CQL::Model
include CQL::ActiveRecord::Model(Int32)
db_context AppDB, :vehicles
column id : Int64, primary: true, auto: true
column type : String
column make : String
column model : String
validates :type, presence: true
validates :make, presence: true
validates :model, presence: true
end
# src/models/car.cr
class Car < Vehicle
def self.create!(attributes)
super(attributes.merge(type: "Car"))
end
end
# src/models/motorcycle.cr
class Motorcycle < Vehicle
def self.create!(attributes)
super(attributes.merge(type: "Motorcycle"))
end
end
Custom Field Types
# Generate model with custom types
azu generate model product name:string price:decimal metadata:json
# src/models/product.cr
class Product
include CQL::ActiveRecord::Model(Int32)
db_context AppDB, :products
column id : Int64, primary: true, auto: true
column name : String
column price : BigDecimal
column metadata : JSON::Any
validates :name, presence: true
validates :price, numericality: { greater_than: 0 }
def metadata_hash
metadata.as_h
end
def set_metadata(key : String, value : String)
current = metadata.as_h
current[key] = value
self.metadata = JSON::Any.new(current)
end
end
Testing
Model Tests
# spec/models/user_spec.cr
require "../spec_helper"
describe User do
describe "validations" do
it "is valid with valid attributes" do
user = User.new(name: "John Doe", email: "john@example.com")
user.valid?.should be_true
end
it "is invalid without name" do
user = User.new(email: "john@example.com")
user.valid?.should be_false
user.errors[:name].should contain("can't be blank")
end
it "is invalid with invalid email" do
user = User.new(name: "John Doe", email: "invalid-email")
user.valid?.should be_false
user.errors[:email].should contain("is invalid")
end
end
describe "associations" do
it "has many posts" do
user = User.create!(name: "John", email: "john@example.com")
post = Post.create!(title: "Test", content: "Content", user: user)
user.posts.should contain(post)
end
end
describe "scopes" do
it "finds active users" do
active_user = User.create!(name: "Active", email: "active@example.com", active: true)
inactive_user = User.create!(name: "Inactive", email: "inactive@example.com", active: false)
User.active.should contain(active_user)
User.active.should_not contain(inactive_user)
end
end
describe "instance methods" do
it "returns full name" do
user = User.new(first_name: "John", last_name: "Doe")
user.full_name.should eq("John Doe")
end
end
end
Best Practices
1. Naming Conventions
# Use singular names for models
azu generate model user # Good
azu generate model users # Avoid
# Use descriptive field names
azu generate model post title:string content:text # Good
azu generate model post t:string c:text # Avoid
2. Field Types
# Use appropriate field types
azu generate model user email:string age:integer # Good
azu generate model user email:string age:string # Avoid
# Use references for relationships
azu generate model post user:references # Good
azu generate model post user_id:integer # Avoid
3. Validations
# Always add validations for important fields
azu generate model user name:string email:string --validations
# Add custom validations when needed
# See custom validation examples above
4. Relationships
# Use proper relationship definitions
belongs_to :user, User
has_many :posts, Post
has_many :tags, through: :post_tags
# Add dependent options when needed
has_many :posts, Post, dependent: :destroy
5. Performance
# Use includes to avoid N+1 queries
User.includes(:posts).all
# Use scopes for common queries
User.active.recent.limit(10)
# Use counter_cache for counts
belongs_to :user, User, counter_cache: :posts_count
Troubleshooting
Migration Issues
# Check migration file
cat db/migrations/*_create_users.cr
# Run migration
azu db:migrate
# If migration fails, check syntax
crystal build db/migrations/*_create_users.cr
Model Issues
# Check model file
cat src/models/user.cr
# Test model compilation
crystal build src/models/user.cr
# Check for syntax errors
crystal tool format src/models/user.cr
Validation Issues
# Debug validations
user = User.new
user.valid?
puts user.errors.full_messages
The model generator creates CQL ORM models with proper validations, relationships, and database migrations for your Azu application.
Next Steps:
Migration Generator - Create database migrations
Endpoint Generator - Create HTTP endpoints
Service Generator - Create business logic services
Last updated