Skip to content

Commit ae93b6f

Browse files
eliasjprclaude
andcommitted
Fix Active Record documentation to match CQL API patterns
- Change relationship macros to use positional foreign_key parameter (e.g., `has_many :posts, Post, :user_id` instead of `foreign_key:`) - Replace `includes` with `preload` for eager loading - Fix validation options: remove non-existent `min`, `max`, `unique`; use correct predicates `gt`, `gte`, `lt`, `lte` - Remove counter_cache references (not implemented in CQL) - Fix `on_delete: "CASCADE"` to use symbol `:cascade` These changes ensure all code examples compile and match the actual CQL Active Record implementation. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent bfee62d commit ae93b6f

14 files changed

Lines changed: 69 additions & 88 deletions

File tree

docs/explanation/design-patterns/active-record.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,8 @@ struct Post
126126
property body : String
127127
property user_id : Int64
128128
129-
belongs_to :user, User, foreign_key: :user_id
130-
has_many :comments, Comment, foreign_key: :post_id
129+
belongs_to :user, User, :user_id
130+
has_many :comments, Comment, :post_id
131131
132132
validate :title, presence: true
133133
@@ -187,8 +187,8 @@ struct Order
187187
property status : String = "pending"
188188
property total : BigDecimal = BigDecimal.new(0)
189189
190-
belongs_to :user, User, foreign_key: :user_id
191-
has_many :order_items, OrderItem, foreign_key: :order_id
190+
belongs_to :user, User, :user_id
191+
has_many :order_items, OrderItem, :order_id
192192
193193
validate :status, in: ["pending", "paid", "shipped", "completed", "cancelled"]
194194

docs/how-to/data-operations/delete.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ user.force_delete! # Permanently removes from database
7474
With foreign key cascade:
7575

7676
```crystal
77-
# If posts have: on_delete: "CASCADE"
77+
# If posts have: on_delete: :cascade
7878
user = User.find!(1)
7979
user.delete! # Also deletes all user's posts
8080
```

docs/how-to/models/soft-delete.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ struct Post
163163
164164
db_context MyDB, :posts
165165
166-
has_many :comments, Comment, foreign_key: :post_id
166+
has_many :comments, Comment, :post_id
167167
168168
after_destroy :soft_delete_comments
169169

docs/how-to/performance/n-plus-one.md

Lines changed: 19 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ With 100 posts, this runs 101 queries.
2222

2323
## Use Eager Loading
2424

25-
Load relationships upfront with a single query.
25+
Load relationships upfront with batch queries using `preload`.
2626

27-
### includes (Preload)
27+
### Basic Preload
2828

2929
```crystal
3030
# 2 queries total: posts + users
31-
posts = Post.includes(:user).all
31+
posts = Post.preload(:user).all
3232
3333
posts.each do |post|
3434
puts post.user.name # No additional query
@@ -40,25 +40,10 @@ end
4040
```crystal
4141
# Load multiple associations
4242
posts = Post
43-
.includes(:user, :comments)
43+
.preload(:user, :comments)
4444
.all
4545
```
4646

47-
### Nested Relationships
48-
49-
```crystal
50-
# Load nested associations
51-
posts = Post
52-
.includes(comments: :user)
53-
.all
54-
55-
posts.each do |post|
56-
post.comments.each do |comment|
57-
puts comment.user.name # Already loaded
58-
end
59-
end
60-
```
61-
6247
## Use Joins for Filtering
6348

6449
When filtering by associated records:
@@ -73,12 +58,11 @@ posts = Post
7358

7459
## Select Only Needed Columns
7560

76-
Reduce memory when eager loading:
61+
Reduce memory by selecting specific columns:
7762

7863
```crystal
7964
posts = Post
80-
.includes(:user)
81-
.select("posts.*, users.name as user_name")
65+
.select(:id, :title, :user_id)
8266
.all
8367
```
8468

@@ -123,7 +107,7 @@ SELECT * FROM users WHERE id = 3
123107

124108
```crystal
125109
# Controller
126-
@posts = Post.includes(:user, :comments).all
110+
@posts = Post.preload(:user, :comments).all
127111
128112
# View - no additional queries
129113
<% @posts.each do |post| %>
@@ -132,20 +116,24 @@ SELECT * FROM users WHERE id = 3
132116
<% end %>
133117
```
134118

135-
### Counter Caches
119+
### Manual Counter Columns
136120

137-
For counting associations, use counter caches:
121+
For frequently accessed counts, add a counter column and maintain it manually:
138122

139123
```crystal
140-
# Add column
141-
add_column :posts, :comments_count, Int32, default: 0
124+
# Add column in migration
125+
schema.alter :posts do
126+
add_column :comments_count, Int32, default: 0
127+
end
142128
143-
# Model configuration
144-
struct Post
145-
has_many :comments, Comment, foreign_key: :post_id, counter_cache: true
129+
# Update counter when adding comments
130+
def create_comment(post : Post, content : String)
131+
Comment.create!(content: content, post_id: post.id.not_nil!)
132+
post.comments_count = post.comments.count.to_i32
133+
post.save!
146134
end
147135
148-
# Now post.comments_count uses cached value
136+
# Now use post.comments_count instead of post.comments.count
149137
```
150138

151139
### Aggregations

docs/how-to/relationships/belongs-to.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ struct Comment
4747
property body : String
4848
property post_id : Int64
4949
50-
belongs_to :post, Post, foreign_key: :post_id
50+
belongs_to :post, Post, :post_id
5151
5252
def initialize(@body : String, @post_id : Int64)
5353
end
@@ -125,7 +125,7 @@ struct Comment
125125
# ...
126126
property user_id : Int64?
127127
128-
belongs_to :user, User, foreign_key: :user_id
128+
belongs_to :user, User, :user_id
129129
130130
def initialize(@body : String, @post_id : Int64, @user_id : Int64? = nil)
131131
end

docs/how-to/relationships/has-many.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ struct User
4444
property id : Int64?
4545
property name : String
4646
47-
has_many :posts, Post, foreign_key: :user_id
47+
has_many :posts, Post, :user_id
4848
4949
def initialize(@name : String)
5050
end
@@ -59,7 +59,7 @@ struct Post
5959
property title : String
6060
property body : String
6161
62-
belongs_to :user, User, foreign_key: :user_id
62+
belongs_to :user, User, :user_id
6363
6464
def initialize(@title : String, @body : String, @user_id : Int64)
6565
end

docs/how-to/relationships/has-one.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ struct User
4848
property id : Int64?
4949
property name : String
5050
51-
has_one :profile, Profile, foreign_key: :user_id
51+
has_one :profile, Profile, :user_id
5252
5353
def initialize(@name : String)
5454
end
@@ -63,7 +63,7 @@ struct Profile
6363
property bio : String?
6464
property avatar_url : String?
6565
66-
belongs_to :user, User, foreign_key: :user_id
66+
belongs_to :user, User, :user_id
6767
6868
def initialize(@user_id : Int64, @bio : String? = nil, @avatar_url : String? = nil)
6969
end

docs/how-to/relationships/many-to-many.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ struct Post
5555
property id : Int64?
5656
property title : String
5757
58-
has_many :post_tags, PostTag, foreign_key: :post_id
58+
has_many :post_tags, PostTag, :post_id
5959
6060
def initialize(@title : String)
6161
end
@@ -89,7 +89,7 @@ struct Tag
8989
property id : Int64?
9090
property name : String
9191
92-
has_many :post_tags, PostTag, foreign_key: :tag_id
92+
has_many :post_tags, PostTag, :tag_id
9393
9494
def initialize(@name : String)
9595
end
@@ -114,8 +114,8 @@ struct PostTag
114114
property tag_id : Int64
115115
property created_at : Time?
116116
117-
belongs_to :post, Post, foreign_key: :post_id
118-
belongs_to :tag, Tag, foreign_key: :tag_id
117+
belongs_to :post, Post, :post_id
118+
belongs_to :tag, Tag, :tag_id
119119
120120
def initialize(@post_id : Int64, @tag_id : Int64)
121121
end

docs/reference/api/validation-options.md

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -45,18 +45,18 @@ validate :tags, size: 1..5 # Array size
4545

4646
**Error:** "field_name must be between X and Y characters"
4747

48-
### min / max
48+
### gt / gte / lt / lte
4949

5050
Validates numeric values are within bounds.
5151

5252
```crystal
53-
validate :age, min: 0
54-
validate :age, max: 150
55-
validate :quantity, min: 1, max: 100
56-
validate :price, min: 0.01
53+
validate :age, gte: 0 # Greater than or equal to 0
54+
validate :age, lte: 150 # Less than or equal to 150
55+
validate :quantity, gte: 1, lte: 100 # Between 1 and 100
56+
validate :price, gt: 0 # Greater than 0
5757
```
5858

59-
**Error:** "field_name must be at least X" / "field_name must be at most X"
59+
**Error:** "field_name must be greater than X" / "field_name must be less than or equal to X"
6060

6161
### in
6262

@@ -70,28 +70,25 @@ validate :priority, in: [1, 2, 3, 4, 5]
7070

7171
**Error:** "field_name must be one of: X, Y, Z"
7272

73-
### unique
73+
### exclude
7474

75-
Validates that a value is unique in the database.
75+
Validates that a value is not in a list of excluded values.
7676

7777
```crystal
78-
validate :email, unique: true
79-
validate :username, unique: true
80-
validate :slug, unique: true
78+
validate :username, exclude: ["admin", "root", "system"]
79+
validate :status, exclude: ["deleted"]
8180
```
8281

83-
**Note:** Performs a database query on each validation.
84-
85-
**Error:** "field_name has already been taken"
82+
**Error:** "field_name must not be included in [X, Y, Z]"
8683

8784
## Combining Validations
8885

8986
Multiple validations can be applied to a single field:
9087

9188
```crystal
92-
validate :email, presence: true, match: /@/, unique: true
89+
validate :email, presence: true, match: /@/
9390
validate :username, presence: true, size: 3..20, match: /^[a-z0-9_]+$/
94-
validate :age, presence: true, min: 0, max: 150
91+
validate :age, presence: true, gte: 0, lte: 150
9592
```
9693

9794
## Model Example
@@ -121,12 +118,8 @@ struct User
121118
validate :username, size: 3..20
122119
validate :password, size: 8..128
123120
124-
# Uniqueness
125-
validate :email, unique: true
126-
validate :username, unique: true
127-
128121
# Numeric constraints
129-
validate :age, min: 0, max: 150
122+
validate :age, gte: 0, lte: 150
130123
131124
# Allowed values
132125
validate :role, in: ["admin", "moderator", "user"]

docs/reference/quick-reference.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,9 @@ User.where(active: true)
7777

7878
```crystal
7979
# Define
80-
belongs_to :user, User, foreign_key: :user_id
81-
has_one :profile, Profile, foreign_key: :user_id
82-
has_many :posts, Post, foreign_key: :user_id
80+
belongs_to :user, User, :user_id
81+
has_one :profile, Profile, :user_id
82+
has_many :posts, Post, :user_id
8383
8484
# Use
8585
user.posts.all

0 commit comments

Comments
 (0)