class CQL::ActiveRecord::Relations::ManyCollection(Target, Through, Pk)

Overview

A collection of records for a many to many relationship This class is used to manage the relationship between two tables through a join table (through)

A many-to-many association occurs when multiple records of one model can be associated with multiple records of another model, and vice versa. Typically, it requires a join table (or a junction table) to store the relationships between the records of the two models.

Here's how a many-to-many association is commonly implemented in CQL using Crystal.

Example

class Movie
  include CQL::Model(Movie, Int64)

  property id : Int64
  property title : String

  many_to_many :actors, Actor, join_through: :movies_actors
end

class Actor
  include CQL::Model(Actor, Int64)
  property id : Int64
  property name : String
end

class MoviesActors
  include CQL::Model(MoviesActors, Int64)
  property id : Int64
  property movie_id : Int64
  property actor_id : Int64
end

movie = Movie.create(title: "The Matrix")
actor = Actor.create(name: "Keanu Reeves")

Defined in:

active_record/relations/many_collection.cr

Constructors

Instance Method Summary

Instance methods inherited from class CQL::ActiveRecord::Relations::Collection(Target, Pk)

<<(record : Target) <<, all : Array(Target) all, build(**attributes) build, clear clear, create(record : Target)
create(**attributes)
create
, delete(record : Target)
delete(id : Pk)
delete
, each(&block : Target -> ) each, empty? empty?, exists?(**attributes) exists?, find(**attributes) find, find_by(**attributes) find_by, first first, ids : Array(Pk) ids, ids=(ids : Array(Pk)) ids=, reload reload, size size, where(**conditions) where

Constructor methods inherited from class CQL::ActiveRecord::Relations::Collection(Target, Pk)

new(key : Symbol, id : Pk, cascade : Bool = false, query : CQL::Query = (CQL::Query.new(Target.schema)).from(Target.table), auto_load : Bool = true) new

Macros inherited from class CQL::ActiveRecord::Relations::Collection(Target, Pk)

method_missing(call) method_missing

Constructor Detail

def self.new(key : Symbol, id : Pk, target_key : Symbol, cascade : Bool = false, query : CQL::Query = (CQL::Query.new(Target.schema)).from(Target.table)) #

Initialize the many-to-many association collection class

  • param : key (Symbol) - The key for the parent record
  • param : id (Pk) - The id value for the parent record
  • param : target_key (Symbol) - The key for the associated record
  • param : cascade (Bool) - Delete associated records
  • param : query (CQL::Query) - Query object
  • return : ManyCollection

Example

ManyCollection.new(
  :movie_id,
  1,
  :actor_id,
  false,
  CQL::Query.new(Actor.schema).from(Actor.table)
)

[View source]

Instance Method Detail

def <<(record : Target) #

Adds an existing record to the association. Saves the association to the join table. Raises an error if the target record is not persisted.

  • param : record (Target)
  • return : self

Example

movie = Movie.find(1)
actor = Actor.create(name: "Laurence Fishburne")
movie.actors << actor
movie.actors.reload.size # => 3 (assuming 2 existed before)

[View source]
def all : Array(Target) #

Override all to use lazy loading


[View source]
def build(**attributes) : Target #

Build a new target record associated with this parent, but don't save it. The association is only truly formed when saved via #create or <<.

  • param : attributes (Hash | NamedTuple) - Attributes for the new target record.
  • return : Target - The newly built target record.

Example

new_actor = movie.actors.build(name: \"Agent Smith\")
new_actor.persisted? # => false
new_actor.save # Creates Actor and the MoviesActors record via appropriate callbacks/methods if defined

[View source]
def clear #

Clears all associated records from the parent record. Removes associations from the join table. If cascade is true, also deletes the target records themselves. Clears the internal collection. Wraps the operation in a transaction.

  • return : self

Example

movie.actors.create(name: \"Carrie-Anne Moss\")
movie.actors.clear
movie.actors.size # => 0
Actor.exists?(name: \"Carrie-Anne Moss\") # => false if cascade was true

[View source]
def create(record : Target) #

Associates an existing or new target record with the parent record. Creates the association in the join table. If the target record is new, it's created first. Wraps the process in a transaction. Adds the record to the collection if loaded.

  • param : record (Target) - The record to associate (can be new or persisted)
  • return : Target - The associated (and possibly created) record
  • raise : CQL::Error on creation failure

Example

actor = Actor.new(name: "Hugo Weaving")
movie.actors.create(actor)
=> #<Actor:0x... @id=..., @name="Hugo Weaving">
movie.actors.all # includes the new actor if loaded

[View source]
def create(**attributes) #

Create a new target record with given attributes, creates the association in the join table, and adds the record to the collection if loaded. Wraps the creation process in a transaction.

  • param : attributes (Hash(Symbol, String | Int64) | NamedTuple)
  • return : Target
  • raise : CQL::Error on creation failure

Example

movie.actors.create(name: "Carrie-Anne Moss")
=> #<Actor:0x... @id=..., @name="Carrie-Anne Moss">
movie.actors.all # includes the new actor if loaded

[View source]
def delete(record : Target) : Target | Nil #

Deletes the association for the given record. If cascade is true, also deletes the target record itself. Removes the record from the collection if loaded.

  • param : record (Target)
  • return : Target? - The deleted target record (if found and cascade=true), or nil

Example

actor = movie.actors.find_by(name: \"Carrie-Anne Moss\")
movie.actors.delete(actor)
movie.actors.all # \"Carrie-Anne Moss\" is gone
Actor.find_by(name: \"Carrie-Anne Moss\") # => nil if cascade was true

[View source]
def delete(id : Pk) : Target | Nil #

Deletes the association for the record with the given ID. If cascade is true, also deletes the target record itself. Removes the record from the collection if loaded. Wraps target deletion in a transaction if cascade is true.

  • param : id (Pk)
  • return : Target? - The target record if cascade was true and deletion occurred, otherwise nil.

Example

movie.actors.delete(1) # Assuming actor with ID 1 exists
movie.actors.reload    # Actor 1 is gone
Actor.find?(1)         # => nil if cascade was true

[View source]
def each(&block : Target -> ) #

Override each to use lazy loading


[View source]
def exists?(**attributes) #

Check if the association exists or not based on the attributes provided

  • param : attributes (Hash(Symbol, String | Int64))
  • return : Bool

Example

movie.actors.exists?(name: "Keanu Reeves")
=> true

[View source]
def find(**attributes) #

Find associated records based on the attributes provided for the parent record

  • param : attributes (Hash(Symbol, String | Int64))
  • return : Array(Target)

Example

movie.actors.find(name: "Keanu Reeves")
=> [#<Actor:0x00007f8b3b1b3f00 @id=1, @name="Keanu Reeves">]

[View source]
def find_by(**attributes) : Target | Nil #

Find the first associated record matching the given attributes. Queries the database directly using a JOIN.

  • param : attributes (NamedTuple | Hash(Symbol, DB::Any))
  • return : Target?

Example

movie.actors.find_by(name: \"Keanu Reeves\")
=> #<Actor...>

[View source]
def ids=(ids : Array(Pk)) #

Associates the parent record with the records that match the primary keys provided

  • param : ids (Array(Pk))
  • return : Array(Target)

Example

movie.actors.ids = [1, 2, 3]
movie.actors.reload
movie.actors.all => [
#<Actor:0x00007f8b3b1b3f00 @id=1, @name="Carrie-Anne Moss">,
   #<Actor:0x00007f8b3b1b3f00 @id=2, @name="Hugo Weaving">,
  #<Actor:0x00007f8b3b1b3f00 @id=3, @name="Laurence Fishburne">]

[View source]
def reload #

Reload the association records from the database and return them This now performs the correct JOIN query.

  • return : Array(Target)

Example

movie.actors.reload
=> [#<Actor:0x00007f8b3b1b3f00 @id=1, @name="Carrie-Anne Moss">]

[View source]
def where(**attributes) : CQL::Query #

Returns a query scope for associated records, filtered by attributes. Allows chaining further query methods (e.g., .limit, .order). Queries the database directly using a JOIN.

  • param : attributes (NamedTuple | Hash(Symbol, DB::Any))
  • return : CQL::Query

Example

movie.actors.where(name: \"Keanu Reeves\").limit(1).first

[View source]