class Rotor::Schema

What a model's table block runs on, at compile time: each of its calls declares a column or an association. Its column takes the column's type as a class: Int, Float, String, Bool, Time or Bytes, one overload each, so that a default is of the column's type.

/home/runner/work/rotor/rotor/src/schema.qz:12

Constants

RESERVED: Array[Symbol]

The methods of Rotor::Model that a column's getter would replace.

/home/runner/work/rotor/rotor/src/schema.qz:284

Class methods

def self.new(model: String) -> Rotor::Schema

A schema for the model named model (its class name).

/home/runner/work/rotor/rotor/src/schema.qz:20

def self.create(repo: Rotor::Repo, models: Array[Type[Rotor::Model]], *, if_not_exists: Bool = …) -> Void

Creates the tables of models from their table blocks, in one transaction: each column (an Int primary key as GENERATED BY DEFAULT AS IDENTITY, the columns' defaults as the tables'), and for each belongs_to an index on its column and, once every table is made, its foreign key (ALTER TABLE, so that models may refer to each other in a cycle). With if_not_exists: true, a table that exists is left as it is. For a development database or a test's: a production database's schema goes through migrations (Rotor::Migration).

class SchemaUser < Rotor::Model
  table "users" do
    column :id, Int, primary: true
    column :name, String
  end
end

class SchemaPost < Rotor::Model
  table "posts" do
    column :id, Int, primary: true
    belongs_to SchemaUser, name: :user
  end
end

PG::Pool.open("postgres://app@localhost/shop_test") do |pool|
  repo = Rotor::Repo.new(pool: pool)
  Rotor::Schema.create(repo, [SchemaUser, SchemaPost])
  Rotor::Schema.drop(repo, [SchemaUser, SchemaPost])
end

/home/runner/work/rotor/rotor/src/schema.qz:216

def self.drop(repo: Rotor::Repo, models: Array[Type[Rotor::Model]], *, if_exists: Bool = …) -> Void

Drops the tables of models, in one DROP TABLE (so that their foreign keys to each other do not stop it; without CASCADE, so that one from another table does). With if_exists: true, a missing table is no error.

/home/runner/work/rotor/rotor/src/schema.qz:245

Methods

def columns -> Array[Rotor::Column]

The columns declared so far, in order.

/home/runner/work/rotor/rotor/src/schema.qz:24

def associations -> Array[Rotor::Association]

The associations declared so far, in order.

/home/runner/work/rotor/rotor/src/schema.qz:27

def timestamp_columns -> [Symbol?, Symbol?]

The timestamps columns: created at, updated at (nil for none).

/home/runner/work/rotor/rotor/src/schema.qz:30

def column(name: Symbol, type: Type[Int], *, null: Bool = …, default: Int? = …, primary: Bool = …, redact: Bool = …) -> Void

An Int column (bigint).

/home/runner/work/rotor/rotor/src/schema.qz:33

def column(name: Symbol, type: Type[Float], *, null: Bool = …, default: Float? = …, primary: Bool = …, redact: Bool = …) -> Void

A Float column (double precision).

/home/runner/work/rotor/rotor/src/schema.qz:38

def column(name: Symbol, type: Type[String], *, null: Bool = …, default: String? = …, primary: Bool = …, redact: Bool = …) -> Void

A String column (text).

/home/runner/work/rotor/rotor/src/schema.qz:43

def column(name: Symbol, type: Type[Bool], *, null: Bool = …, default: Bool? = …, primary: Bool = …, redact: Bool = …) -> Void

A Bool column (boolean).

/home/runner/work/rotor/rotor/src/schema.qz:48

def column(name: Symbol, type: Type[Time], *, null: Bool = …, primary: Bool = …, redact: Bool = …) -> Void

A Time column (timestamptz). It takes no default: a time is not a value the compiler can bake into the program.

/home/runner/work/rotor/rotor/src/schema.qz:54

def column(name: Symbol, type: Type[Bytes], *, null: Bool = …, default: Bytes? = …, primary: Bool = …, redact: Bool = …) -> Void

A Bytes column (bytea).

/home/runner/work/rotor/rotor/src/schema.qz:59

def timestamps(*, created_at: Symbol | Bool = …, updated_at: Symbol | Bool = …) -> Void

created_at and updated_at, Time columns (timestamptz) that are not NULL, which a repository sets when it inserts and updates the model. Either is renamed by giving its name, or left out with false.

/home/runner/work/rotor/rotor/src/schema.qz:67

def belongs_to[T <: Rotor::Model](target: Type[T], *, name: Symbol? = …, foreign_key: Symbol? = …, null: Bool = …) -> Void

The model belongs to target: the column <name>_id (an Int, or foreign_key:) holds the target's key. The association's name is the target's in snake_case (belongs_to Account: account, and the column account_id), or name:.

/home/runner/work/rotor/rotor/src/schema.qz:82

def has_many[T <: Rotor::Model](target: Type[T], *, name: Symbol? = …, foreign_key: Symbol? = …, through: Type[Rotor::Model]? = …, source: Symbol? = …) -> Void

The model has many of target, whose foreign_key: column (by default this model's name in snake_case with _id) holds this one's key. The association's name is the target's in snake_case, plural (has_many Post in a model User: posts, through user_id), or name:.

class DocAuthor < Rotor::Model
  table do
    column :id, Int, primary: true
    has_many DocBook
  end
end

class DocBook < Rotor::Model
  table do
    column :id, Int, primary: true
    belongs_to DocAuthor
  end
end

DocAuthor.associations[0].name         # => :doc_books
DocAuthor.associations[0].foreign_key  # => :doc_author_id
DocBook.columns.map { |c| c.name }     # => [:id, :doc_author_id]

With through: a join model, the model has many of target through it: the join model's belongs_to this model and belongs_to the target (source: names the latter when it has several) pair them; the keys are found when the association is first used, all table blocks having run (Rotor::Through.keys). foreign_key: is the join model's then, not given here.

class DocArticle < Rotor::Model
  table do
    column :id, Int, primary: true
    has_many DocLabel, through: DocArticleLabel
  end
end

class DocArticleLabel < Rotor::Model
  table do
    column :id, Int, primary: true
    belongs_to DocArticle
    belongs_to DocLabel
  end
end

class DocLabel < Rotor::Model
  table do
    column :id, Int, primary: true
  end
end

DocArticle.load({id: 3}).doc_labels.query.to_sql  # => ["SELECT \"id\" FROM \"doc_labels\" WHERE \"id\" IN (SELECT \"doc_label_id\" FROM \"doc_article_labels\" WHERE \"doc_article_id\" = $1)", [3]]

/home/runner/work/rotor/rotor/src/schema.qz:150