Closed eddyystop closed 2 years ago
+1 I agree with this idea.
But I would recommend a hook argument that describes the param used to determine if we are doing a create or a patch.
So, for example,
module.exports = {
before: {
all: [],
find: [],
get: [],
create: [upsertServiceRecordBykey({ updateByKey: 'someField' })], // or typically: upsertServiceRecordBykey({ updateByKey: '_id' })
update: [],
patch: [],
remove: []
},
after: {
all: [],
find: [],
get: [],
create: [],
update: [],
patch: [],
remove: []
},
error: {
all: [],
find: [],
get: [],
create: [],
update: [],
patch: [],
remove: []
}
}
This way you can tell the hook what to use in it's where clause..... even better yet, perhaps an array of naturalKeys. so a compound where cleause can detrmine if we need to update, or insert a new record.
I came to this issue while looking for a common hook that behaves this way.
I'll have a go at writing my own now, and keep you posted here
This is what I have so far:
module.exports = function (options = {}) { // eslint-disable-line no-unused-vars
return function (hook) {
// Hooks can either return nothing or a promise
// that resolves with the `hook` object for asynchronous operations
const query = {}
query[options.updateByKey] = hook.data[options.updateByKey]
return hook.app.service(options.service)
.find({ query }).then(page => {
if (page.total === 0) {
console.info(`No existing record found in service: ${options.service} with key: '${options.updateByKey}:${hook.data[options.updateByKey]}'`)
return Promise.resolve(hook)
} else {
console.info(`Existing record found in service: ${options.service} with key: '${options.updateByKey}:${hook.data[options.updateByKey]}'`)
hook.app.service(options.service).update(page.data[0]._id, hook.data)
// How do I stop the create from carrying on?
}
})
}
}
Called like so: upsertServiceRecordBykey({ service: 'stocks', updateByKey: 'code' })
But how do I stop the create from carrying on?
Makes sense @eddyystop. Ended up with this working example:
module.exports = function (options = {}) {
return function (hook) {
if (options.updateByKey && options.updateByKey) {
const query = {}
query[options.updateByKey] = hook.data[options.updateByKey]
return hook.app.service(options.service)
.find({ query }).then(page => {
if (page.total === 0) {
console.info(`No existing record found in service: ${options.service} with key: '${options.updateByKey}:${hook.data[options.updateByKey]}'`)
} else {
console.info(`Existing record found in service: ${options.service} with key: '${options.updateByKey}:${hook.data[options.updateByKey]}, updating...'`)
hook.app.service(options.service).update(page.data[0]._id, hook.data)
hook.result = page.data[0]
}
return Promise.resolve(hook)
})
} else {
return Promise.resolve(hook)
}
}
}
I'd like to improve this in two ways;
Thoughts?
find
query object somewhere in the hook that you can use in the find
.hook.app.service(...).id
returns the default or configured name of the id field.Thanks for the pointers @eddyystop !
Here is the latest implementation that determines the primary key's field name and allows for multiple lookup keys:
Hook usage:
Context, I have a service
called stocks
that has the fields: code
, name
, sector
When a stock
is posted, I only want a new record when there are no the records with the matching fields code
& name
, otherwise, an update is required.
In stock.hooks.js
before: {
all: [],
find: [],
get: [],
create: [upsertServiceRecord({ service: 'stocks', updateByKeys: ['code', 'name'] })],
update: [],
patch: [],
remove: []
},
The Hook itself:
/*
* A hook to update a record instead of creating a new one, based on an array of keys
*/
module.exports = function (options = {}) { // eslint-disable-line no-unused-vars
return function (hook) {
if (options.updateByKeys && options.updateByKeys.length > 0) {
const primaryKeyField = hook.app.service(options.service).id
const query = {}
for (const updateByKey of options.updateByKeys) {
query[updateByKey] = hook.data[updateByKey]
}
return hook.app.service(options.service)
.find({ query }).then(page => {
if (page.total === 0) {
console.info(`No existing record found in service: ${options.service} with key(s): '${options.updateByKeys.join(', ')}'`)
} else {
console.info(`Existing record found in service: ${options.service} with keys(s): '${options.updateByKeys.join(', ')}' updating...`)
hook.app.service(options.service).update(page.data[0][primaryKeyField], hook.data)
hook.result = page.data[0]
}
return Promise.resolve(hook)
})
} else {
return Promise.resolve(hook)
}
}
}
The body of a post request:
{
"code": "BHP",
"name": "BHP BILLITON LIMITED",
"sector": "Materials"
}
When the above json is posted, a new record is created. If it is posted again, and update is performed.
I'd really like to get better at writing tests for feathers apps, is there more examples/tutorials or docs to help me with this?
Once tested further, would the feathersjs team consider accepting a PR of this hook?
feathers-community-hooks exists and will be mentioned in the next monthly Feathers newsletter. We hope many useful hooks such as yours will be contributed. I hope you get yours in in time for the newsletter so readers can see it.
This upsert service is nice. A little issue when the pagination is not set but it works fine. Do you think it will be added to the plus/commons ? I id not find it there: https://feathers-plus.github.io/v1/feathers-hooks-common/
You can propose a PR
What is the best way to cover this hook with a test ? Any direction to point me to? In order to make sure the PR will be clean and accepted ?
G Le mar. 18 déc. 2018 à 19:55, Eddyystop notifications@github.com a écrit :
You can propose a PR
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/feathers-plus/feathers-hooks-common/issues/99#issuecomment-448330200, or mute the thread https://github.com/notifications/unsubscribe-auth/AARZaZZl5DSI3rm0gxOVq4b7KQTCyDFaks5u6TohgaJpZM4Ldvls .
-- 👌
There are existing DB tests, for example stash-before.test.js. The docs are in feathers-plus/docs.
Being rather new to the FeatherJS ecosystem, I'm sure I'm doing something wrong, but using the upsert function in https://github.com/feathers-plus/feathers-hooks-common/issues/99#issuecomment-314963532, I'm getting both the created
and updated
events firing. I was hoping that only one or the other would fire. Is both events firing expected, and any advice on how to work around that?
Idea from a discussion on #general
"Say you get a
CREATE
request for IDs[1, 2, 3, 4, 5]
. You callFIND
with those IDs which tells you that IDs 3 and 5 already exist. You'd then callPATCH
with[3, 5]
andCREATE
with[1, 2, 4]
"