Support transactions
maciejmyslinski opened this issue · comments
Hey!
Thank you for creating this outstanding library, it really helps with testing.
One use case that I stumbled upon was the need to check if an object X exists in a database before I can create another object Y that has a relationship many to one with object X.
For example:
factory({
reviewer: {
id: primaryKey(Number),
reporters: manyOf('reporter'),
},
reporter: {
id: primaryKey(Number),
}
})
Let's say I want to create a reporter for a given reviewer. First, I have to check if a reviewer exists, then I can create a reporter, then I can assign a reporter to a reviewer. Otherwise, if the reviewer would not exist, the code would fail on the last step and the reviewer would hang in the database unnecessarily.
In contract, with transacions, if it fails at any step, all operations would be rolled back.
Hey, @maciejmyslinski. Thank you for the kind feedback!
This is a great suggestion. Let's collaborate on it together.
With the current API, any relation flow is parent -> child, so in your scenario "create a reporter for a given reviewer" implies you're operating with an existing reviewer object:
const john = db.reviewer.create({
reporters: [db.reporter.create({ firstName: 'Mary' })]
})
Even if you split this operation in steps and create a reporter first, you still need a physically existing reviewer object to assign its reporters
property to a new value:
const mary = db.reporter.create({ firstName: 'Mary' })
const john = db.reviewer.create({
reporters: [mary]
})
// or
john.update({
reporters: [mary]
})
I think I fail to see a scenario when you could assign a reporter to a potentially non-existing reviewer. Could you please share with us your usage of the library that showcases this problematic scenario?
In my use case, I have a createReporter
endpoint that takes reporter name and reviewer id as an input.
Now, reviewer with a given id might not exist - we can't trust user input. So we either:
- check if reviewer exists
- create a reporter
- update reviewer
With transactions we could skip the first step as the whole transaction would fail if reviewer does not exist.
You can describe this transaction using the lazy value getters during the reviewer update:
function createReporter(reviewerId, reporterName) {
db.reviewer.update({
which: {
id: { equals: reviewerId }
},
data: {
reporters(reporters) {
const newReporter = db.reporter.create({
name: reporterName
})
return reporters.concat([newReporter])
}
}
})
}
Note that the
data.reporters
value is a function that produces the next value only when the entity (reviewer) exists.
This works because .update()
method queries the database for the given entity first (reviewer in your case):
Lines 138 to 154 in 3016d6d
and updates it only when it exists. Since the lazy value getters are executed during the update phase, you are certain that no reporter is created if the queried reviewer doesn't exist.
data/src/model/updateEntity.ts
Lines 19 to 20 in 3016d6d
By default, the .update()
method will do nothing if the entity cannot be queried. If you wish to raise an exception in this case, use the strict mode with the .update()
method:
db.reviewer.update({
which: { ... },
data: { ... },
strict: true
})
Using the strict
method will throw an exception when querying a non-existing entity.
Would this cover your use case? If not, please provide additional context so I could understand your requirements better.