module Lustra::Model::HasSaving

Overview

This module handles saving models to the database and triggers lifecycle callbacks. It's where the actual :create, :update, :destroy, and :save callbacks are triggered.

Key callback trigger points:

The callbacks are triggered via with_triggers which calls Lustra::Model::EventManager

Direct including types

Defined in:

lustra/model/modules/has_saving.cr

Instance Method Summary

Instance Method Detail

def decrement(column : Symbol | String, by = 1) #

Decrement a numeric column without saving to the database. Only updates the in-memory value. Call #save! to persist.

user.decrement(:attempts_left)
user.save! # Persist the change

Returns self for chaining.


[View source]
def decrement!(column : Symbol | String, by = 1) #

Decrement a numeric column by a specific amount (default 1) without running validations or callbacks. Updates the database directly and updates the in-memory value.

user.decrement!(:attempts_left) # Decrement by 1
user.decrement!(:balance, 10.5) # Decrement by 10.5

Returns self for chaining.


[View source]
def delete #

Delete the model from the database WITHOUT running callbacks. Use this for performance when you don't need to trigger callbacks. The deleted model is not persisted anymore and can be saved again.

user.delete
user.persisted? # => false

Returns true if successfully deleted, false otherwise.


[View source]
def destroy #

Destroy the model from the database WITH callbacks and validations. This is the safe way to delete records as it triggers all :destroy callbacks. The destroyed model is not persisted anymore and can be saved again.

user.destroy    # Triggers before(:destroy) and after(:destroy) callbacks
user.persisted? # => false

Returns true if successfully destroyed, false otherwise.


[View source]
def increment(column : Symbol | String, by = 1) #

Increment a numeric column without saving to the database. Only updates the in-memory value. Call #save! to persist.

user.increment(:login_count)
user.save! # Persist the change

Returns self for chaining.


[View source]
def increment!(column : Symbol | String, by = 1) #

Increment a numeric column by a specific amount (default 1) without running validations or callbacks. Updates the database directly and updates the in-memory value.

user.increment!(:login_count)   # Increment by 1
user.increment!(:score, 10)     # Increment by 10
user.increment!(:balance, -5.5) # Can use negative values

Returns self for chaining.


[View source]
def persisted? : Bool #

[View source]
def reload : self #

[View source]
def save(on_conflict : Lustra::SQL::InsertQuery -> | Nil = nil) #

Save the model. If the model is already persisted, will call UPDATE query. If the model is not persisted, will call INSERT

Optionally, you can pass a Proc to refine the INSERT with on conflict resolution functions.

Return false if the model cannot be saved (validation issue) Return true if the model has been correctly saved.

Example:

u = User.new
if u.save
  puts "User correctly saved !"
else
  puts "There was a problem during save: "
  # do something with `u.errors`
end

on_conflict optional parameter

Example:

u = User.new id: 123, email: "email@example.com"
u.save(-> (qry) { qry.on_conflict.do_update { |u| u.set(email: "email@example.com") } #update
# IMPORTANT NOTICE: user may not be saved, but will be still detected as persisted !

You may want to use a block for on_conflict optional parameter:

u = User.new id: 123, email: "email@example.com"
u.save do |qry|
   qry.on_conflict.do_update { |u| u.set(email: "email@example.com")
end

[View source]
def save(&block) #

[View source]
def save!(on_conflict : Lustra::SQL::InsertQuery -> | Nil = nil) #

Performs #save call, but instead of returning false if validation failed, raise Lustra::Model::InvalidError exception Automatically handles built associations


[View source]
def save!(&block : Lustra::SQL::InsertQuery -> ) #

Pass the on_conflict optional parameter via block.


[View source]
def save_with_associations(on_conflict : Lustra::SQL::InsertQuery -> | Nil = nil) #

Save the model along with all built associations


[View source]
def update(**args) #

Set the fields passed as argument and call #save on the object


[View source]
def update!(**args) #

Set the fields passed as argument and call #save! on the object


[View source]
def update_column(column : Symbol | String, value) #

Update a single column directly in the database without running validations or callbacks. This is useful for updating columns that don't need validation (like counters, flags, timestamps).

user.update_column(:login_count, 10)
user.update_column(:last_login_at, Time.utc)

Warning: This bypasses:

  • Validations
  • Callbacks
  • Timestamp updates (updated_at will NOT change)

Returns self for chaining.


[View source]
def update_columns(columns : NamedTuple) #

Update multiple columns directly in the database without running validations or callbacks.

user.update_columns(login_count: 10, last_login_at: Time.utc)
user.update_columns({status: "active", verified: true})

Warning: This bypasses:

  • Validations
  • Callbacks
  • Timestamp updates (updated_at will NOT change)

Returns self for chaining.


[View source]
def update_columns(columns : Hash(String, Lustra::SQL::Any)) #

Update multiple columns directly in the database without running validations or callbacks.

user.update_columns(login_count: 10, last_login_at: Time.utc)
user.update_columns({status: "active", verified: true})

Warning: This bypasses:

  • Validations
  • Callbacks
  • Timestamp updates (updated_at will NOT change)

Returns self for chaining.


[View source]
def update_columns(**columns) #

Update multiple columns directly in the database without running validations or callbacks.

user.update_columns(login_count: 10, last_login_at: Time.utc)
user.update_columns({status: "active", verified: true})

Warning: This bypasses:

  • Validations
  • Callbacks
  • Timestamp updates (updated_at will NOT change)

Returns self for chaining.


[View source]