-
-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
MorphMany #111
Open
ryandialpad
wants to merge
23
commits into
master
Choose a base branch
from
morph-many
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
MorphMany #111
Changes from 7 commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
8bec10a
Init MorphMany
ryandialpad 2f75dcd
Adding MorphMany tests, removing TODOs from tested MorphMany methods.
ryandialpad 1bba446
Removing TODO from tested method in MorphMany
ryandialpad e774cbe
Fixing a typo
ryandialpad 03160be
Moving fill state into before all block
ryandialpad d6dd6c6
Correcting a typo
ryandialpad 41aa1a8
Touching up some test descriptions
ryandialpad cafc529
Addressing PR comments
ryandialpad 98d366f
Merge branch 'master' into morph-many
ryandialpad 7d7c50d
Refactoring MorphMany match method to use query instead of results
ryandialpad 6eb7366
Init MorphMany
ryandialpad f3b449d
Adding MorphMany tests, removing TODOs from tested MorphMany methods.
ryandialpad f13c298
Removing TODO from tested method in MorphMany
ryandialpad 3defb88
Fixing a typo
ryandialpad b83e961
Moving fill state into before all block
ryandialpad de213c4
Correcting a typo
ryandialpad ae8453e
Touching up some test descriptions
ryandialpad ea25ea6
Addressing PR comments
ryandialpad 8afbfd8
Refactoring MorphMany match method to use query instead of results
ryandialpad 739dd38
Merge branch 'morph-many' of github.com:vuex-orm/vuex-orm-next into m…
ryandialpad ad65710
Resolving some duplicate code after resolving conflicts
ryandialpad 7e5f0fe
Removing another duplicated import
ryandialpad f16c621
Fixing up some comment headers
ryandialpad File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import { Schema as NormalizrSchema } from 'normalizr' | ||
import { Schema } from '../../../schema/Schema' | ||
import { Element, Collection } from '../../../data/Data' | ||
import { Query } from '../../../query/Query' | ||
import { Model } from '../../Model' | ||
import { Relation, Dictionary } from './Relation' | ||
|
||
export class MorphMany extends Relation { | ||
/** | ||
* The field name that contains id of the parent model. | ||
*/ | ||
protected morphId: string | ||
|
||
/** | ||
* The field name that contains type of the parent model. | ||
*/ | ||
protected morphType: string | ||
|
||
/** | ||
* The local key of the model. | ||
*/ | ||
protected localKey: string | ||
|
||
/** | ||
* Create a new morph-many relation instance. | ||
*/ | ||
constructor( | ||
parent: Model, | ||
related: Model, | ||
morphId: string, | ||
morphType: string, | ||
localKey: string | ||
) { | ||
super(parent, related) | ||
this.morphId = morphId | ||
this.morphType = morphType | ||
this.localKey = localKey | ||
} | ||
|
||
/** | ||
* Get all related models for the relationship. | ||
*/ | ||
getRelateds(): Model[] { | ||
return [this.related] | ||
} | ||
|
||
/** | ||
* Define the normalizr schema for the relation. | ||
*/ | ||
define(schema: Schema): NormalizrSchema { | ||
return schema.many(this.related, this.parent) | ||
} | ||
|
||
/** | ||
* Attach the parent type and id to the given relation. | ||
*/ | ||
attach(record: Element, child: Element): void { | ||
child[this.morphId] = record[this.localKey] | ||
child[this.morphType] = this.parent.$entity() | ||
} | ||
|
||
/** | ||
* Set the constraints for an eager load of the relation. | ||
*/ | ||
addEagerConstraints(query: Query, models: Collection): void { | ||
query.where(this.morphType, this.parent.$entity()) | ||
query.whereIn(this.morphId, this.getKeys(models, this.localKey)) | ||
} | ||
|
||
/** | ||
* Match the eagerly loaded results to their parents. | ||
*/ | ||
match(relation: string, models: Collection, results: Collection): void { | ||
const dictionary = this.buildDictionary(results) | ||
|
||
models.forEach((model) => { | ||
const key = model[this.localKey] | ||
|
||
dictionary[key] | ||
? model.$setRelation(relation, dictionary[key]) | ||
: model.$setRelation(relation, []) | ||
}) | ||
} | ||
|
||
/** | ||
* Build model dictionary keyed by the relation's foreign key. | ||
*/ | ||
protected buildDictionary(results: Collection): Dictionary { | ||
return this.mapToDictionary(results, (result) => { | ||
return [result[this.morphId], result] | ||
}) | ||
} | ||
|
||
/** | ||
* Make related models. | ||
*/ | ||
make(elements?: Element[]): Model[] { | ||
return elements | ||
? elements.map((element) => this.related.$newInstance(element)) | ||
: [] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { Model } from '../../../Model' | ||
import { PropertyDecorator } from '../../Contracts' | ||
|
||
/** | ||
* Create a morph-many attribute property decorator. | ||
*/ | ||
export function MorphMany( | ||
related: () => typeof Model, | ||
id: string, | ||
type: string, | ||
localKey?: string | ||
): PropertyDecorator { | ||
return (target, propertyKey) => { | ||
const self = target.$self() | ||
|
||
self.setRegistry(propertyKey, () => | ||
self.morphMany(related(), id, type, localKey) | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
import { | ||
createStore, | ||
fillState, | ||
assertModel, | ||
assertInstanceOf | ||
} from 'test/Helpers' | ||
import { Model, Attr, Str, Num, MorphMany } from '@/index' | ||
|
||
describe('feature/relations/morph_many_retrieve', () => { | ||
class Comment extends Model { | ||
static entity = 'comments' | ||
|
||
@Num(0) id!: number | ||
@Str('') body!: string | ||
@Attr(null) commentableId!: number | null | ||
@Attr(null) commentableType!: string | null | ||
} | ||
|
||
class Video extends Model { | ||
static entity = 'videos' | ||
|
||
@Num(0) id!: number | ||
@Str('') link!: string | ||
|
||
@MorphMany(() => Comment, 'commentableId', 'commentableType') | ||
comments!: Comment[] | ||
} | ||
|
||
class Post extends Model { | ||
static entity = 'posts' | ||
|
||
@Num(0) id!: number | ||
@Str('') title!: string | ||
@MorphMany(() => Comment, 'commentableId', 'commentableType') | ||
comments!: Comment[] | ||
} | ||
|
||
const ENTITIES = { | ||
videos: { 1: { id: 1, link: '/video.mp4' } }, | ||
posts: { | ||
1: { id: 1, title: 'Hello, world!' }, | ||
2: { id: 2, title: 'Hello, world! Again!' } | ||
}, | ||
comments: { | ||
1: { | ||
id: 1, | ||
body: 'Cool Video!', | ||
commentableId: 1, | ||
commentableType: 'videos' | ||
}, | ||
2: { | ||
id: 2, | ||
body: 'Cool Video Again!', | ||
commentableId: 1, | ||
commentableType: 'videos' | ||
}, | ||
3: { | ||
id: 3, | ||
body: 'Cool Post!', | ||
commentableId: 1, | ||
commentableType: 'posts' | ||
}, | ||
4: { | ||
id: 4, | ||
body: 'Cool Post 2!', | ||
commentableId: 2, | ||
commentableType: 'posts' | ||
} | ||
} | ||
} | ||
|
||
describe('when there are comments', () => { | ||
const store = createStore() | ||
|
||
beforeAll(() => { | ||
fillState(store, ENTITIES) | ||
}) | ||
|
||
it('can eager load morph many relation for video', () => { | ||
const video = store.$repo(Video).with('comments').first()! | ||
|
||
expect(video).toBeInstanceOf(Video) | ||
assertInstanceOf(video.comments, Comment) | ||
assertModel(video, { | ||
id: 1, | ||
link: '/video.mp4', | ||
comments: [ | ||
{ | ||
id: 1, | ||
body: 'Cool Video!', | ||
commentableId: 1, | ||
commentableType: 'videos' | ||
}, | ||
{ | ||
id: 2, | ||
body: 'Cool Video Again!', | ||
commentableId: 1, | ||
commentableType: 'videos' | ||
} | ||
] | ||
}) | ||
}) | ||
|
||
it('can eager load morph many relation for post', () => { | ||
const post = store.$repo(Post).with('comments').first()! | ||
|
||
expect(post).toBeInstanceOf(Post) | ||
assertInstanceOf(post.comments, Comment) | ||
assertModel(post, { | ||
id: 1, | ||
title: 'Hello, world!', | ||
comments: [ | ||
{ | ||
id: 3, | ||
body: 'Cool Post!', | ||
commentableId: 1, | ||
commentableType: 'posts' | ||
} | ||
] | ||
}) | ||
}) | ||
}) | ||
|
||
describe('when there are no comments', () => { | ||
const store = createStore() | ||
|
||
beforeAll(() => { | ||
fillState(store, { | ||
videos: { | ||
1: { id: 1, link: '/video.mp4' } | ||
}, | ||
posts: {}, | ||
comments: {} | ||
}) | ||
}) | ||
|
||
it('can eager load missing relation as empty array', () => { | ||
const video = store.$repo(Video).with('comments').first()! | ||
|
||
expect(video).toBeInstanceOf(Video) | ||
assertModel(video, { | ||
id: 1, | ||
link: '/video.mp4', | ||
comments: [] | ||
}) | ||
}) | ||
}) | ||
|
||
it('can revive "morph many" relations', () => { | ||
const store = createStore() | ||
|
||
fillState(store, { | ||
videos: { | ||
1: { id: 1, link: '/video.mp4' } | ||
}, | ||
comments: { | ||
1: { | ||
id: 1, | ||
commentableId: 1, | ||
commentableType: 'videos', | ||
body: 'Cool Video!' | ||
}, | ||
2: { | ||
id: 2, | ||
commentableId: 1, | ||
commentableType: 'videos', | ||
body: 'Cool Video Again!' | ||
} | ||
} | ||
}) | ||
|
||
const schema = { | ||
id: '1', | ||
comments: [{ id: 2 }, { id: 1 }] | ||
} | ||
|
||
const video = store.$repo(Video).revive(schema)! | ||
|
||
expect(video.comments.length).toBe(2) | ||
cuebit marked this conversation as resolved.
Show resolved
Hide resolved
|
||
expect(video.comments[0]).toBeInstanceOf(Comment) | ||
expect(video.comments[1]).toBeInstanceOf(Comment) | ||
cuebit marked this conversation as resolved.
Show resolved
Hide resolved
|
||
expect(video.comments[0].id).toBe(2) | ||
expect(video.comments[1].id).toBe(1) | ||
}) | ||
}) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am noticing a fair amount of code duplication, at some point we might want to DRY these up once the library is feature complete (1.x).