Last active
March 4, 2022 23:03
-
-
Save devinhalladay/72704afb4fcfaade478b4f8977637925 to your computer and use it in GitHub Desktop.
Valiating slugs of child documents in parent→child relations for Sanity Studio
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
import ComputedField from 'sanity-plugin-computed-field'; | |
import { validateLessonSlugUniqueness } from '../../utils/validateLessonSlugUniquness'; | |
import { getPublishedId } from '../../utils/documentPublishStatus'; | |
export default { | |
name: 'lesson', | |
title: 'Lesson', | |
type: 'document', | |
fields: [ | |
/** | |
* This field uses the computed field plugin to compute the most recently | |
* published course that this lesson belongs to. | |
* Right now, course-lesson relationships are one-to-one. | |
*/ | |
{ | |
title: 'Parent Course', | |
name: 'referringCourse', | |
description: | |
'The course this lesson belongs to. A lesson can be associated with one course at a time.', | |
type: 'reference', | |
to: [{ type: 'course' }], | |
weak: true, | |
inputComponent: ComputedField, | |
options: { | |
buttonText: 'Re-link lesson', | |
documentQuerySelection: `// groq | |
"course": *[_type == "course" && references(^._id)][0]`, | |
reduceQueryResult: (queryResult) => { | |
if (queryResult.course) { | |
/** | |
* Set a reference to the implied parent course. | |
*/ | |
return { | |
_type: 'reference', | |
_ref: getPublishedId(queryResult.course._id), | |
}; | |
} | |
return null; | |
}, | |
}, | |
}, | |
/** | |
* Here we will use our custom uniquness function to ensure that the slug | |
* is unique within the course. | |
*/ | |
{ | |
name: 'slug', | |
type: 'slug', | |
options: { source: 'title', isUnique: validateLessonSlugUniqueness }, | |
}, | |
/** | |
* If you are using the generalized version of the function validateNestedSlugUniqueness, | |
* you can use the following to validate uniqueness. | |
{ | |
name: 'slug', | |
type: 'slug', | |
options: { | |
source: 'title', | |
isUnique: async (value, context) => { | |
const isUnique = await validateNestedSlugUniqueness( | |
value, | |
context, | |
'referringCourse', | |
); | |
return isUnique; | |
}, | |
}, | |
}, | |
*/ | |
], | |
}; |
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 is specific to my schema, and validates the uniqueness of slugs for lessons with the same "parent" course. | |
* In our schema, the parent course is accessed via a calculated `referringCourse` field, but this could also be | |
* done by using the `references()` function in groq. | |
*/ | |
import { getPublishedId } from './documentPublishStatus'; | |
import { SanityDocument } from '@sanity/client'; | |
import client from '../sanityClient'; | |
interface ValidationContext { | |
/** | |
* The field's parent object. This might be the document. | |
*/ | |
parent: Record<string, any>; | |
/** | |
* The parent document | |
*/ | |
document: SanityDocument; | |
} | |
/** | |
* Validates that the slug is unique within the current course. | |
* | |
* @param value The slug to validate. | |
* @param context The context of the slug field. | |
* @returns A promise that resolves to true if the slug is unique, false otherwise. | |
*/ | |
export const validateLessonSlugUniqueness = async ( | |
value: string | null | undefined, | |
context: ValidationContext, | |
): Promise<boolean> => { | |
const slug = value; | |
const currentParentCourse = context.document?.referringCourse; | |
/** If there is no parent course, check against all lesson documents. */ | |
if (!currentParentCourse) { | |
return client.fetch( | |
`*[(_type == "lesson") && !(_id in path("drafts.**"))]`, | |
); | |
} | |
/** Fetch all other lessons with the same slug, within the same course. */ | |
const lessonsWithSameSlug = await client.fetch( | |
`*[ | |
(_type == "lesson") | |
&& !(_id in path('drafts.**')) | |
&& _id != $currentId | |
&& slug.current == $slug | |
&& referringCourse._ref == $parentCourseId | |
]._id | |
`, | |
{ | |
slug, | |
currentId: getPublishedId(context.document._id), | |
parentCourseId: currentParentCourse._ref, | |
}, | |
); | |
return !lessonsWithSameSlug.length; | |
}; |
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 is more generalized for any schema. It still relies on a field on the child document which | |
* references the parent — in my case this is calculated with sanity-plugin-computed-field. | |
* However, if this does not fit your needs, you can simply get the parent course using the `references()` function in groq. | |
*/ | |
interface ValidationContext { | |
/** | |
* The field's parent object. This might be the document. | |
*/ | |
parent: Record<string, any>; | |
/** | |
* The parent document | |
*/ | |
document: SanityDocument; | |
} | |
/** | |
* Validates that the slug is unique within a parent document, such as lessons belonging to a course. | |
* | |
* @param value The slug to validate. | |
* @param context The context of the slug field. | |
* @returns A promise that resolves to true if the slug is unique, false otherwise. | |
*/ | |
export const validateNestedSlugUniqueness = async ( | |
value: string | null | undefined, | |
context: ValidationContext, | |
parentReferenceField: string, | |
): Promise<boolean> => { | |
const slug = value; | |
const currentParentDocument = context.document[parentReferenceField]; | |
/** If there is no parent document referenced, check against all documents of the current type. */ | |
if (!currentParentDocument) { | |
const allDocumentsWithSameSlug = client.fetch( | |
`*[(_type == $childType) && !(_id in path("drafts.**"))]`, | |
{ | |
childType: context.document._type, | |
}, | |
); | |
return !allDocumentsWithSameSlug.length; | |
} | |
/** Fetch all other documents of the current type with the same slug, with the same parent document reference. */ | |
const childDocumentsWithSameSlug = await client.fetch( | |
`*[ | |
(_type == $childType) | |
&& !(_id in path('drafts.**')) | |
&& _id != $currentChildId | |
&& slug.current == $slug | |
&& @[parentReferenceField]->._id == $parentDocumentId | |
]._id | |
`, | |
{ | |
slug, | |
currentChildId: getPublishedId(context.document._id), | |
parentDocumentId: currentParentDocument._ref, | |
childType: context.document._type, | |
parentReferenceField: 'referringCourse', | |
}, | |
); | |
return !childDocumentsWithSameSlug.length; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment