How can I validate an object with unknown keys with Joi? Here is an example of my data
{
'5520': {
name: 'string',
order: 0,
},
'8123': {
name: 'string',
order: 5,
},
'8219': {
name: 'string',
order: 1,
},
'10113': {
name: 'string',
order: 2,
},
'14538': {
name: 'string',
order: 6,
},
'15277': {
name: 'string',
order: 4,
},
'16723': {
name: 'string',
order: 3,
}
I would like to validate each one of those unknown keys to make sure they all contain name, order and a few other properties, they all must have the same properties.
I read their documentation, just can't figure out how to deal with those unknown keys.
You can use object.pattern:
Joi.object().pattern(
/\d/,
Joi.object().keys({
name: Joi.string().valid('string'),
order: Joi.number().integer(),
})
)
This means that your keys must be numbers, and their value must be the object defined with name and order, which means, any of your example data will pass.
Related
I started to learn GraphQL and I'm trying to create the following relationship:
type User {
id: ID!,
name: String!,
favoriteFoods: [Food]
}
type Food {
id: ID!
name: String!
recipe: String
}
So basically, a user can have many favorite foods, and a food can be the favorite of many users. I'm using graphql.js, here's my code:
const Person = new GraphQLObjectType({
name: 'Person',
description: 'Represents a Person type',
fields: () => ({
id: {type: GraphQLNonNull(GraphQLID)},
name: {type: GraphQLNonNull(GraphQLString)},
favoriteFoods: {type: GraphQLList(Food)},
})
})
const Food = new GraphQLObjectType({
name: 'Food',
description: 'Favorite food(s) of a person',
fields: () => ({
id: {type: GraphQLNonNull(GraphQLID)},
name: {type: GraphQLNonNull(GraphQLString)},
recipe: {type: GraphQLString}
})
})
And here's the food data:
let foodData = [
{id: 1, name: 'Lasagna', recipe: 'Do this then that then put it in the oven'},
{id: 2, name: 'Pancakes', recipe: 'If you stop to think about, it\'s just a thin, tasteless cake.'},
{id: 3, name: 'Cereal', recipe: 'The universal "I\'m not in the mood to cook." recipe.'},
{id: 4, name: 'Hashbrowns', recipe: 'Just a potato and an oil and you\'re all set.'}
]
Since I'm just trying things out yet, my resolver basically just returns a user that is created inside the resolver itself. My thought process was: put the food IDs in a GraphQLList, then get the data from foodData usind lodash function find(), and replace the values in person.favoriteFoods with the data found.
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
description: 'Root Query',
fields: {
person: {
type: Person,
resolve(parent) {
let person = {
name: 'Daniel',
favoriteFoods: [1, 2, 3]
}
foodIds = person.favoriteFoods
for (var i = 0; i < foodIds.length; i++) {
person.favoriteFoods.push(_.find(foodData, {id: foodIds[i]}))
person.favoriteFoods.shift()
}
return person
}
}
}
})
But the last food is returning null. Here's the result of a query:
query {
person {
name
favoriteFoods {
name
recipe
}
}
}
# Returns
{
"data": {
"person": {
"name": "Daniel",
"favoriteFoods": [
{
"name": "Lasagna",
"recipe": "Do this then that then put it in the oven"
},
{
"name": "Pancakes",
"recipe": "If you stop to think about, it's just a thin, tasteless cake."
},
null
]
}
}
}
Is it even possible to return the data from the Food type by using only its ID? Or should I make another query just for that? In my head the relationship makes sense, I don't think I need to store the IDs of all the users that like a certain food in the foodData since it has an ID that I can use to fetch the data, so I can't see the problem with the code or its structure.
Calling shift and push on an array while iterating through that same array will invariably lead to some unexpected results. You could make a copy of the array, but it'd be much easier to just use map:
const person = {
name: 'Daniel',
favoriteFoods: [1, 2, 3],
}
person.favoriteFoods = person.favoriteFoods.map(id => {
return foodData.find(food => food.id === id)
})
return person
The other issue here is that if your schema returns a Person in another resolver, you'll have to duplicate this logic in that resolver too. What you really should do is just return the person with favoriteFoods: [1, 2, 3]. Then write a separate resolver for the favoriteFoods field:
resolve(person) {
return person.favoriteFoods.map(id => {
return foodData.find(food => food.id === id)
})
}
Below is a Contentful migration I wrote to create a content model called 'Trip' in Contentful. What I would like to do is specify the format of the "Start Date" and "End Date" fields. Contentful gives you three formatting options that can be set in the UI:
Date only
Date and time without timezone
Date and time with timezone
Without specifying the format in my migration file, I get format #3 by default and I need format #1. Anyone familiar with how to do this?
Thanks!
class CreateTrip < RevertableMigration
self.content_type_id = 'trip'
def up
with_space do |space|
# Create content model
content_type = space.content_types.create(
name: 'Trip',
id: content_type_id,
description: 'Content model for trip cards'
)
# Set validation
validation_for_country = Contentful::Management::Validation.new
validation_for_country.in = ['Bolivia','Haiti','India','Nicaragua', 'Puerto Rico', 'South Africa']
content_type.fields.create(id: 'image', name: 'Image', type: 'Link', link_type: 'Asset', required: true)
content_type.fields.create(id: 'country', name: 'Country', type: 'Symbol', required: true, validations: [validation_for_country])
content_type.fields.create(id: 'trip_details', name: 'Trip Details', type: 'Symbol')
content_type.fields.create(id: 'start_date', name: 'Start Date', type: 'Date', required: true)
content_type.fields.create(id: 'end_date', name: 'End Date', type: 'Date', required: true)
content_type.fields.create(id: 'trip_description', name: 'Trip Description', type: 'Text')
content_type.fields.create(id: 'link_url', name: 'Link URL', type: 'Symbol', required: true)
# Publish
content_type.save
content_type.publish
# Editor interface config
editor_interface = content_type.editor_interface.default
controls = editor_interface.controls
field = controls.detect { |e| e['fieldId'] == 'trip_details' }
field['settings'] = { 'helpText' => 'City, month, participant type, etc.' }
editor_interface.update(controls: controls)
editor_interface.reload
content_type.save
content_type.publish
end
end
end
When I export my content types using the contentful export command via the Contentful CLI, I can see something similar to this in my JSON:
{
"fieldId": "endDate",
"settings": {
"ampm": "24",
"format": "timeZ",
"helpText": "(Optional) The date and time when the event ends..."
},
"widgetId": "datePicker"
},
{
"fieldId": "internalTitle",
"widgetId": "singleLine"
},
{
"fieldId": "startDate",
"settings": {
"ampm": "24",
"format": "timeZ",
"helpText": "The date/time when this schedule starts..."
},
"widgetId": "datePicker"
}
Now, I don't use the Ruby migration tooling, but this leads me to believe you can set field['widgetId'] = 'datePicker' and
field['settings'] = {
'format' => 'dateonly',
'helpText' => ...
}
Let me know if that helps!
I'm trying to use the NGXS state operators inside of my application, but I'm having trouble finding good examples of how to use them for slightly more complex updates.
For example, NGXS's documentation shows an example of updating this state:
#State<AnimalsStateModel>({
name: 'animals',
defaults: {
zebras: ['Jimmy', 'Jake', 'Alan'],
pandas: ['Michael', 'John']
}
})
In order to change the names of one of the pandas, it uses NGXS's "updateItem" state operator like this:
#Action(ChangePandaName)
changePandaName(ctx: StateContext<AnimalsStateModel>, { payload }: ChangePandaName) {
ctx.setState(
patch({
pandas: updateItem(name => name === payload.name, payload.newName)
})
);
}
In this example, the updateItem function uses a lambda expression in its first parameter to find the correct object in the array and replaces it with with the object in the second parameter.
How would you do this with an array containing complex objects of which you only wanted to change the value of one property? For instance, what if my state was this:
#State<AnimalsStateModel>({
name: 'animals',
defaults: {
zebras: [{1, 'Jimmy'} , {2, 'Jake'}, {3, 'Alan'}],
pandas: [{1, 'Michael'}, {2, 'John'}]
}
})
How would I use the updateItem function to locate the correct animal using the ID and then update the name?
The default state example you provided is invalid syntax, but I think I get what you intended to provide. Something like this:
#State<AnimalsStateModel>({
name: 'animals',
defaults: {
zebras: [{id: 1, name: 'Jimmy'} , {id: 2, name: 'Jake'}, {id: 3, name: 'Alan'}],
pandas: [{id: 1, name: 'Michael'}, {id: 2, name: 'John'}]
}
})
updateItem also accepts a state operator as a second parameter so you could use the patch operator again to modify the item. Your action would then look like this:
#Action(ChangePandaName)
changePandaName(ctx: StateContext<AnimalsStateModel>, { payload }: ChangePandaName) {
ctx.setState(
patch({
pandas: updateItem(item=> item.id === payload.id, patch({ name: payload.newName }))
})
);
}
So I have 2 models Books and Classes:
$books = Books::limit(3)->get(['id','classable_id','easy_book']);
// Books returned:
{ id: 200,
classable_id: 2,
easy_book: false
},
{ id: 201,
classable_id: 3,
easy_book: true
},
{ id: 202,
classable_id: 4,
easy_book: false
}
$classIds = $books->pluck('classable_id');
$classes = Classes::whereIn('id', $classIds);
// Classes returned:
{ id: 2,
subject: Math,
students: 30
},
{ id: 3,
subject: History,
students: 30
},
{ id: 4,
subject: Physics,
students: 30
}
Then trying to get the following output (without combining the queries, but keeping them separate like above, and just using php logic to output):
Classes returned:
{ id: 2,
subject: Math,
students: 30.
easy_book: false }, // trying to merge this!
{ id: 3,
subject: History,
students: 30.
easy_book: true}, // trying to merge this!
{ id: 4,
subject: Physics,
students: 30.
easy_book: false } // trying to merge this!
Basically, I am trying to merge the easy_book field from books returned to the respective class returned based on class.id == books.classable_id. Any idea how to merge it?
Add a relationship to your Books model like so:
public function class() {
return $this->belongsTo(Classes::class, 'id', 'classable_id);
}
Then you can do:
Book::with('class')->select('id', 'classable_id', 'easy_book')->limit(3)->get();
Each collection item will then have a collection of classes where applicable.
If after that you want to manipulate them, you can use the map function as documented here: https://laravel.com/docs/5.7/collections#method-map
I'm using aldeed:collection2 and aldeed:simple-schema packages. I want to validate a doc against the schema. My schema contains e.g. a string field with allowedValues array and an array of nested object, described with sub-schema. Like this:
...
type: {
type: String,
allowedValues: [ 'A', 'B', 'C' ],
defaultValue: 'A',
index: 1,
},
nestedStuff: {
type: [ new SimpleSchema(nestedStuffSchema.schema(Meteor, SimpleSchema)) ],
defaultValue: [],
},
...
I have a 'bad' doc which has e.g. "D" in type field and invalid nested array items.
At client I'm trying to:
Contacts.simpleSchema().namedContext().validate(badDoc);
and it returns true. SimpleSchema says the doc is valid even though its fields do not abide to schema.
Validating 'bad' type field individually also returns true.
What am I doing wrong? Why could SimpleSchema assume random stuff to be valid?
if you want to validate an array of strings you need to keep String inside [ ].See the below code it may help
type: {
type: [String],
allowedValues: [ 'A', 'B', 'C' ],
defaultValue: ['A'],
index: 1,
},
nestedStuff: {
type: [ new SimpleSchema(nestedStuffSchema.schema(Meteor,SimpleSchema)) ],
defaultValue: [],
},
Thanks