|
| 1 | +# Crecto API Reference |
| 2 | + |
| 3 | +This reference captures the public APIs that exist in the repository today. All snippets are derived from the current code under `src/crecto`. |
| 4 | + |
| 5 | +## `Crecto::Repo` |
| 6 | + |
| 7 | +Repositories are singletons that wrap a database connection. |
| 8 | + |
| 9 | +### Configuration |
| 10 | + |
| 11 | +```crystal |
| 12 | +class Repo < Crecto::Repo |
| 13 | + config do |conf| |
| 14 | + conf.adapter = Crecto::Adapters::Postgres |
| 15 | + conf.database = "app_dev" |
| 16 | + conf.username = "postgres" |
| 17 | + conf.password = "postgres" |
| 18 | + conf.hostname = "localhost" |
| 19 | + end |
| 20 | +end |
| 21 | +``` |
| 22 | + |
| 23 | +`config` returns the same `Config` instance every time; use it to tweak settings at boot. |
| 24 | + |
| 25 | +### Reading |
| 26 | + |
| 27 | +- `Repo.all(queryable, query = Crecto::Repo::Query.new, **opts)` – returns an `Array(queryable)`. Optional keyword `preload:` merges extra preloads. |
| 28 | +- `Repo.get(queryable, id, tx = nil)` / `Repo.get!(...)`. |
| 29 | +- `Repo.get_by(queryable, **opts)` / `Repo.get_by!(...)`. |
| 30 | +- `Repo.get_association(model, :name, query = Query.new)` – loads associations on demand. |
| 31 | +- `Repo.aggregate(queryable, function, field, query = Query.new)` – executes `AVG/COUNT/MAX/MIN/SUM`. |
| 32 | +- `Repo.query(queryable, sql, params = [] of DbValue)` – executes raw SQL, casting rows into models. |
| 33 | +- `Repo.query(sql, params = [] of DbValue)` – returns `DB::ResultSet`. Callers must close the result set. |
| 34 | +- `Repo.raw_exec(*args)`, `Repo.raw_query(sql, *args)`, `Repo.raw_scalar(*args)` – thin wrappers around the underlying `DB::Database`. |
| 35 | + |
| 36 | +### Writing |
| 37 | + |
| 38 | +All mutating methods expect a valid changeset and return a `Crecto::Changeset::Changeset`. |
| 39 | + |
| 40 | +- `Repo.insert(changeset)` / `Repo.insert!(changeset)` |
| 41 | +- `Repo.update(changeset)` / `Repo.update!(changeset)` |
| 42 | +- `Repo.delete(changeset)` / `Repo.delete!(changeset)` |
| 43 | +- `Repo.insert_all(queryable, Array(Model | Hash | NamedTuple))` – returns `Crecto::BulkResult`. Hashes and named tuples are cast into models before validation. |
| 44 | +- `Repo.insert_all!(...)` – raises if any record fails. |
| 45 | +- `Repo.update_all(queryable, query, Hash | NamedTuple, tx = nil)` – returns `Nil`. |
| 46 | +- `Repo.delete_all(queryable, query = Query.new, tx = nil)` – returns `Nil`. |
| 47 | + |
| 48 | +### Transactions |
| 49 | + |
| 50 | +- `Repo.transaction(multi : Crecto::Multi)` – runs the operations described by the multi and returns the same multi. If any operation fails the transaction is rolled back and `multi.errors` contains the failure details. |
| 51 | +- `Repo.transaction! { |tx| ... }` – yields a `Crecto::LiveTransaction`. Any raised exception rolls back. |
| 52 | +- `Repo.transaction_with_savepoint!(name = nil) { |tx| ... }` – creates a savepoint when already inside a transaction, otherwise behaves like `transaction!`. |
| 53 | + |
| 54 | +`Crecto::LiveTransaction` exposes the same methods as the repository (`insert`, `update`, `delete`, `insert_all`, `update_all`, `delete_all`, `get`, `get_by`). Each forwards to the repo while reusing the `DB::Transaction`. |
| 55 | + |
| 56 | +## `Crecto::Repo::Query` |
| 57 | + |
| 58 | +The query builder composes SQL fragments. |
| 59 | + |
| 60 | +Common helpers: |
| 61 | + |
| 62 | +- `Query.where(...)`, `Query.or_where(...)` – accept keyword arguments, `(String, Array(DbValue))`, `(Symbol, DbValue)`, or plain SQL. |
| 63 | +- `Query.join(:association)` / `Query.join("JOIN ...")` |
| 64 | +- `Query.preload(:association, query = Query.new)` |
| 65 | +- `Query.order_by("field DESC")` |
| 66 | +- `Query.limit(Int32 | Int64)` and `Query.offset(Int32 | Int64)` |
| 67 | +- `Query.group_by("expression")` |
| 68 | +- `Query.distinct("expression")` |
| 69 | +- `Query.combine(other_query)` – merges select lists and where expressions. |
| 70 | +- `Query.and { |expr| ... }` and `Query.or { |expr| ... }` – scope combinators. |
| 71 | + |
| 72 | +`Query#stream` and `Query#each_cursor` exist as placeholders and currently raise `NotImplementedError`. |
| 73 | + |
| 74 | +## `Crecto::Model` |
| 75 | + |
| 76 | +Subclassing `Crecto::Model` adds: |
| 77 | + |
| 78 | +- `schema` macro for columns. |
| 79 | +- Association macros: `has_many`, `has_one`, `belongs_to`. `has_many` accepts `through:` to model join tables. |
| 80 | +- Field introspection: `self.fields`, `self.primary_key_field`, `self.table_name`. |
| 81 | +- Initializers: `new`, `new(**attrs)` to apply attributes via the casting pipeline, and `new(named_tuple)` for runtime data. |
| 82 | +- Changeset helpers: `self.changeset(instance, validation_context = nil)`, `instance.get_changeset`. |
| 83 | +- Casting helpers: `self.cast(hash)`, `instance.cast(hash)`, `cast!(...)`. |
| 84 | +- Timestamp helpers: `instance.created_at_to_now`, `instance.updated_at_to_now`. |
| 85 | + |
| 86 | +Associations store metadata in `CRECTO_ASSOCIATIONS` and raise `Crecto::AssociationNotLoaded` until preloaded. |
| 87 | + |
| 88 | +## `Crecto::Changeset` |
| 89 | + |
| 90 | +`Crecto::Changeset::Changeset` instances expose: |
| 91 | + |
| 92 | +- `#valid?` – boolean result. |
| 93 | +- `#errors` – `Array(Tuple(String, String))`. |
| 94 | +- `#changes` / `#source` – change tracking. |
| 95 | +- `#instance` – the wrapped model. |
| 96 | +- `#action` – set by the repository (`:insert`, `:update`, `:delete`). |
| 97 | +- `#validate_required`, `#validate_format`, etc. – imperative validators run inside custom logic. |
| 98 | +- `#unique_constraint(field)` – converts database uniqueness violations into changeset errors. |
| 99 | + |
| 100 | +Validations declared on the model class (e.g. `validate_required`) accumulate in class-level caches and run automatically when a changeset is instantiated. |
| 101 | + |
| 102 | +## `Crecto::Multi` |
| 103 | + |
| 104 | +`Crecto::Multi` collects operations to run inside a transaction. |
| 105 | + |
| 106 | +- `multi.insert(model_or_changeset)` |
| 107 | +- `multi.update(model_or_changeset)` |
| 108 | +- `multi.delete(model_or_changeset)` |
| 109 | +- `multi.delete_all(queryable, query = Crecto::Repo::Query.new)` |
| 110 | +- `multi.update_all(queryable, query, update_hash)` |
| 111 | +- `multi.operations` – the ordered list of pending operations. |
| 112 | +- `multi.errors` – populated when `Repo.transaction(multi)` encounters a failure. |
| 113 | +- `multi.changesets_valid?` – returns `false` and sets `errors` if any queued changeset is invalid. |
| 114 | + |
| 115 | +## `Crecto::BulkResult` |
| 116 | + |
| 117 | +Returned by `Repo.insert_all`. |
| 118 | + |
| 119 | +- `#total_count`, `#successful_count`, `#failed_count` |
| 120 | +- `#success_rate` |
| 121 | +- `#inserted_ids` |
| 122 | +- `#errors` – array of `BulkInsertError` |
| 123 | +- `#successful?`, `#partial_success?`, `#complete_failure?` |
| 124 | +- `#finalize_result(duration_ms)` – called internally to compute `failed_count`. |
| 125 | + |
| 126 | +`BulkInsertError` provides `index`, `error_message`, `error_class`, `validation_errors`, `database_error_code`, and helpers like `#constraint_violation?`. |
| 127 | + |
| 128 | +## Errors |
| 129 | + |
| 130 | +Defined under `src/crecto/errors/`: |
| 131 | + |
| 132 | +- `Crecto::Errors::InvalidChangeset` |
| 133 | +- `Crecto::Errors::InvalidAdapter` |
| 134 | +- `Crecto::Errors::InvalidOption` |
| 135 | +- `Crecto::Errors::InvalidType` |
| 136 | +- `Crecto::Errors::AssociationError` |
| 137 | +- `Crecto::Errors::AssociationNotLoaded` |
| 138 | +- `Crecto::Errors::BulkError` |
| 139 | +- `Crecto::Errors::NoResults` |
| 140 | +- `Crecto::Errors::ConcurrentModificationError` |
| 141 | +- `Crecto::Errors::RecordNotFoundError` |
| 142 | +- `Crecto::Errors::IteratorError` |
| 143 | + |
| 144 | +Handle them with Crystal's standard exception mechanisms (`rescue`). |
| 145 | + |
| 146 | +## Logging |
| 147 | + |
| 148 | +`Crecto::DbLogger` captures SQL statements, timings, and bulk operation diagnostics. Adapters call `DbLogger.log` and `DbLogger.log_error` internally. You can configure the logger by assigning `Crecto::DbLogger.logger = Log.for("crecto")`. |
| 149 | + |
| 150 | +--- |
| 151 | + |
| 152 | +This reference reflects the behaviour of the code as of the current repository revision. If you add new features, update this document alongside the implementation to keep it trustworthy. |
0 commit comments