Clear ORM
  • Welcome to Clear
  • Introduction
    • Setup
  • Model
    • Defining your model
      • Describing your columns
      • Primary Keys
      • Converters
    • Associations
      • belongs_to
      • has_many
      • has_many through
      • has_one
    • Lifecycle
      • Persistence
      • Validations
      • Triggers
    • Batchs operations
      • Bulk update
      • Bulk insert & delete
    • Transactions & Save Points
      • Transaction & Savepoints
      • Connection pool
    • Locks
  • Querying
    • The collection object
      • Filter the query
        • Filter the query – The Expression Engine
        • Find, First, Last, Offset, Limit
        • Aggregation
        • Ordering & Group by
      • Fetching the query
        • Each and Fetch
        • Cursored fetching
        • Model extra attributes
      • Joins
      • Eager Loading
      • Window and CTE
      • Scopes
    • Writing low-level SQL
      • Select Clause
      • Insert Clause
      • Delete Clause
  • Migrations
    • Manage migrations
    • Call migration script
    • Migration CLI
  • Additional and advanced features
    • JSONB
    • Symbol vs String
    • Enums
    • BCrypt
    • Full Text Search
    • Handling multi-connection
  • Other resources
    • API Documentation
    • Inline documentation
    • Github repository
    • Credits
    • Benchmark
Powered by GitBook
On this page
  • Usage Example
  • Middle-table model
  1. Model
  2. Associations

has_many through

Has many through represents a relation where both side can relate to multiple models.

Basically, in SQL this can be performed by using a middle-table which store foreign key of both of the classes.

Usage Example

For example, let's assume we have a table posts and a table tags which are loosely connected: a post can have multiple tags at once, while a tag can references multiple posts. In this example, we will need a middle-table which will be named post_tags :

CREATE TABLE tags (
    id bigserial NOT NULL PRIMARY KEY, 
    name text NOT NULL
);

CREATE UNIQUE INDEX tags_name ON tags (name);

CREATE TABLE posts (
    id bigserial NOT NULL PRIMARY KEY,
    name text NOT NULL,
    content text
);

CREATE TABLE post_tags (
    tag_id bigint NOT NULL, 
    post_id bigint NOT NULL, 
    FOREIGN KEY (tag_id) REFERENCES tags (id) ON DELETE CASCADE, 
    FOREIGN KEY (post_id) REFERENCES posts (id) ON DELETE CASCADE
);

You may notice usage of FOREIGN KEY constraints over post_tags. Clear doesn't provide any feature for cascading deletion, and relay exclusively on PostgreSQL.

Now, let's define our models:

class Post
  include Clear::Model

  primary_key

  column name : String
  column content : String?

  has_many tags : Tag, through: "post_tags"
end

class Tag
  include Clear::Model

  primary_key

  column name : String

  has_many tags : Post, through: "post_tags"
end

And thats all ! Basically, in this case, we may not want to create a model PostTag as the table exists only to make the link between the two models.

Addition and deletion is provided in elegant way even without model:

add_to_post.cr
p = Post.new({name: "My new post"})
p.save!
# Add the tag Technology to the post
p.tags << Tag.query.find_or_create({name: "Technology"}){}

p has to be saved in database before linking the tag.

delete_tag.cr
p = Post.query.first!

tags = p.tags
tags.unlink( tags.where(name: "Technology").first! )

Middle-table model

Optionally, we can define our middle-table model. In this case, you should use the model as through argument :

class Post
  include Clear::Model

  class Tag
    include Clear::Model

    belongs_to post : Post
    belongs_to tag : Tag

    self.table = "post_tags"
  end

  primary_key

  column name : String
  column content : String?

  has_many tags : Tag, through: Post::Tag
end

class Tag
  include Clear::Model

  primary_key

  column name : String

  has_many tags : Post, through: Post::Tag
end
Previoushas_manyNexthas_one

Last updated 5 years ago

Note: The model Post::Tag don't have primary key which can lead to issues with Clear.

Feel free to leave issues to the community here.