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.
null: true: the column admits NULL, and its getter returnsT?.default:: the value a model made withnewstarts with (not forTime).primary: true: the primary key (at most one; never NULL).redact: true: the value is hidden frominspect(the model's and a changeset's), for a password or a token.
/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.
Class methods
def self.new(model: String) -> Rotor::Schema
A schema for the model named model (its class name).
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
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.
Methods
def columns -> Array[Rotor::Column]
The columns declared so far, in order.
def associations -> Array[Rotor::Association]
The associations declared so far, in order.
def timestamp_columns -> [Symbol?, Symbol?]
The timestamps columns: created at, updated at (nil for none).
def column(name: Symbol, type: Type[Int], *, null: Bool = …, default: Int? = …, primary: Bool = …, redact: Bool = …) -> Void
An Int column (bigint).
def column(name: Symbol, type: Type[Float], *, null: Bool = …, default: Float? = …, primary: Bool = …, redact: Bool = …) -> Void
A Float column (double precision).
def column(name: Symbol, type: Type[String], *, null: Bool = …, default: String? = …, primary: Bool = …, redact: Bool = …) -> Void
A String column (text).
def column(name: Symbol, type: Type[Bool], *, null: Bool = …, default: Bool? = …, primary: Bool = …, redact: Bool = …) -> Void
A Bool column (boolean).
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.
def column(name: Symbol, type: Type[Bytes], *, null: Bool = …, default: Bytes? = …, primary: Bool = …, redact: Bool = …) -> Void
A Bytes column (bytea).
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.
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:.
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]]