outlet-orm 6.0.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/bin/init.js +122 -0
- package/bin/mcp.js +78 -0
- package/bin/migrate.js +25 -0
- package/docs/skills/outlet-orm/ADVANCED.md +575 -0
- package/docs/skills/outlet-orm/AI.md +220 -0
- package/docs/skills/outlet-orm/API.md +522 -0
- package/docs/skills/outlet-orm/BACKUP.md +150 -0
- package/docs/skills/outlet-orm/MIGRATIONS.md +605 -0
- package/docs/skills/outlet-orm/MODELS.md +427 -0
- package/docs/skills/outlet-orm/QUERIES.md +345 -0
- package/docs/skills/outlet-orm/RELATIONS.md +555 -0
- package/docs/skills/outlet-orm/SECURITY.md +386 -0
- package/docs/skills/outlet-orm/SEEDS.md +98 -0
- package/docs/skills/outlet-orm/SKILL.md +205 -0
- package/docs/skills/outlet-orm/TYPESCRIPT.md +480 -0
- package/package.json +7 -3
- package/src/AI/AISafetyGuardrails.js +146 -0
- package/src/AI/MCPServer.js +685 -0
- package/src/AI/PromptGenerator.js +318 -0
- package/src/Model.js +154 -2
- package/src/QueryBuilder.js +82 -0
- package/src/index.js +11 -1
- package/types/index.d.ts +147 -0
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
# Outlet ORM - Relations & Eager Loading
|
|
2
|
+
|
|
3
|
+
[← Back to Index](SKILL.md) | [Previous: Queries](QUERIES.md) | [Next: Migrations →](MIGRATIONS.md)
|
|
4
|
+
|
|
5
|
+
> 📘 **TypeScript**: Use typed relationships like`HasOneRelation<Profile>`,`HasManyRelation<Post>`. See [TYPESCRIPT.md](TYPESCRIPT.md#relationships-typedes)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Naming Conventions
|
|
10
|
+
|
|
11
|
+
### Tables
|
|
12
|
+
- Singular or plural:`user`or`users`
|
|
13
|
+
- Pivot tables: alphabetical order`role_user`(not`user_role`)
|
|
14
|
+
|
|
15
|
+
### Foreign Keys
|
|
16
|
+
- Format:`{model}_id`(e.g.,`user_id`,`post_id`)
|
|
17
|
+
|
|
18
|
+
### Polymorphic Columns
|
|
19
|
+
- Type:`{name}_type`(e.g.,`commentable_type`)
|
|
20
|
+
- ID:`{name}_id`(e.g.,`commentable_id`)
|
|
21
|
+
|
|
22
|
+
### Relation Methods
|
|
23
|
+
|
|
24
|
+
| Type | Naming | Example |
|
|
25
|
+
|------|--------|---------|
|
|
26
|
+
|`belongsTo`| singular |`user()`,`category()`|
|
|
27
|
+
|`hasOne`| singular |`profile()`|
|
|
28
|
+
|`hasMany`| plural |`posts()`,`comments()`|
|
|
29
|
+
|`belongsToMany`| plural |`tags()`,`roles()`|
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Relation Types Overview
|
|
35
|
+
|
|
36
|
+
| Relation | Description | Example |
|
|
37
|
+
|----------|-------------|---------|
|
|
38
|
+
|`hasOne`| One-to-One | User → Profile |
|
|
39
|
+
|`hasMany`| One-to-Many | User → Posts |
|
|
40
|
+
|`belongsTo`| Inverse of hasOne/hasMany | Post → User |
|
|
41
|
+
|`belongsToMany`| Many-to-Many | User ↔ Roles |
|
|
42
|
+
|`hasManyThrough`| One-to-Many via intermediate | Country → Posts via Users |
|
|
43
|
+
|`hasOneThrough`| One-to-One via intermediate | Supplier → UserHistory via User |
|
|
44
|
+
|`morphOne`| Polymorphic One-to-One | Post → Image |
|
|
45
|
+
|`morphMany`| Polymorphic One-to-Many | Post → Comments |
|
|
46
|
+
|`morphTo`| Polymorphic inverse | Comment → (Post\|Video) |
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Has One (One-to-One)
|
|
51
|
+
|
|
52
|
+
A user has one profile.
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
const { Model } = require('outlet-orm');
|
|
56
|
+
|
|
57
|
+
class Profile extends Model {
|
|
58
|
+
static table = 'profiles';
|
|
59
|
+
|
|
60
|
+
user() {
|
|
61
|
+
return this.belongsTo(User, 'user_id');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
class User extends Model {
|
|
66
|
+
static table = 'users';
|
|
67
|
+
|
|
68
|
+
profile() {
|
|
69
|
+
return this.hasOne(Profile, 'user_id');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Usage
|
|
74
|
+
const user = await User.find(1);
|
|
75
|
+
const profile = await user.profile().get();
|
|
76
|
+
|
|
77
|
+
// With eager loading
|
|
78
|
+
const user = await User.with('profile').find(1);
|
|
79
|
+
console.log(user.relationships.profile);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Parameters:**
|
|
83
|
+
-`hasOne(RelatedModel, foreignKey, localKey)`
|
|
84
|
+
-`foreignKey`: default =`{model}_id`
|
|
85
|
+
-`localKey`: default =`id`
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Has Many (One-to-Many)
|
|
90
|
+
|
|
91
|
+
A user has many posts.
|
|
92
|
+
|
|
93
|
+
```javascript
|
|
94
|
+
class Post extends Model {
|
|
95
|
+
static table = 'posts';
|
|
96
|
+
|
|
97
|
+
author() {
|
|
98
|
+
return this.belongsTo(User, 'user_id');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
class User extends Model {
|
|
103
|
+
static table = 'users';
|
|
104
|
+
|
|
105
|
+
posts() {
|
|
106
|
+
return this.hasMany(Post, 'user_id');
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Usage
|
|
111
|
+
const user = await User.find(1);
|
|
112
|
+
const posts = await user.posts().get();
|
|
113
|
+
|
|
114
|
+
// With eager loading
|
|
115
|
+
const user = await User.with('posts').find(1);
|
|
116
|
+
console.log(user.relationships.posts); // Array of posts
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Belongs To (Inverse)
|
|
122
|
+
|
|
123
|
+
A post belongs to a user.
|
|
124
|
+
|
|
125
|
+
```javascript
|
|
126
|
+
class User extends Model {
|
|
127
|
+
static table = 'users';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
class Post extends Model {
|
|
131
|
+
static table = 'posts';
|
|
132
|
+
|
|
133
|
+
author() {
|
|
134
|
+
return this.belongsTo(User, 'user_id');
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Usage
|
|
139
|
+
const post = await Post.find(1);
|
|
140
|
+
const author = await post.author().get();
|
|
141
|
+
|
|
142
|
+
// With eager loading
|
|
143
|
+
const post = await Post.with('author').find(1);
|
|
144
|
+
console.log(post.relationships.author);
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Parameters:**
|
|
148
|
+
-`belongsTo(RelatedModel, foreignKey, ownerKey)`
|
|
149
|
+
-`foreignKey`: FK on current model
|
|
150
|
+
-`ownerKey`: default =`id`
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Belongs To Many (Many-to-Many)
|
|
155
|
+
|
|
156
|
+
Users and roles with pivot table.
|
|
157
|
+
|
|
158
|
+
```sql
|
|
159
|
+
-- Tables
|
|
160
|
+
users (id, name, email)
|
|
161
|
+
roles (id, name)
|
|
162
|
+
role_user (user_id, role_id) -- Pivot table
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
```javascript
|
|
166
|
+
class Role extends Model {
|
|
167
|
+
static table = 'roles';
|
|
168
|
+
|
|
169
|
+
users() {
|
|
170
|
+
return this.belongsToMany(User, 'role_user', 'role_id', 'user_id');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
class User extends Model {
|
|
175
|
+
static table = 'users';
|
|
176
|
+
|
|
177
|
+
roles() {
|
|
178
|
+
return this.belongsToMany(
|
|
179
|
+
Role,
|
|
180
|
+
'role_user', // Pivot table
|
|
181
|
+
'user_id', // FK to User
|
|
182
|
+
'role_id' // FK to Role
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Usage
|
|
188
|
+
const user = await User.find(1);
|
|
189
|
+
const roles = await user.roles().get();
|
|
190
|
+
|
|
191
|
+
// Pivot methods
|
|
192
|
+
await user.roles().attach([1, 2]); // Attach roles
|
|
193
|
+
await user.roles().attach(3); // Attach single
|
|
194
|
+
await user.roles().detach(2); // Detach role
|
|
195
|
+
await user.roles().detach(); // Detach all
|
|
196
|
+
await user.roles().sync([1, 3, 4]); // Sync (replace all)
|
|
197
|
+
|
|
198
|
+
// Access pivot data
|
|
199
|
+
const roles = await user.roles().get();
|
|
200
|
+
roles.forEach(role => {
|
|
201
|
+
console.log(role.pivot); // { user_id: 1, role_id: 2 }
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Has Many Through
|
|
208
|
+
|
|
209
|
+
Access remote relationships via intermediate model.
|
|
210
|
+
|
|
211
|
+
```sql
|
|
212
|
+
-- Country -> User -> Post
|
|
213
|
+
countries (id, name)
|
|
214
|
+
users (id, country_id, name)
|
|
215
|
+
posts (id, user_id, title)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
```javascript
|
|
219
|
+
class Country extends Model {
|
|
220
|
+
static table = 'countries';
|
|
221
|
+
|
|
222
|
+
posts() {
|
|
223
|
+
return this.hasManyThrough(
|
|
224
|
+
Post, // Final model
|
|
225
|
+
User, // Intermediate model
|
|
226
|
+
'country_id', // FK on User
|
|
227
|
+
'user_id', // FK on Post
|
|
228
|
+
'id', // Local key on Country
|
|
229
|
+
'id' // Local key on User
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Get all posts from French users
|
|
235
|
+
const france = await Country.with('posts').where('name', 'France').first();
|
|
236
|
+
console.log(france.relationships.posts);
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Has One Through
|
|
242
|
+
|
|
243
|
+
One-to-one via intermediate.
|
|
244
|
+
|
|
245
|
+
```javascript
|
|
246
|
+
class Mechanic extends Model {
|
|
247
|
+
static table = 'mechanics';
|
|
248
|
+
|
|
249
|
+
carOwner() {
|
|
250
|
+
return this.hasOneThrough(
|
|
251
|
+
Owner,
|
|
252
|
+
Car,
|
|
253
|
+
'mechanic_id',
|
|
254
|
+
'car_id',
|
|
255
|
+
'id',
|
|
256
|
+
'id'
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## Polymorphic Relations
|
|
265
|
+
|
|
266
|
+
### Morph One (Polymorphic One-to-One)
|
|
267
|
+
|
|
268
|
+
An image can belong to a User or a Post.
|
|
269
|
+
|
|
270
|
+
```sql
|
|
271
|
+
images (id, url, imageable_type, imageable_id)
|
|
272
|
+
-- imageable_type: 'User' or 'Post'
|
|
273
|
+
-- imageable_id: corresponding ID
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
```javascript
|
|
277
|
+
// Configure morph map
|
|
278
|
+
Model.setMorphMap({
|
|
279
|
+
'User': User,
|
|
280
|
+
'Post': Post
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
class User extends Model {
|
|
284
|
+
static table = 'users';
|
|
285
|
+
|
|
286
|
+
image() {
|
|
287
|
+
return this.morphOne(Image, 'imageable');
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
class Post extends Model {
|
|
292
|
+
static table = 'posts';
|
|
293
|
+
|
|
294
|
+
image() {
|
|
295
|
+
return this.morphOne(Image, 'imageable');
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
class Image extends Model {
|
|
300
|
+
static table = 'images';
|
|
301
|
+
|
|
302
|
+
imageable() {
|
|
303
|
+
return this.morphTo('imageable');
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Usage
|
|
308
|
+
const user = await User.with('image').find(1);
|
|
309
|
+
console.log(user.relationships.image);
|
|
310
|
+
|
|
311
|
+
const image = await Image.with('imageable').find(1);
|
|
312
|
+
console.log(image.relationships.imageable); // User or Post
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Morph Many (Polymorphic One-to-Many)
|
|
316
|
+
|
|
317
|
+
Comments on Posts and Videos.
|
|
318
|
+
|
|
319
|
+
```javascript
|
|
320
|
+
Model.setMorphMap({
|
|
321
|
+
'posts': Post,
|
|
322
|
+
'videos': Video
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
class Post extends Model {
|
|
326
|
+
static table = 'posts';
|
|
327
|
+
|
|
328
|
+
comments() {
|
|
329
|
+
return this.morphMany(Comment, 'commentable');
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
class Video extends Model {
|
|
334
|
+
static table = 'videos';
|
|
335
|
+
|
|
336
|
+
comments() {
|
|
337
|
+
return this.morphMany(Comment, 'commentable');
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
class Comment extends Model {
|
|
342
|
+
static table = 'comments';
|
|
343
|
+
|
|
344
|
+
commentable() {
|
|
345
|
+
return this.morphTo('commentable');
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Usage
|
|
350
|
+
const post = await Post.with('comments').find(1);
|
|
351
|
+
console.log(post.relationships.comments);
|
|
352
|
+
|
|
353
|
+
const comment = await Comment.with('commentable').find(1);
|
|
354
|
+
console.log(comment.relationships.commentable); // Post or Video
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## Eager Loading
|
|
360
|
+
|
|
361
|
+
### Basic Eager Loading
|
|
362
|
+
|
|
363
|
+
```javascript
|
|
364
|
+
// Single relation
|
|
365
|
+
const users = await User.with('posts').get();
|
|
366
|
+
|
|
367
|
+
// Multiple relationships
|
|
368
|
+
const users = await User.with('posts', 'profile', 'roles').get();
|
|
369
|
+
|
|
370
|
+
// Access loaded relationships
|
|
371
|
+
users.forEach(user => {
|
|
372
|
+
console.log(user.relationships.posts);
|
|
373
|
+
console.log(user.relationships.profile);
|
|
374
|
+
console.log(user.relationships.roles);
|
|
375
|
+
});
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### Nested Relations (Dot Notation)
|
|
379
|
+
|
|
380
|
+
```javascript
|
|
381
|
+
// Load nested relationships
|
|
382
|
+
const users = await User.with('posts.comments.author').get();
|
|
383
|
+
|
|
384
|
+
// Combined
|
|
385
|
+
const users = await User.with('profile', 'posts.comments').get();
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### Eager Loading with Constraints
|
|
389
|
+
|
|
390
|
+
```javascript
|
|
391
|
+
const users = await User.with({
|
|
392
|
+
posts: (query) => query
|
|
393
|
+
.where('status', 'published')
|
|
394
|
+
.orderBy('created_at', 'desc')
|
|
395
|
+
.limit(5)
|
|
396
|
+
}).get();
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Load on Existing Instance
|
|
400
|
+
|
|
401
|
+
```javascript
|
|
402
|
+
const user = await User.find(1);
|
|
403
|
+
|
|
404
|
+
// Load single relation
|
|
405
|
+
await user.load('posts');
|
|
406
|
+
|
|
407
|
+
// Load multiple
|
|
408
|
+
await user.load('posts', 'profile');
|
|
409
|
+
await user.load(['roles', 'posts.comments']);
|
|
410
|
+
|
|
411
|
+
// Access
|
|
412
|
+
console.log(user.relationships.posts);
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
## Relational Filters
|
|
418
|
+
|
|
419
|
+
### whereHas (Filter by Relation)
|
|
420
|
+
|
|
421
|
+
Get users that have at least one published post:
|
|
422
|
+
|
|
423
|
+
```javascript
|
|
424
|
+
const authors = await User
|
|
425
|
+
.whereHas('posts', (query) => {
|
|
426
|
+
query.where('status', 'published');
|
|
427
|
+
})
|
|
428
|
+
.get();
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### has (Relation Count Filter)
|
|
432
|
+
|
|
433
|
+
```javascript
|
|
434
|
+
// Users with at least 1 post
|
|
435
|
+
const withPosts = await User.has('posts').get();
|
|
436
|
+
|
|
437
|
+
// Users with at least 10 posts
|
|
438
|
+
const prolific = await User.has('posts', '>=', 10).get();
|
|
439
|
+
|
|
440
|
+
// Users with exactly 5 posts
|
|
441
|
+
const exact = await User.has('posts', '=', 5).get();
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### whereDoesntHave (No Relation)
|
|
445
|
+
|
|
446
|
+
```javascript
|
|
447
|
+
// Users without any posts
|
|
448
|
+
const noPosts = await User.whereDoesntHave('posts').get();
|
|
449
|
+
|
|
450
|
+
// Users without published posts
|
|
451
|
+
const noPublished = await User
|
|
452
|
+
.whereDoesntHave('posts', (q) => q.where('status', 'published'))
|
|
453
|
+
.get();
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### withCount (Relation Count)
|
|
457
|
+
|
|
458
|
+
```javascript
|
|
459
|
+
const users = await User.withCount('posts').get();
|
|
460
|
+
|
|
461
|
+
users.forEach(user => {
|
|
462
|
+
console.log(user.getAttribute('posts_count'));
|
|
463
|
+
});
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
---
|
|
467
|
+
|
|
468
|
+
## Automatic Relations Detection
|
|
469
|
+
|
|
470
|
+
The`outlet-convert`CLI automatically detects relationships from your SQL schema.
|
|
471
|
+
|
|
472
|
+
### Detection Rules
|
|
473
|
+
|
|
474
|
+
| Pattern | Detected Relation |
|
|
475
|
+
|---------|-------------------|
|
|
476
|
+
| Column`*_id`with FK |`belongsTo()`|
|
|
477
|
+
| FK referencing this table (non-unique) |`hasMany()`|
|
|
478
|
+
| FK referencing this table (UNIQUE) |`hasOne()`|
|
|
479
|
+
| Pivot table (2 FKs only) |`belongsToMany()`|
|
|
480
|
+
| Self-referencing FK | Recursive relation |
|
|
481
|
+
|
|
482
|
+
### Example: Auto-Generated
|
|
483
|
+
|
|
484
|
+
**SQL:**
|
|
485
|
+
```sql
|
|
486
|
+
CREATE TABLE profiles (
|
|
487
|
+
id INT PRIMARY KEY,
|
|
488
|
+
user_id INT UNIQUE, -- UNIQUE = hasOne
|
|
489
|
+
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
490
|
+
);
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
**Generated User.js:**
|
|
494
|
+
```javascript
|
|
495
|
+
class User extends Model {
|
|
496
|
+
profile() {
|
|
497
|
+
return this.hasOne(Profile, 'user_id'); // Detected from UNIQUE
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### Recursive Relations
|
|
503
|
+
|
|
504
|
+
```sql
|
|
505
|
+
CREATE TABLE categories (
|
|
506
|
+
id INT PRIMARY KEY,
|
|
507
|
+
parent_id INT,
|
|
508
|
+
FOREIGN KEY (parent_id) REFERENCES categories(id)
|
|
509
|
+
);
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
**Auto-Generated:**
|
|
513
|
+
```javascript
|
|
514
|
+
class Category extends Model {
|
|
515
|
+
parent() {
|
|
516
|
+
return this.belongsTo(Category, 'parent_id');
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
children() {
|
|
520
|
+
return this.hasMany(Category, 'parent_id');
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
---
|
|
526
|
+
|
|
527
|
+
## Relations Methods Summary
|
|
528
|
+
|
|
529
|
+
| Method | Description |
|
|
530
|
+
|--------|-------------|
|
|
531
|
+
|`hasOne(Model, fk, lk)`| One-to-One |
|
|
532
|
+
|`hasMany(Model, fk, lk)`| One-to-Many |
|
|
533
|
+
|`belongsTo(Model, fk, ok)`| Inverse relation |
|
|
534
|
+
|`belongsToMany(Model, pivot, fk, rk)`| Many-to-Many |
|
|
535
|
+
|`hasManyThrough(Model, Through, fk1, fk2)`| Via intermediate |
|
|
536
|
+
|`hasOneThrough(Model, Through, fk1, fk2)`| One via intermediate |
|
|
537
|
+
|`morphOne(Model, name)`| Polymorphic One-to-One |
|
|
538
|
+
|`morphMany(Model, name)`| Polymorphic One-to-Many |
|
|
539
|
+
|`morphTo(name)`| Polymorphic inverse |
|
|
540
|
+
|`with(...relationships)`| Eager load |
|
|
541
|
+
|`load(...relationships)`| Load on instance |
|
|
542
|
+
|`whereHas(rel, cb)`| Filter by relation |
|
|
543
|
+
|`has(rel, op, count)`| Relation count filter |
|
|
544
|
+
|`whereDoesntHave(rel)`| Filter by no relation |
|
|
545
|
+
|`withCount(rel)`| Add relation count |
|
|
546
|
+
|`attach(ids)`| Attach (many-to-many) |
|
|
547
|
+
|`detach(ids?)`| Detach (many-to-many) |
|
|
548
|
+
|`sync(ids)`| Sync (many-to-many) |
|
|
549
|
+
|
|
550
|
+
---
|
|
551
|
+
|
|
552
|
+
## Next Steps
|
|
553
|
+
|
|
554
|
+
- [Migrations & Schema Builder →](MIGRATIONS.md)
|
|
555
|
+
- [Advanced Features →](ADVANCED.md)
|