I'm using prisma2, and I don't know how to delete items having relations with other models.
This is my models.
model User {
id String #default(cuid()) #id
email String #unique
password String
name String
teams Team[]
memberships Membership[]
}
model Team {
id String #default(cuid()) #id
name String
founder User?
memberships Membership[]
}
model Membership {
id String #default(cuid()) #id
class String
owner User
team Team
}
User-Team is 1:n relationship.
Team-Membership is 1:n relationship.
And I wanna delete a Team.
I've tried this.
t.list.field("deleteTeam", {
type: "Team",
args: {
teamid: idArg()
},
resolve: (_, { teamid }, ctx) => {
return ctx.photon.teams.deleteMany({
where: { id: teamid }
});
}
});
But it doensn't work because it violates relation.
How can I delete team with disconnecting all the relations at the same time?
Deletes that have dependent relationships usually require that a cascading delete be specified.
Based on your model, I believe you need to update your graphql schemas to handle either CASCADE on SET_NULL for relations onDelete.
I know that in other systems like 8base there is a force: Boolean flag that can be specified solve this. However, here is the Prisma docs section for your problem: https://prisma-docs.netlify.com/docs/1.4/reference/prisma-api/concepts-utee3eiquo/#cascading-deletes
Related
Let's say we have two entities.
users that has uuid, name and age
users_books that has user_uuid, book_id and recommended_age.
user_uuid was added as foreign key pointing to uuid in Users
Using user name I want to get all the books that that user reads and have recommended age equal to users age.
Following query will get me all the books that user reads
query getUserBooks($uuid: uuid!) {
users_by_pk(uuid: $uuid) {
uuid
name
age
users_books() {
book_id
recommended_age
}
}
}
And this is the query I am trying to create:
query getUserBooksWithRestrictedAge($uuid: uuid!) {
users_by_pk(uuid: $uuid) {
uuid
name
age
users_books(where:{recommended_age:{_eq: *WHAT_SHOULD_GO_HERE?*}}) {
book_id
recommended_age
}
}
}
Is this even possible?
Hasura supports columns comparison operators only when setting permissions. To accomplish what you need you have to create a view.
CREATE OR REPLACE VIEW users_books_by_age AS
SELECT * from users_books
Set the relationships between the view, user and books table. In the Hasura view permission builder filter the age comparing the columns. Something like
{
user: {
age: {
_ceq: recommended_age
}
}
}
This way you can run your query like:
query getUserBooks($uuid: uuid!) {
users_by_pk(uuid: $uuid) {
uuid
name
age
users_books_by_age {
book_id
recommended_age
}
}
}
Another solution would be to create a computed field. Something like:
CREATE FUNCTION author_full_name(user_row user)
RETURNS SETOF users_books AS $$
SELECT * FROM users_books where recommended_age = user_row.age
$$ LANGUAGE sql STABLE;
I hope that helps.
This is more a design question than a coding question. Suppose the following schema:
// schema.prisma
// Solution 1
model Entity {
id Int #id #default(autoincrement())
attrs EntityAttr[]
}
model EntityAttr {
id Int #id #default(autoincrement())
value Json // or String, doesnt matter much here
// the point is I need to attach info on the
// join table of this relation
attr Attr #relation(fields: [attrId], references: [id])
entity Entity #relation(fields: [entityId], references: [id])
entityId Int
attrId Int
##unique([entityId, attrId])
}
model Attr {
id Int #id #default(autoincrement())
entities EntityAttr[]
}
// Solution 2
model Entity {
id Int #id #default(autoincrement())
dateAttrs DateAttr[]
recordAttrs RecordAttr[]
// ... this pattern could continue for more Attr-like models
}
model DateAttr {
id Int #id #default(autoincrement())
name String
entity Entity #relation(fields: [entityId], references: [id])
value DateTime // Stronger typing in generated code
}
model RecordAttr {
// ... define another Entity #relation(...)
name String
value String
// ...
}
// ... and so on
Please note that the schema might not be 100% complete or accurate. It is mainly to get the point across.
Solution 1 has its merits where redundancy and the number of tables in the database is reduced significantly (depending on the number of Attrs). Its downfall comes as confusing queries*, possible case-specific type casting and no code-completion for the value field for each Attr-like model.
* by confusing, I mean that the option for simplified m-n queries in prisma is functionally disabled when using a custom join table (e.g. EntityAttr)
Solution 2 has its merits where the generated code results in more strongly typed code generation for the value field, however it falls in the number of generated tables (I don't actually know if more tables is a good thing or a bad thing, all I think is that if you have similar values, they ought to be in the same table).
What would you do in my shoes?
I was looking pretty long for an appropriate answer and found it here.
I'm not sure if it could be applied to your question, but this is question about prisma and polymorphism, so I think this code snippet might be useful for developers:
model Photo {
id Int #id #default(autoincrement())
likes Like[] #relation("PhotoLike")
}
model Video {
id Int #id #default(autoincrement())
likes Like[] #relation("VideoLike")
}
enum LikableType {
Photo
Video
}
model Like {
id Int #id #default(autoincrement())
Photo Photo? #relation("PhotoLike", fields: [likableId], references: [id], map: "photo_likableId")
Video Video? #relation("VideoLike", fields: [likableId], references: [id], map: "video_likableId")
likableId Int
likableType LikableType
}
Resuling relations in dbdocs:
Sometimes the use case can't be generalized to abstract and have a typing's.
if you control them and has a limited attribute sure you can create each attribute as a separate table each has it is own schema.
Some Times more freedom is needed or the blocks are dynamic.
Use Case: Build A Block Document Editor Like 'notion.so' and you want to let the user create custom blocks or configure them.
you can do it like :
model Document {
id String #id
blocks Block[]
}
model Block {
id String #id
value Json
index Int
customConfig Json?
document Document? #relation(fields: [documentID], references: [id])
documentID String?
blockType BlockType #relation(fields: [blockTypeID], references: [id])
blockTypeID String
}
model BlockType {
id String #id
name String
config Json
blocks Block[]
}
where config and custom config can contains html,custom css classes, link attribute color or anything.
using type script you can create block.types.ts and add different let say templates for the config's .
I hope that I was useful to you, To sum it, it depends on the requirements :>)
Imagine two tables:
People {
id: uuid
home_id: uuid
}
Homes {
id: uuid
}
Table People is already populated. What I would like to do, is to insert a new Home and update home_id field in the People table at the same time. Is this possible?
I ended up solving this by creating a HomeOwnership table { person_id: uuid, home_id: uuid } and removing home_id from the People table. Then I established foreign key relationships between the tables. Then the mutation would look like following:
mutation MyMutation($person_id: uuid = "some_id") {
insert_home_one(object: {home_membership: {data: {person_id: $person_id}}}) {
id
home_membership{
person{
name
}
}
}
}
My data model includes the following nodes:
model User {
id Int #id #default(autoincrement())
name String
posts Post[]
}
model Post {
id Int #id #default(autoincrement())
body String
user User #relation(fields: [userId], references: [id])
userId Int
}
I tried to delete one User like this:
async function deleteUser(_, args) {
const { id } = args
return prisma.user.delete({
where: { id: id }
})
}
But it gives an error: ... The change you are trying to make would violate the required relation UserToPost between the User and Post models.
Then how to delete one user? Even I tried to delete the post first then the user but again same error happened.
This has now been released as a preview feature behind a preview feature flag. You can read about it in the release notes for 2.26.0: https://github.com/prisma/prisma/releases/tag/2.26.0
The preview feature can be enabled by setting the preview feature flag referentialActions in the generator block of Prisma Client in your Prisma schema file:
generator client {
provider = "prisma-client-js"
previewFeatures = ["referentialActions"]
}
Looks like your table does not support CASCADE deletions and prisma does not automatically add it for you. You will have to manually update the definition of your table either while migration or after the fact.
so basically, alter your table definition.
ALTER TABLE public.Post
DROP CONSTRAINT Post_user_fkey,
ADD CONSTRAINT Post_user_fkey
FOREIGN KEY (user)
REFERENCES public.User(user)
ON DELETE CASCADE
ON UPDATE CASCADE;
Refer to these docs on options of configuring relational queries.
I have a platform that enables users to download sound clips. Producers of the sound clips can see their history of downloads, but they can also delete files they don't want available for download anymore. Each download event has a DB record with a polymorphic relationship to two different tables, depending on the sound clip type.
I'm trying to put together a GraphQL query that will return the download statistics within a selected date range, including download stats of soft deleted files. The Laravel query is returning the soft deleted records but the soft deleted records aren't available on the graphQL side.
Laravel Download Model (each download event creates a record here) - it could be of a BirdSound or a HumanSound record:
class Download extends BaseModel
{
protected $types = [
'bird' => BirdSound::class,
'human' => HumanSound::class,
];
/**
* #return MorphTo|EloquentBuilder|QueryBuilder
*/
public function downloadable() : MorphTo
{
return $this->morphTo();
}
}
Laravel BirdSound Model (there is an equivalent HumanSound model). There is one record in this table for each file available for download:
class BirdSounds extends Sounds
{
use SoftDeletes;
/**
* #return MorphMany|EloquentBuilder|QueryBuilder
*/
public function Download(): MorphMany
{
return $this->morphMany(Download::class, 'downloadable');
}
}
GraphQL Schema:
type Download {
id: ID!
name: String!
email: String!
downloadable: Downloadable #morphTo
}
interface Downloadable {
id: ID!
name: String
freeDownloads: [Download!] #morphMany
}
type BirdSound implements Downloadable {
id: ID!
name: String
duration: String
user: User #belongsTo(relation: "user")
filename: String
freeDownloads: [Download] #morphMany
}
type HumanSound implements Downloadable {
id: ID!
name: String
snippet: Int
user: User #belongsTo(relation: "user")
artwork_id: Int
freeDownloads: [Download] #morphMany
}
# Using DownloadCount type to handle the data returned by the laravel function that counts the downloads
type DownloadCount {
downloadable_id: Int
downloadable_type: String
count: Int
downloadable: MediaInfo
}
# Using MediaInfo instead of the actual `downloadable` object in order to include soft-deleted records,
# which I can't get working with the polymorphic relationship for lighthouse
type MediaInfo {
id: ID! # ID of the downloadable record
name: String # name from the BirdSound or HumanSound
}
extend type Query {
myTopDownloads(downloaded_at: DateRange)): [DownloadCount]
#field(resolver: "App\\GraphQL\\Queries\\FreeDownloads#topDownloads")
}
Laravel function that gets the data:
public function topDownloads($rootValue, array $args, GraphQLContext $context, ResolveInfo $resolveInfo)
{
return auth()->guard('graphql')->user()->freeDownloads()
->selectRaw('id, downloadable_id, downloadable_type, count(downloadable_id) as count')
->groupBy('downloadable_id')
->orderBy('count', 'desc')
->with(['downloadable' => function($query){
$query->withTrashed();
}])
->limit($limit)
->get();
}
The above Laravel query returns both the download info AND the related downloadable data, whether it's been soft deleted or not, and with the graphQL schema described above, I can access the downloadable object through the MediaInfo relation on the Download object. However, I can't figure out how to get the actual downloadable relation as defined on the models available in graphQL - the relation always shows null.
I've tried the following:
Changing the Media type:
type Media {
id: Int
downloadable_id: Int
name: String
downloadable_type: String
count: Int
downloadable: Downloadable #morphTo #softDeletes
}
Adding trashed: Trash to the query (which I assume is only single-dimensional, which is why it wouldn't work):
extend type Query {
myTopDownloads(trashed: Trash, downloaded_at: DateRange)): [Download]
#field(resolver: "App\\GraphQL\\Queries\\FreeDownloads#topDownloads")
}
...I've tried multiple variations of the above examples, all of which result in a null for the downloadable object or I get an error.
Query example:
{
myTopDownloads{
downloadable_id
downloadable_type
count
downloadable(trashed:WITH) {
__typename
id
name
}
}
}
"debugMessage": "Use #trashed only for Model classes that use the SoftDeletes trait.",
The Laravel models all use the SoftDeletes trait, and I couldn't find documentation on adding a specific trait to the graphQL type (ex: in my BirdSound type).
I assume the issue may be with the fact that it's a polymorphic relationship, but this is my first project using GraphQL and I'm still trying to wrap my head around some of the details...Any insight into this would be great!
According to lighthouse, in a polymorphic relationship, you should point to a union indicating which types it may return.
union Downloadable = HumanSound | BirdSound
https://lighthouse-php.com/master/eloquent/polymorphic-relationships.html#one-to-many
To query those types, use the notation to specify on a given type, which fields to return
{
myTopDownloads{
downloadable_id
downloadable_type
count
downloadable(trashed:WITH) {
__typename
... on BirdSound {
id
name
}
... on HumanSound {
id
name
}
}
}
}
https://graphql.org/learn/schema/#union-types
Don't forget you need to specify the relationships in the models, including the return type, and remove the implements.