Takarik::Data::BaseModel
Inherits Takarik::Data::Associations < Takarik::Data::Validations < Reference < Object
Base class for all ORM models, providing ActiveRecord-like functionality but designed specifically for Crystal language features
Constructors
Class methods
Check if any records exist. This is an alias for exists?.
Examples: Order.any? # => true if any orders exist Order.shipped.any? # => true if any shipped orders exist Book.where(out_of_print: true).any? # => true if any out of print books exist
Default method that does nothing - can be overridden by default_scope macro
Calculate the average of values in the specified column for all records in the model's table. This method call will look something like this: Order.average("subtotal") Returns a number (possibly a floating-point number such as 3.14159265).
Examples: Order.average("subtotal") # => 25.125 Order.where(status: "shipped").average("subtotal") # => 29.83
Count records by a specific column, counting only non-null values. If you want to be more specific and find all the customers with a title present in the database you can use Customer.count(:title).
Examples: Customer.count(:title) # => 3 (only customers with a title) Customer.where(active: true).count(:email) # => 5 (active customers with email)
Count the number of records in the model's table. If you want to see how many records are in your model's table you could call Customer.count. Returns the number of records as an integer.
Examples: Customer.count # => 5 Customer.where(first_name: 'Ryan').count # => 2 Customer.group(:status).count # => {"active" => 3, "inactive" => 2}
Delete all records in the model's table (without callbacks). This method executes a DELETE SQL statement directly without loading records or triggering callbacks. This is much faster than destroy_all but doesn't trigger callbacks or validations.
Examples: Book.delete_all Customer.where(active: false).delete_all
Returns the number of records deleted.
SQL: DELETE FROM books
Delete records matching the given conditions (without callbacks). This method executes a DELETE SQL statement directly without loading records or triggering callbacks. This is much faster than destroy_by but doesn't trigger callbacks or validations.
Examples: Book.delete_by(author: "Douglas Adams") Customer.delete_by(active: false) Order.delete_by(status: ["cancelled", "refunded"]) # Array values (IN clause) User.delete_by(name: nil) # Nil values (IS NULL) Product.delete_by(price: 10.0..50.0) # Range values (BETWEEN)
Returns the number of records deleted.
SQL: DELETE FROM books WHERE (books.author = 'Douglas Adams')
Destroy all records in the model's table (with callbacks). This method loads each record and calls destroy on it, triggering callbacks.
Examples: Book.destroy_all Customer.where(active: false).destroy_all
Returns the number of records destroyed.
SQL: SELECT * FROM books Then: DELETE FROM books WHERE id = ? (for each record)
Find records matching the given conditions and destroy them (with callbacks). This method loads each record and calls destroy on it, triggering callbacks.
Examples: Book.destroy_by(author: "Douglas Adams") Customer.destroy_by(active: false) Order.destroy_by(status: "cancelled", created_at: 1.week.ago..Time.current)
Returns the number of records destroyed.
SQL: SELECT * FROM books WHERE (books.author = 'Douglas Adams') Then: DELETE FROM books WHERE id = ? (for each record)
Check if the table/relation is empty.
Examples: Customer.empty? # => true if no customers exist Customer.where(active: false).empty? # => true if no inactive customers exist
Check for the existence of objects using various approaches. This method will query the database using an optimized existence check, but instead of returning an object or collection of objects it will return either true or false.
Examples: Customer.exists?(1) # => true/false Customer.exists?([1, 2, 3]) # => true if any of these IDs exist Customer.exists?(id: [1, 2, 3]) # => true if any of these IDs exist Customer.exists?(first_name: ["Jane", "Sergei"]) # => true if any match Customer.exists? # => true if any customers exist Customer.where(first_name: "Ryan").exists? # => true if any Ryan exists
Find record by primary key. Returns the record or nil if not found.
Examples: Customer.find(10) # => Customer or nil
SQL: SELECT * FROM customers WHERE (customers.id = 10) LIMIT 1
Find multiple records by array of primary keys. Returns array of records. Raises RecordNotFound if any ID is missing.
Examples: Customer.find([1, 10]) # => Array of Customer records
SQL: SELECT * FROM customers WHERE (customers.id IN (1,10))
Find multiple records by splat arguments. Returns array of records. Equivalent to find([id1, id2, ...])
Examples: Customer.find(1, 10) # Same as Customer.find([1, 10])
Find record by primary key. Raises RecordNotFound exception if not found.
Examples: Customer.find!(10) # => Customer or raises RecordNotFound
Find multiple records by array of primary keys. Raises RecordNotFound if not found.
Examples: Customer.find!([1, 10]) # => Array of Customer records or raises RecordNotFound
Find multiple records by splat arguments. Raises RecordNotFound if not found.
Examples: Customer.find!(1, 10) # Same as Customer.find!([1, 10])
Find the first record matching the given conditions without any implicit ordering. Returns nil if no record is found.
Examples: Customer.find_by(first_name: "Lifo") # => Customer or nil Customer.find_by(first_name: "Lifo", last_name: "Smith") # => Customer or nil
SQL: SELECT * FROM customers WHERE (customers.first_name = 'Lifo') LIMIT 1
Find the first record matching the given conditions without any implicit ordering. Raises RecordNotFound if no record is found.
Examples: Customer.find_by!(first_name: "Lifo") # => Customer or raises RecordNotFound Customer.find_by!(first_name: "NonExistent") # => raises RecordNotFound
Execute a custom SQL query and return an array of model instances. This method provides a way to use custom SQL while still getting instantiated objects.
Examples: Customer.find_by_sql("SELECT * FROM customers WHERE age > 21") Customer.find_by_sql("SELECT * FROM customers INNER JOIN orders ON customers.id = orders.customer_id ORDER BY customers.created_at DESC") Customer.find_by_sql("SELECT * FROM customers WHERE name = ?", ["John"]) Customer.find_by_sql("SELECT * FROM customers WHERE name = ? AND age > ?", ["John", 21])
The method always returns an array, even if the query returns a single record. Returns an empty array if no records are found.
SQL: Executes the provided SQL directly
Retrieve records in batches and yield each one to the block. This is efficient for processing large datasets without loading everything into memory.
Examples: Customer.find_each do |customer| NewsMailer.weekly(customer).deliver_now end
Customer.find_each(start: 2000, batch_size: 5000) do |customer| process_customer(customer) end
For composite primary keys
Order.find_each(order: [:asc, :desc]) do |order| process_order(order) end
Options: :start - Starting cursor value (inclusive) :finish - Ending cursor value (inclusive) :batch_size - Number of records per batch (default: 1000) :error_on_ignore - Raise error if existing order is present (default: nil) :cursor - Column(s) to use for batching (default: primary_key) :order - Cursor order (:asc/:desc or array like [:asc, :desc], default: :asc)
Returns an Enumerator when no block is given
Yields each batch of records as an array.
Examples: Customer.find_in_batches do |batch| batch.each { |customer| NewsMailer.weekly(customer).deliver_now } end
Customer.find_in_batches(start: 1000, batch_size: 500) do |batch| batch.each { |customer| process_customer(customer) } end
Returns an Enumerator when no block is given
Find the first record matching the given conditions or create a new one. Returns the existing record if found, otherwise creates and returns a new record.
Examples: Customer.find_or_create_by(first_name: "Andy") Customer.find_or_create_by(first_name: "Andy", last_name: "Smith")
With a block to set additional attributes only when creating: Customer.find_or_create_by(first_name: "Andy") do |customer| customer.locked = false end
SQL: SELECT * FROM customers WHERE (customers.first_name = 'Andy') LIMIT 1 If not found: INSERT INTO customers (first_name, ...) VALUES ('Andy', ...)
Find the first record matching the given conditions or create a new one. Raises an exception if the new record is invalid.
Examples: Customer.find_or_create_by!(first_name: "Andy") Customer.find_or_create_by!(first_name: "Andy") { |c| c.locked = false }
Find the first record matching the given conditions or initialize a new one. The new record will not be saved to the database.
Examples: nina = Customer.find_or_initialize_by(first_name: "Nina") nina.persisted? # => false nina.new_record? # => true nina.save # Save when ready
With a block to set additional attributes only when initializing: Customer.find_or_initialize_by(first_name: "Nina") do |customer| customer.locked = false end
SQL: SELECT * FROM customers WHERE (customers.first_name = 'Nina') LIMIT 1
Find up to the specified number of records ordered by primary key (default).
Examples: Customer.first(3) # => Array of up to 3 Customer records
SQL: SELECT * FROM customers ORDER BY customers.id ASC LIMIT 3
Find the first record ordered by primary key (default). If default scope contains an order method, returns the first record according to that ordering.
Examples: Customer.first # => Customer or nil
SQL: SELECT * FROM customers ORDER BY customers.id ASC LIMIT 1 SQL (composite): SELECT * FROM customers ORDER BY customers.store_id ASC, customers.id ASC LIMIT 1
Find up to the specified number of records ordered by primary key. Raises RecordNotFound if no records found.
Examples: Customer.first!(3) # => Array of Customer records or raises RecordNotFound
Find the first record ordered by primary key. Raises RecordNotFound if no record found.
Examples: Customer.first! # => Customer or raises RecordNotFound
Pluck all the IDs for the relation using the table's primary key. This is a convenience method equivalent to pluck(primary_key).
Examples: Customer.ids # => [1, 2, 3] Customer.where(active: true).ids # => [1, 3]
Yields QueryBuilder objects to work with a batch of records. This is similar to Rails' in_batches method.
Examples: User.where("age > 21").in_batches do |relation| relation.delete_all sleep(1) # Throttle the delete queries end
User.in_batches.each_with_index do |relation, batch_index| puts "Processing relation ##{batch_index}" relation.delete_all end
Returns a BatchEnumerator when no block is given
Find up to the specified number of records ordered by primary key (default) in reverse.
Examples: Customer.last(3) # => Array of up to 3 Customer records (highest IDs first)
SQL: SELECT * FROM customers ORDER BY customers.id DESC LIMIT 3
Find the last record ordered by primary key (default). If default scope contains an order method, returns the last record according to that ordering.
Examples: Customer.last # => Customer or nil
SQL: SELECT * FROM customers ORDER BY customers.id DESC LIMIT 1 SQL (composite): SELECT * FROM customers ORDER BY customers.store_id DESC, customers.id DESC LIMIT 1
Find up to the specified number of records ordered by primary key in reverse. Raises RecordNotFound if no records found.
Examples: Customer.last!(3) # => Array of Customer records or raises RecordNotFound
Find the last record ordered by primary key. Raises RecordNotFound if no record found.
Examples: Customer.last! # => Customer or raises RecordNotFound
Adds a locking clause to queries for pessimistic locking
Check if there are more than one record. Uses an optimized approach to avoid counting all records.
Examples: Order.many? # => true if there are 2 or more orders Order.shipped.many? # => true if there are 2 or more shipped orders Book.where(out_of_print: true).many? # => true if there are 2 or more out of print books
Find the maximum value in the specified column for all records in the model's table. This method call will look something like this: Order.maximum("subtotal")
Examples: Order.maximum("subtotal") # => 199.99 Order.where(status: "shipped").maximum("created_at") # => 2023-12-31 23:59:59
Find the minimum value in the specified column for all records in the model's table. This method call will look something like this: Order.minimum("subtotal")
Examples: Order.minimum("subtotal") # => 5.99 Order.where(status: "shipped").minimum("created_at") # => 2023-01-15 10:30:00
Pick the value(s) from the named column(s) using the table's primary key ordering. Returns the first row of the specified column values with corresponding data type. This is a short-hand for relation.limit(1).pluck(*column_names).first.
Examples: Customer.pick(:id) # => 1 Customer.pick(:id, :first_name) # => [1, "David"] Customer.where(id: 999).pick(:id) # => nil
Pluck the value(s) from the named column(s) in the current relation. Returns an array of values of the specified columns with the corresponding data type.
Examples: Book.where(out_of_print: true).pluck(:id) # => [1, 2, 3] Order.distinct.pluck(:status) # => ["shipped", "being_packed", "cancelled"] Customer.pluck(:id, :first_name) # => [[1, "David"], [2, "Fran"], [3, "Jose"]]
Execute a custom SQL query and return raw results as an array of hashes. This is similar to find_by_sql but returns raw data instead of model instances. This method is equivalent to Rails' lease_connection.select_all.
Examples: Customer.select_all("SELECT first_name, created_at FROM customers WHERE id = '1'")
=> [{"first_name" => "Rafael", "created_at" => "2012-11-10 23:23:45.281189"}]
Calculate the sum of values in the specified column for all records in the model's table. This method call will look something like this: Order.sum("subtotal")
Examples: Order.sum("subtotal") # => 150.75 Order.where(status: "shipped").sum("subtotal") # => 89.50
Retrieve up to the specified number of records without any implicit ordering.
Examples: Customer.take(2) # => Array of up to 2 Customer records
SQL: SELECT * FROM customers LIMIT 2
Retrieve a record without any implicit ordering. Returns nil if no record found.
Examples: Customer.take # => Customer or nil
SQL: SELECT * FROM customers LIMIT 1
Retrieve up to the specified number of records without any implicit ordering. Raises RecordNotFound if no records found.
Examples: Customer.take!(2) # => Array of Customer records or raises RecordNotFound
Retrieve a record without any implicit ordering. Raises RecordNotFound if no record found.
Examples: Customer.take! # => Customer or raises RecordNotFound
Executes the given block within a database transaction. If an exception is raised within the block, the transaction is rolled back. Otherwise, the transaction is committed.
Example: User.transaction do user = User.create(name: "John") user.posts.create(title: "Hello World") end
Remove all scoping and execute the given block in an unscoped context This is useful for temporarily bypassing default scopes
Examples: Book.unscoped { Book.out_of_print } # Executes without default scope User.unscoped { User.where(active: false) } # Bypasses default scope
The block is executed in the context of the unscoped model class
Instance methods
Delete this record from the database without callbacks. This method executes a DELETE SQL statement directly without triggering any callbacks. This is much faster than destroy but doesn't trigger callbacks or dependent associations.
Examples: user = User.find(1) user.delete # => true (record deleted without callbacks)
Returns true if the record was successfully deleted, false otherwise. Returns false if the record is a new record (not persisted).
Note: This method does NOT handle dependent associations or run callbacks. Use destroy if you need callbacks and dependent association handling.
Transaction-aware destroy method for use within existing transactions
Returns the value of the :id column specifically (not primary key). This is different from primary key access in composite key models.
Examples: customer = Customer.last customer.id_value # => 10 (returns the :id column value)
For composite primary key models like [:store_id, :id], this returns only the :id column value, not the full composite key.
======================================== INSTANCE METHODS - PERSISTENCE
These methods implement real database transactions with proper callback execution:
Execution order:
- before_* callbacks (outside transaction)
- START TRANSACTION
- Database operation (INSERT/UPDATE/DELETE)
- after_create/after_update/after_destroy callbacks (inside transaction)
- after_save callbacks (inside transaction, for create/update only)
- COMMIT TRANSACTION (automatic if no exceptions)
- after_commit callbacks (after successful commit)
On failure or exception:
- ROLLBACK TRANSACTION (automatic on exception, explicit on failed operation)
- after_rollback callbacks (after rollback)
Obtains a row lock on this record, reloads the attributes, and yields to the block. The lock is released when the block completes. This is useful for ensuring that only one process can modify a record at a time.
Example: book = Book.first book.with_lock do # This block is called within a transaction, # book is already locked. book.increment!(:views) end
Macros
Generic commit callbacks (ActiveRecord pattern)
Object lifecycle callbacks (ActiveRecord pattern)
Enumerate macro for creating enumerated attributes with automatic scopes and methods
Enhanced scope macro supporting arguments and conditionals like ActiveRecord