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

/home/runner/work/rotor/rotor/src/repo.qz:373

Class methods

def self.new(*, pool: PG::Pool) -> Rotor::Repo

Over a pg pool: each statement takes a connection.

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

def self.new(*, connection: PG::Connection) -> Rotor::Repo

Over one pg connection.

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

def self.new(adapter: Rotor::Adapter) -> Rotor::Repo

Over any adapter.

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

def self.unwrap[M <: Rotor::Model](outcome: Rotor::Outcome[M]) -> M

The value of Rotor::Outcome::Ok; Rotor::Outcome::Invalid raised as Rotor::InvalidChangeset.

/home/runner/work/rotor/rotor/src/repo.qz:233

def self.now -> Time

Now, in UTC, to the microsecond: what a timestamp column keeps.

/home/runner/work/rotor/rotor/src/repo.qz:368

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.

/home/runner/work/rotor/rotor/src/repo.qz:376

Methods

def adapter -> Rotor::Adapter

The adapter.

/home/runner/work/rotor/rotor/src/repo.qz:47

def run(statement: Rotor::Statement) -> Int

Runs a statement: how many rows it changed.

/home/runner/work/rotor/rotor/src/repo.qz:50

def run[T](query: Rotor::Query[T]) -> Array[T]

Runs a query: its rows.

/home/runner/work/rotor/rotor/src/repo.qz:56

def all[T](query: Rotor::Query[T]) -> Array[T]

The query's rows: run(query).

/home/runner/work/rotor/rotor/src/repo.qz:62

def one[T](query: Rotor::Query[T]) -> T?

The query's one row, or nil; raises Rotor::MultipleResultsError when there are more.

/home/runner/work/rotor/rotor/src/repo.qz:66

def one![T](query: Rotor::Query[T]) -> T

The query's one row; raises Rotor::NoResultsError when there is none, Rotor::MultipleResultsError when there are more.

/home/runner/work/rotor/rotor/src/repo.qz:75

def first[T](query: Rotor::Query[T]) -> T?

The query's first row, or nil.

/home/runner/work/rotor/rotor/src/repo.qz:81

def get[M <: Rotor::Model](model: Type[M], id: Object) -> M?

The model whose primary key is id, or nil.

/home/runner/work/rotor/rotor/src/repo.qz:84

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.

/home/runner/work/rotor/rotor/src/repo.qz:98

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.

/home/runner/work/rotor/rotor/src/repo.qz:101

def count[T](query: Rotor::Query[T]) -> Int

The number of the query's rows.

/home/runner/work/rotor/rotor/src/repo.qz:104

def exists?[T](query: Rotor::Query[T]) -> Bool

Whether the query has a row.

/home/runner/work/rotor/rotor/src/repo.qz:107

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.

/home/runner/work/rotor/rotor/src/repo.qz:111

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.

/home/runner/work/rotor/rotor/src/repo.qz:135

def insert![M <: Rotor::Model](changeset: Rotor::Changeset[M]) -> M

As insert, with Rotor::Outcome::Invalid raised as Rotor::InvalidChangeset.

/home/runner/work/rotor/rotor/src/repo.qz:138

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.

/home/runner/work/rotor/rotor/src/repo.qz:143

def update![M <: Rotor::Model](changeset: Rotor::Changeset[M]) -> M

As update, with Rotor::Outcome::Invalid raised as Rotor::InvalidChangeset.

/home/runner/work/rotor/rotor/src/repo.qz:146

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).

/home/runner/work/rotor/rotor/src/repo.qz:151

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.

/home/runner/work/rotor/rotor/src/repo.qz:243

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.

/home/runner/work/rotor/rotor/src/repo.qz:257

def delete[M <: Rotor::Model](model: M) -> M

Deletes model's row, and gives the model; raises Rotor::StaleError when there was none.

/home/runner/work/rotor/rotor/src/repo.qz:286

def delete_all[T](query: Rotor::Query[T]) -> Int

Deletes the query's rows: how many.

/home/runner/work/rotor/rotor/src/repo.qz:292

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.

/home/runner/work/rotor/rotor/src/repo.qz:296

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.

/home/runner/work/rotor/rotor/src/repo.qz:299

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).

/home/runner/work/rotor/rotor/src/repo.qz:305

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).

/home/runner/work/rotor/rotor/src/repo.qz:316

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).

/home/runner/work/rotor/rotor/src/repo.qz:320

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.

/home/runner/work/rotor/rotor/src/repo.qz:338