class

Lustra::Model::CollectionBase(T)

Inherits Lustra::SQL::SelectBuilder < Lustra::SQL::Query::WithPagination < Lustra::SQL::Query::BeforeQuery < Lustra::SQL::Query::Change < Lustra::SQL::Query::Connection < Lustra::SQL::Query::Pluck < Lustra::SQL::Query::Fetch < Lustra::SQL::Query::Execute < Lustra::SQL::Query::Lock < Lustra::SQL::Query::Window < Lustra::SQL::Query::CTE < Lustra::SQL::Query::Aggregate < Lustra::SQL::Query::OffsetLimit < Lustra::SQL::Query::GroupBy < Lustra::SQL::Query::OrderBy < Lustra::SQL::Query::Having < Lustra::SQL::Query::Where < Lustra::SQL::Query::Join < Lustra::SQL::Query::From < Lustra::SQL::Query::Select < Enumerable < Reference < Object

CollectionBase(T) is the base class for collection of model. Collection of model are a SQL SELECT query mapping & building system. They are Enumerable and are `Lustra::SQL::SelectBuilder` behavior; therefore, they can be used array-like and are working with low-level SQL Building.

The CollectionBase(T) is extended by each model. For example, generating the model MyModel will generate the class MyModel::Collection which inherits from CollectionBase(MyModel)

Collection are instantiated using Model.query method.

Instance methods

<<(item : T)

Add an item to the current collection.

If the current collection is not originated from a has_many or has_many through: relation, calling << over the collection will raise a Lustra::SQL::OperationNotPermittedError

Returns self and therefore can be chained

Source
[](range : Range(Number, Number), fetch_columns = false) : Array(T)

Get a range of models

Source
[](off, fetch_columns = false) : T

Basically a fancy way to write OFFSET x LIMIT 1

Source
[]?(off, fetch_columns = false) : T | Nil

Basically a fancy way to write OFFSET x LIMIT 1

Source
add(item : T)

Alias for Collection#<<

Source
any?

Check whether the query return any row.

Source
association_name
Source
association_name=(association_name : String | Nil)
Source
autosave=(autosave : Bool)
Source
autosave?
Source
build(x : NamedTuple, &block : T -> Nil) : T

Build a new collection; if the collection comes from a has_many relation (e.g. my_model.associations.build), the foreign column which store the primary key of my_model will be setup by default, preventing you to forget it. You can pass extra parameters using a named tuple: my_model.associations.build({a_column: "value"})

Source
build

Build a new collection; if the collection comes from a has_many relation (e.g. my_model.associations.build), the foreign column which store the primary key of my_model will be setup by default, preventing you to forget it. You can pass extra parameters using a named tuple: my_model.associations.build({a_column: "value"})

Source
build(x : NamedTuple) : T

Build a new collection; if the collection comes from a has_many relation (e.g. my_model.associations.build), the foreign column which store the primary key of my_model will be setup by default, preventing you to forget it. You can pass extra parameters using a named tuple: my_model.associations.build({a_column: "value"})

Source
build

Build a new collection; if the collection comes from a has_many relation (e.g. my_model.associations.build), the foreign column which store the primary key of my_model will be setup by default, preventing you to forget it. You can pass extra parameters using a named tuple: my_model.associations.build({a_column: "value"})

Source
count(type : X.class = Int64) forall X

Use SQL COUNT over your query, and return this number as a Int64

Source
create(x : NamedTuple, &block : T -> Nil) : T

Build a new object and setup the fields like setup in the condition tuple. Just after building, save the object.

Source
create

Build a new object and setup the fields like setup in the condition tuple. Just after building, save the object.

Source
create(x : NamedTuple) : T

Build a new object and setup the fields like setup in the condition tuple. Just after building, save the object.

Source
create

Build a new object and setup the fields like setup in the condition tuple. Just after building, save the object.

Source
create!(x : NamedTuple, &block : T -> Nil) : T

Build a new object and setup the fields like setup in the condition tuple. Just after building, save the object. But instead of returning self if validation failed, raise Lustra::Model::InvalidError exception

Source
create!

Build a new object and setup the fields like setup in the condition tuple. Just after building, save the object. But instead of returning self if validation failed, raise Lustra::Model::InvalidError exception

Source
create!(x : NamedTuple) : T

Build a new object and setup the fields like setup in the condition tuple. Just after building, save the object. But instead of returning self if validation failed, raise Lustra::Model::InvalidError exception

Source
create!

Build a new object and setup the fields like setup in the condition tuple. Just after building, save the object. But instead of returning self if validation failed, raise Lustra::Model::InvalidError exception

Source
delete_all

Delete all the rows which would have been returned by this collection WITHOUT callbacks. This is a bulk operation that doesn't load models into memory. Is equivalent to collection.to_delete.execute

User.query.where { active == false }.delete_all

Returns self for chaining.

Source
destroy_all

Destroy all the rows which would have been returned by this collection WITH callbacks. This loads each model into memory and calls destroy on it, triggering all :delete callbacks. Use delete_all if you don't need callbacks (much faster for large datasets).

# With callbacks - slower but safe
User.query.where { active == false }.destroy_all

# Without callbacks - faster
User.query.where { active == false }.delete_all

Returns self for chaining.

Source
dup

Duplicate the query

Source
each(fetch_columns = false, & : T -> ) : Nil

Build the SQL, send the query then iterate through each models gathered by the request.

Source
each_with_cursor(batch = 1000, fetch_columns = false, &block : T -> )

Build the SQL, send the query then iterate through each models gathered by the request. Use a postgres cursor to avoid memory bloating. Useful to fetch millions of rows at once.

Source
empty?

Inverse of any?, return true if the request return no rows.

Source
find(fetch_columns = false, &) : T | Nil

A convenient way to write where { condition }.first(fetch_columns)

Source
find(tuple : NamedTuple, fetch_columns = false) : T | Nil

A convenient way to write where({any_column: "any_value"}).first(fetch_columns)

Source
find(ids : Array)

Find multiple models by an array of primary keys. Returns an array of models (may be empty if none found).

Source
find(x)

Returns a model using primary key equality Returns nil if not found.

Source
find

A convenient way to write where({any_column: "any_value"}).first

Source
find!(fetch_columns = false, &) : T

A convenient way to write where { condition }.first!(fetch_columns)

Source
find!(tuple : NamedTuple, fetch_columns = false) : T

A convenient way to write where({any_column: "any_value"}).first!(fetch_columns)

Source
find!(ids : Array)

Find multiple models by an array of primary keys. Raises error if ANY of the IDs are not found.

Source
find!(x)

Returns a model using primary key equality. Raises error if the model is not found.

Source
find!

A convenient way to write where({any_column: "any_value"}).first!

Source
find_by(fetch_columns = false, &) : T | Nil

A convenient way to write where { condition }.first(fetch_columns)

Source
find_by(tuple : NamedTuple, fetch_columns = false) : T | Nil

Find a model by column values. Returns nil if not found.

user = User.query.find_by(email: "test@example.com")
user = User.query.where { active == true }.find_by(role: "admin")
Source
find_by

Find a model by column values. Returns nil if not found.

user = User.query.find_by(email: "test@example.com")
user = User.query.where { active == true }.find_by(role: "admin")
Source
find_by!(fetch_columns = false, &) : T

A convenient way to write where { condition }.first!(fetch_columns)

Source
find_by!(tuple : NamedTuple, fetch_columns = false) : T

Find a model by column values. Raises error if not found.

user = User.query.find_by!(email: "test@example.com")
Source
find_by!

Find a model by column values. Raises error if not found.

user = User.query.find_by!(email: "test@example.com")
Source
find_or_build(x : NamedTuple, &block : T -> Nil) : T
Source
find_or_build

Try to fetch a row. If not found, build a new object and setup the fields like setup in the condition tuple.

Source
find_or_build(x : NamedTuple) : T
Source
find_or_build
Source
find_or_create(x : NamedTuple, &block : T -> Nil) : T

Try to fetch a row. If not found, build a new object and setup the fields like setup in the condition tuple. Just after building, save the object.

Source
find_or_create

Try to fetch a row. If not found, build a new object and setup the fields like setup in the condition tuple. Just after building, save the object.

Source
find_or_create(x : NamedTuple) : T

Try to fetch a row. If not found, build a new object and setup the fields like setup in the condition tuple. Just after building, save the object.

Source
find_or_create

Try to fetch a row. If not found, build a new object and setup the fields like setup in the condition tuple. Just after building, save the object.

Source
first(fetch_columns = false) : T | Nil

Get the first row from the collection query. if not found, return nil

Source
first!(fetch_columns = false) : T

Get the first row from the collection query. if not found, throw an error

Source
full_outer_join(association : Lustra::SQL::Symbolic, lateral = false)

FULL_OUTER JOIN using association name (auto-detects join conditions)

Source
ids

Convenient shortcut to get an array of primary key values. Equivalent to pluck_col(T.__pkey__) but more readable.

User.query.where { active == true }.ids
# => [1, 2, 3, 4, 5]

Post.query.where { published == true }.ids
# => [10, 25, 42, 100]

Returns an array of primary key values (typically Array(Int64) or Array(Int32)).

Source
inner_join(association : Lustra::SQL::Symbolic, lateral = false)

INNER JOIN using association name (auto-detects join conditions)

Source
item_class

Return the model class for this collection

Source
join(association : Lustra::SQL::Symbolic, type = :inner, lateral = false)

Join a relation using association name (auto-detects join conditions) Overrides the parent join to handle association names without blocks

Source
last(fetch_columns = false) : T | Nil

Get the last row from the collection query. if not found, return nil

Source
last!(fetch_columns = false) : T

Get the last row from the collection query. if not found, throw an error

Source
left_join(association : Lustra::SQL::Symbolic, lateral = false)

LEFT JOIN using association name (auto-detects join conditions)

Source
map(fetch_columns = false, &block : T -> X) : Array(X) forall X

Build the SQL, send the query then build and array by applying the block transformation over it.

Source
none

Return an empty, chainable collection (Rails-like .none). Useful for conditional branches where no records should be returned while keeping query chaining intact.

User.query.none.where { active == true }.count # => 0
Source
parent_model

Parent model context for autosave functionality

Source
parent_model=(parent_model : Lustra::Model | Nil)

Parent model context for autosave functionality

Source
right_join(association : Lustra::SQL::Symbolic, lateral = false)

RIGHT JOIN using association name (auto-detects join conditions)

Source
save!(item : T)

Save a model and handle append_operation for has_many through relationships This allows the build + save pattern to work

Source
tags
Source
to_a(fetch_columns = false) : Array(T)

Create an array from the query.

Source
update_all(fields : NamedTuple) : Int64

Update all the rows which would have been returned by this collection without loading the models into memory. Bypasses validations and callbacks.

This is useful for bulk updates where you don't need to run validations or callbacks on each individual model.

# Update all inactive users to have a specific status
affected = User.query.where { active == false }.update_all(status: "inactive")
puts "Updated #{affected} users"

# Update multiple columns at once
Post.query.where { published == false }.update_all(published: true, published_at: Time.utc)

# With complex conditions
User.query.where { created_at < 1.year.ago }.update_all(archived: true)

Returns the number of rows affected.

Source
update_all(fields : Hash(String, Lustra::SQL::Any)) : Int64

Update all the rows which would have been returned by this collection without loading the models into memory. Bypasses validations and callbacks.

This is useful for bulk updates where you don't need to run validations or callbacks on each individual model.

# Update all inactive users to have a specific status
affected = User.query.where { active == false }.update_all(status: "inactive")
puts "Updated #{affected} users"

# Update multiple columns at once
Post.query.where { published == false }.update_all(published: true, published_at: Time.utc)

# With complex conditions
User.query.where { created_at < 1.year.ago }.update_all(archived: true)

Returns the number of rows affected.

Source
update_all

Update all the rows which would have been returned by this collection without loading the models into memory. Bypasses validations and callbacks.

This is useful for bulk updates where you don't need to run validations or callbacks on each individual model.

# Update all inactive users to have a specific status
affected = User.query.where { active == false }.update_all(status: "inactive")
puts "Updated #{affected} users"

# Update multiple columns at once
Post.query.where { published == false }.update_all(published: true, published_at: Time.utc)

# With complex conditions
User.query.where { created_at < 1.year.ago }.update_all(archived: true)

Returns the number of rows affected.

Source