I'm using adonisjs, and am trying to sanitize a post request on the server. The data I get back has extra properties that are not mapped to the table/model so it is erroring when I try to save. Here is the update method
async update ({ params, request, response }) {
const contract = await Contract.find(params.id);
contract.merge(request.post());
return await contract.save();
}
The problem is that when I returned the contract earlier on a get request, I added some computed properties. I could do something along the lines of
const { prop1, prop2 } = request.post();
but that doesn't feel like a future proof or clean solution. Ideally I want the object to only have the properties defined on the table/model. I have a validator setup as described in the validator docs, but it still lets other properties bypass it.
I resolved this by adding a beforeSave hook in the model class that filter's properties on the object, which allows us to keep a thin controller.
const FIELDS = ['id', 'description'];
class Contract extends Model {
static boot() {
super.boot();
this.addHook('beforeSave', async contractInstance => {
Object.keys(contractInstance.$attributes).forEach(key => {
if (!CONTRACT_FIELDS.includes(key)) {
delete contractInstance.$attribute[key];
}
});
});
}
}
What about an helper class?
class RequestContractExtractor{
static desiredProps = [
"prop1",
"prop2",
]; // you could replace this with a list you get from your model class
constructor(requestData){
desiredProps.forEach(prop => this[prop] = requestData[prop]);
}
static from(...args){ return new this(...args); }
}
Then you'd be able to do:
async update ({ params, request, response }) {
const contract = await Contract.find(params.id);
contract.merge(RequestContractExtractor.from(request.post()));
return await contract.save();
}
I resolved this by adding a beforeSave hook that filter's properties on the object.
Create a file at /app/Model/Hooks/ContractHook.js
'use strict'
const ContractHook = module.exports = {}
const CONTRACT_FIELDS = ["id", "description"];
ContractHook.removeDynamicFields = async (contractInstance) => {
if (contractInstance) {
Object.keys(contractInstance.$attributes).forEach((key) => {
if (!CONTRACT_FIELDS.includes(key) && contractInstance.$attributes[key]) {
delete contractInstance.$attributes[key];
}
});
}
};
Use it in your model like so...
class Contract extends Model {
static boot() {
super.boot();
this.addHook("beforeCreate", [
"ContractHook.removeDynamicFields",
]);
}
Related
My GraphQL query looks like this:
{
p1: property(someArgs: "some_value") {
id
nestedField {
id
moreNestedField {
id
}
}
}
}
On the server side, I'm using Apollo Server.
I have a resolver for the property and other resolvers for nestedField and moreNestedField.
I need to retrieve the value of someArgs on my nested resolvers.
I tried to do this using the context available on the resolver:
property: (_, {someArgs}, ctx) => {
ctx.someArgs = someArgs;
// Do something
}
But this won't work as the context is shared among all resolvers, thus if I have multiple propertyon my query, the context value won't be good.
I also tried to use the path available on info on my nested resolvers. I'm able to go up to the property field but I don't have the arguments here...
I also tried to add some data on info but it's not shared on nested resolvers.
Adding arguments on all resolvers is not an option as it would make query very bloated and cumbersome to write, I don't want that.
Any thoughts?
Thanks!
Params can be passed down to child resolvers using the currently returned value. Additional data will be removed from the response later.
I'll 'borrow' Daniel's code, but without specific params - pass args down as reference (suitable/cleaner/more readable for more args):
function propertyResolver (parent, args) {
const property = await getProperty()
property.propertyArgs = args
return property
}
// if this level args required in deeper resolvers
function nestedPropertyResolver (parent, args) {
const nestedProperty = await getNestedProperty()
nestedProperty.propertyArgs = parent.propertyArgs
nestedProperty.nestedPropertyArgs = args
return nestedProperty
}
function moreNestedPropertyResolver (parent) {
// do something with parent.propertyArgs.someArgs
}
As Daniels stated this method has limited functionality. You can chain results and make something conditionally in child resolver. You'll have parent and filtered children ... not filtered parent using child condition (like in SQL ... WHERE ... AND ... AND ... on joined tables), this can be done in parent resolver.
Do not pass your argument through root, except IDs or parent object, anything from client, use field level argument.
Please check this answer here on how to pass the arguments:
https://stackoverflow.com/a/63300135/11497165
To simplify it, you can put args in your field:
Example Type Definition
Server defination:
type Query{
getCar(color: String): Car
... other queries
}
type Car{
door(color: String): Door // <-- added args
id: ID
previousOwner(offset: Int, limit: Int): Owner // <-- added args
...
}
client query:
query getCar(carId:'123'){
door(color:'grey') // <-- add variable
id
previousOwner(offset: 3) // <-- added variable
... other queries
}
You should be able to access color in your child resolver arguments:
In your resolver:
Car{
door(root,args,context){
const color = args.color // <-- access your arguments here
}
previousOwner(root,args,context){
const offset = args.offset // <-- access your arguments here
const limit = args.limit // <-- access your arguments here
}
...others
}
For your example:
it will be like this
{
p1: property(someArgs: "some_value") { // <-- added variable
id
nestedField(someArgs: "some_value") { // <-- added variable
id
moreNestedField(offset: 5) {
id
}
}
}
}
You can pass the value through the parent field like this:
function propertyResolver (parent, { someArgs }) {
const property = await getProperty()
property.someArgs = someArgs
return property
}
function nestedPropertyResolver ({ someArgs }) {
const nestedProperty = await getNestedProperty()
nestedProperty.someArgs = someArgs
return nestedProperty
}
function moreNestedPropertyResolver ({ someArgs }) {
// do something with someArgs
}
Note that while this works, it may also point to an underlying issue with your schema design in the first place. Depending on how you're resolving these fields (getting them from a database, making requests to another API, etc.), it may be preferable to take a different approach altogether -- for example, by eager loading everything inside the root resolver. Without more context, though, it's hard to make any additional recommendations.
I'm build a store, and want to make action generator or others with a plugin.
On there met a problem. Because of this.
That is impossible thing on Nuxt & Vuex?
I posted plugin's source code ( with some black box )
My final goal is generateActionList with configuration object
export default ({ app }, inject) => {
const callAxios = (/* params */) => { /* some codes */ }
const generateAction = (config) => {
return (context, payload) => callAxios(/* params */)
}
const generateActionList = (config) => { // <= it's my Goal
const actions = {}
for (const [key, config] of Object.entries(conf)) {
actions[key] = generateAction(config)
}
return actions
}
inject('storeUtil', {
callAxios,
generateAction,
generateActionList
})
}
if have some solution, please talk to me.
i saw, 'this' is only lexically available on document.
ps) https://nuxtjs.org/guide/vuex-store/
Store is a module, not an instance of a class, so no this.
Instead of ...this.$storeUtil, get it from Vue directly.
Vue.prototype.$storeUtil
It might also be available at Vue.$storeUtil. If not, you could always init it there in storeUtil.
As an reference, this is how filters are reused outside of a component.
If it was never registered as prototype, you can just import it directly.
import storeUtil from '../storeUtil'
//...
[...storeUtil]
///...
I'm trying to figure out how to set a property type to be a specific object.
Or, to be more clear about my specific situation --- I'm injecting all of my models into GraphQL's context object. Each of the models are simple objects that look like:
const getUser = (id: string): UserType => db.collections('User').find({ id })
const User = {
getUser,
// ...etc
}
A resolver would look like:
const user = (_: Object, args: GetUserArgs, ctx: GraphQLContext) => {
ctx.models.User.getUser(args.input.id)
}
I could type GraphQLContext like this:
type UserModel = {
getUser: (id: string): UserType
// ... etc, for each function. But I've already typed `getUser` above.
}
type Models = {
user: UserModel
}
type GraphQLContext = {
models: Models
}
But, this seems tedious and error-prone as functions I'm adding to my models grow and change during development. Is there a way that I can type the models property that would get all of the type definitions that are already applied to each function? In other words, is there a way I can do this without having to type getUser in both places?
Here's an example
You could use the typeof operator to get the type of your model objects.
const User = {
getUser
}
const Models = {
User
}
type GraphQLContext = {
models: typeof Models
}
I implemented the idea in your example
In my web app's client code I have a class responsible for a bunch of websocket IO. This class has a global itemUpdatedObservable that various parts of the UI can subscribe to to do little things. There is also a public function UpdateItem which returns a promise-esq Observable. When the item is updated in response to the call to UpdateItem I want both the returned observable and global observable to emit. The returned observable should also complete after emitting.
I have come up with this solution:
// Singleton
class API {
readonly itemUpdatedObservable: Observable<Item>;
private pendingItemUpdates: { [id: string]: Observer<Item> };
constructor() {
this.itemUpdatedObservable = new Observable(observer => {
socketio.on('itemUpdated', res => {
// do a bunch of validation on item
// ...
if (!res.error) {
observer.next(res.item);
} else {
observer.error(res.error);
}
let pendingObs = pendingItemUpdates[res.id]
if (pendingObs) {
if (!res.error) {
pendingObs.next(res.item);
} else {
pendingObs.error(res.error);
}
pendingObs.complete()
delete pendingItemUpdates[res.id];
}
})
});
this.pendingItemUpdates
}
public UpdateItem(item: Item): Observable<Item> {
const o = new Observable(observer => {
let id = uniqueId(); // Some helper somewhere.
this.pendingItemUpdates[id] = observer;
socketio.emit('updateitem', {item: item, id: id});
}).publish();
o.connect();
return o;
}
}
My question is if there is a cleaner, shorter way of doing this? I have something like 10+ observables in addition to itemUpdatedObservable that all are events for different Object types. This code is messy and unwieldy especially when I am writing it 10x over. Is there a way to streamline the two observables such that I am only calling observable.next(...) or observable.error(...) once?
The above code blob is a simplification of my actual code, there is a lot more validation and context-specific values and parameters in reality.
Maybe you can start with creating some reusable socket function which return observable.
const socketOn = (event) => {
return Observable.create(obs => {
socketio.on(event, res => {
if (!res.error) {
obs.next(res.item);
} else {
obs.error(res.error);
}
})
}).share()
}
// usuage
itemUpdated$=socketOn('itemUpdated')
itemUpdated$.map(res=>...).catch(e=>...)
I need a resource that wouldn't be a collection but single item instead. I don't see anything about customizing mongoose service in that way.
You can return anything from your find method, it does not have to be a collection. So to get an object for e.g. /singleton you can just do something like:
app.use('/singleton', {
find: function(params) {
return Promise.resolve({
test: 'data'
});
}
});
This will of course also work via a websocket socket.emit('singleton::find'). For the Mongoose service there are two options:
1) Extending
Extend the service and then call it with a single object like this:
const MongooseService = require('feathers-mongoose').Service;
class SingletonService extends MongooseService {
find(params) {
return super.find(params).then(data => data[0]);
}
}
app.use('/singleton', new SingletonService({
Model: Todo,
name: 'todo'
}));
2) Hooks
Potentially even nicer with feathers-hooks, register an after hook that retrieves the singleton item from the collection originally requested:
const hooks = require('feathers-hooks');
app.configure(hooks())
.use('/singleton', mongooseService('todo', Todo));
app.service('singleton').hooks({
after: {
find(hook) {
const firstItem = hook.result[0];
hook.result = firstItem;
}
}
});