class Rotor::Repo
Runs queries and statements (Rotor::Query, Rotor::Statement)
against a database, through an adapter, and loads what they give.
class RUser < Rotor::Model
table "users" do
column :id, Int, primary: true
column :name, String
column :age, Int, default: 0
timestamps
end
end
PG::Pool.open("postgres://app@localhost/shop") do |pool|
repo = Rotor::Repo.new(pool: pool)
u = repo.insert(RUser.new(name: "Ann")) # its id and timestamps set
repo.all(RUser.where { age >= 18 }) # Array[RUser]
repo.transaction { |tx| tx.update(u.with(age: 41)) }
end
Models are immutable: insert, update and touch give the model as
saved, the one given unchanged. Timestamps (timestamps in a table
block) are set here, never by SQL: insert sets both to now (in UTC, to the microsecond, as the
database keeps them), keeping a creation time the model has; update
sets the update time when a column changed, and issues no UPDATE when
none did; touch sets it alone. update, touch and delete of a
model whose row no longer exists raise Rotor::StaleError.
/home/runner/work/rotor/rotor/src/repo.qz:29
Constants
MICROSECOND: Duration
Class methods
def self.new(*, pool: PG::Pool) -> Rotor::Repo
Over a pg pool: each statement takes a connection.
def self.new(*, connection: PG::Connection) -> Rotor::Repo
Over one pg connection.
def self.new(adapter: Rotor::Adapter) -> Rotor::Repo
Over any adapter.
def self.unwrap[M <: Rotor::Model](outcome: Rotor::Outcome[M]) -> M
The value of Rotor::Outcome::Ok; Rotor::Outcome::Invalid raised as Rotor::InvalidChangeset.
def self.now -> Time
Now, in UTC, to the microsecond: what a timestamp column keeps.
def self.values(row: Rotor::Row, columns: Array[Rotor::Column]) -> Hash[Symbol, Object?]
A row's values by column, read with each column's type.
Methods
def adapter -> Rotor::Adapter
The adapter.
def run(statement: Rotor::Statement) -> Int
Runs a statement: how many rows it changed.
def run[T](query: Rotor::Query[T]) -> Array[T]
Runs a query: its rows.
def all[T](query: Rotor::Query[T]) -> Array[T]
The query's rows: run(query).
def one[T](query: Rotor::Query[T]) -> T?
The query's one row, or nil; raises Rotor::MultipleResultsError
when there are more.
def one -> T
The query's one row; raises Rotor::NoResultsError when there is
none, Rotor::MultipleResultsError when there are more.
def first[T](query: Rotor::Query[T]) -> T?
The query's first row, or nil.
def get[M <: Rotor::Model](model: Type[M], id: Object) -> M?
The model whose primary key is id, or nil.
def preload[M <: Rotor::Model](models: Array[M], &spec: Quoted[shareable M.() -> Object]) -> Array[M]
The models with the associations the block names preloaded: one
query per association and level, WHERE key = ANY($1), for all the
models at once. The block names them as the getters table
generates, so that a misspelling does not compile: { posts },
{ [posts, account] }, and those of their models with nest
({ nest(posts) { comments } }). An association a model has
preloaded already is not loaded again. Models are values: the ones
given are left as they were.
def preload[M <: Rotor::Model](model: M, &spec: Quoted[shareable M.() -> Object]) -> M
The models with the associations the block names preloaded: one
query per association and level, WHERE key = ANY($1), for all the
models at once. The block names them as the getters table
generates, so that a misspelling does not compile: { posts },
{ [posts, account] }, and those of their models with nest
({ nest(posts) { comments } }). An association a model has
preloaded already is not loaded again. Models are values: the ones
given are left as they were.
def count[T](query: Rotor::Query[T]) -> Int
The number of the query's rows.
def exists?[T](query: Rotor::Query[T]) -> Bool
Whether the query has a row.
def insert[M <: Rotor::Model](model: M) -> M
Inserts model's row, its timestamps set first, and gives the model
the row reads back (its key, the database's defaults), unchanged.
def insert[M <: Rotor::Model](changeset: Rotor::Changeset[M]) -> Rotor::Outcome[M]
Inserts the changeset's model (Rotor::Changeset#apply, with its
timestamps as insert sets them) and its put_assoc sets: Rotor::Outcome::Ok
with the model as saved, or Rotor::Outcome::Invalid with the changeset when it has
errors or the database refused the row by a constraint it declares
(unique_constraint...), which becomes an error on its field. Any
other error raises. Inside a transaction the save runs under a
savepoint, so that an Rotor::Outcome::Invalid leaves the transaction usable.
def insert -> M
As insert, with Rotor::Outcome::Invalid raised as Rotor::InvalidChangeset.
def update[M <: Rotor::Model](changeset: Rotor::Changeset[M]) -> Rotor::Outcome[M]
Updates the row of the changeset's model with its changes (no
statement when there are none) and replaces its put_assoc sets, as
insert does.
def update -> M
As update, with Rotor::Outcome::Invalid raised as Rotor::InvalidChangeset.
def delete[M <: Rotor::Model](changeset: Rotor::Changeset[M]) -> Rotor::Outcome[M]
Deletes the row of the changeset's model, as delete(model): Rotor::Outcome::Ok
with it, or Rotor::Outcome::Invalid for errors or a declared constraint
(no_assoc_constraint: rows of another table still refer to it).
def update[M <: Rotor::Model](model: M) -> M
Updates model's row with the columns assigned since it was loaded
(and its update time), and gives the model as saved, unchanged; does
nothing when none were.
def touch[M <: Rotor::Model](model: M, *columns: Symbol) -> M
Sets model's update time, and the Time columns named, to now, and
writes those alone: the model with them, its other assignments still
pending.
def delete[M <: Rotor::Model](model: M) -> M
Deletes model's row, and gives the model; raises
Rotor::StaleError when there was none.
def delete_all[T](query: Rotor::Query[T]) -> Int
Deletes the query's rows: how many.
def update_all[T](query: Rotor::Query[T], &set: Quoted[shareable T.() -> Hash[Symbol, Object]]) -> Int
Updates the query's rows as the block says (Rotor::Query#update;
no timestamps are set): how many.
def update_all[T](query: Rotor::Query[T], &set: Quoted[shareable (T) -> Hash[Symbol, Object]]) -> Int
Updates the query's rows as the block says (Rotor::Query#update;
no timestamps are set): how many.
def transaction[R](&: (Rotor::Repo) -> R) -> R
Runs the block in a transaction, with a repository bound to it:
committed when the block returns, rolled back when it raises.
Transactions do not nest (pg raises PG::TransactionError for one
inside another).
def exec(sql: String, params: Array[Object?] = …) -> Int
Runs raw SQL: the count the server reported (the rows changed, or a SELECT's rows; 0 for none).
def query[M <: Rotor::Model](model: Type[M], sql: String, params: Array[Object?] = …) -> Array[M]
Runs raw SQL and loads its rows as models, by the result's column
names (columns it does not give are left unloaded).
def query[R](sql: String, params: Array[Object?] = …, &: (Rotor::Row) -> R) -> Array[R]
Runs raw SQL and gives the block's value for each row, read with
Rotor::Row's typed readers.