Retrieve / navigate to a view that expects a struct? - struct

I'm using NavigationLink to navigate to another view, and within a completely different view, I need to navigate back to the "original" view.
My book struct looks like this:
struct Book: Decodable, Identifiable {
var id: Int
var author: String
var chapters: Int
var group: String
var name: String
}
The book view expects a book, as such: destination: BookView(book: book),
However, in my "foreign" view that I wish to navigate back from (ChapterView), all I got are the "loose" values of the book - book_id, author, chapters etc.
How do I "transform"/cast these loose values back into a book struct so I can pass that along in the view?
I can't simply go destination: BookView(book.id: book_id, author: chapter_author) if you know what I mean.

Related

Automatically manipulating argument for Mongoose Document constructor

Let's say I have have this model:
const employeeSchema = new Schema({
name: String,
age: Number,
employeeData: {
department: String,
position: String,
lastTraining: Date
}
});
const Employee = mongoose.model('employee', employeeSchema);
In the database, the only thing that is going to be saved is something that looks like this:
{
_id: ...
name: 'John Smith',
age: 40,
employeeCode: '.... '
}
What's going on is that by some business rules, the employeeData info, which is coming from the reqeust body, is going through some function that compiles out of it the employeeCode, and when saving to the database I just use to the employeeCode.
Right now, the way I am implementing this is using statics. So, I have in the model the follwing:
employeeSchema.statics.compileEmployeeCode = (doc) => {
if (!doc.employeeData) {
doc.employeeCode= compileCode(doc.employeeData);
delete doc.employeeData;
}
return doc;
}
And then, I need to remember, for each call that receives info from the client, to call this function before creating the document (an instance of the model):
const compiledDoc = Employee.compileEmployeeCode(req.body);
const employee = new Employee(comiledDoc);
My question is: is there a way to automatically invoke some function that compiles the code out of the data any time I create a document like that, so I won't need to remember to always call on the static method beforehand?
Middlaware is what you are looking for. You need to create a function that will set a pre-save hook on the schema (which will be triggered every time before saving a new document) and to plug this function into the schema.
function compileEmployeeCode (schema) {
schema.pre('save', next => {
if (this.employeeData) {
this.employeeCode= compileCode(this.employeeData);
delete this.employeeData;
next();
}
});
}
employeeSchema.plugin(compileEmployeeCode);
OK. It was really hard but I finally managed to find the solution. The trick is to use a setter on a specific path. Each field in the schema is of type SchemaType which can have a setter apply on it:
https://mongoosejs.com/docs/api.html#schematype_SchemaType-set
Anyway, if I want to make it possible for the request to enter an object that will be converted to some other format, say a string, I would need to define the schema like this:
const employeeSchema = new Schema({
name: String,
age: Number,
employeeCode: {
type: String,
set: setCodeFromObj,
alias: 'employeeData'
}
});
The setter function I'm using here looks something like this (I'm omitting here all the error handling and the like to keep this short:
function setCodeFromObj(v) {
const obj = {};
obj.department = v.department;
obj.position = v.position;
obj.lastTraining = v.lastTraing
// breaking the object to properties just to show that v actually includes them
return compileEmployeeCode(obj);
}
I used an alias to make the name visible to the user different from what is actually saved in the database. I could have also done that using virtuals or just design the system a bit differently to use up the same name.

Swift adding a reference to the owner of an object

For brevity, I am going to use some simple examples to illustrate my problem. So I currently have two classes:
Class Person and Class Pet
class Person:Codable {
var name:String
var pets:[Pet]?
}
class Pet:Codable {
var name:String
weak var owner:Person?
}
How would I add the owner reference of "Pet" if I am retrieving the data from a json?
JSON would be probably like this:
[
{
"name":"John",
"pets":[
{
"name":"Meow",
"owner":"John"
},
{
"name":"Woof",
"owner":"John"
}
]
}
]
Your JSON is an array of dictionaries, each dictionary representing a person. Each person dictionary itself has an array (associated with the key pets) and each entry in that array is a dictionary representing a pet owned by the person. Your question is how you set your pet -> person weak link, you don't say what you've tried. Here is some sample pseudo-code outlining how you would process this JSON:
parsedJSON = ... // parse the JSON using favourite method, returning an array of dictionaries
allPeople = new Array // place to store all the people
for each personDictionary in parsedJSON // iterate over every person dictionary
nextPerson = new Person // create a new Person
nextPerson.name = personDictionary["name"] // set the name
nextPerson.pets = new Array // create an array for the pets
for each petDictionary in personDictionary["pets"] // iterate over the pets
nextPet = new Pet // create a new Pet
nextPet.name = petDictionary["name"] // set its name
nextPet.owner = nextPerson // set its owner <- answer to your question
nextPerson.pets.add(nextPet) // add pet to person
end for
allPeople.add(nextPerson) // add completed person to allPeople
end for
// at this point allPeople has all the people, and each person
// has all their pets, and each pet has an owner
Now just code that up in Swift 4.
I'm open to changing the json structure as well
The above pseudo-code ignores the owner field for each pet, the pets are nested inside a person dictionary representing the owner so the owner field in a pet is just repeating that information and can be dropped form the JSON.
In your internal data structures having the (weak) back link to the owner might be useful so you can keep that, as the above pseudo-code does.
If you have trouble writing the above algorithm then ask a new question showing the code you've written, describe where your issue is, and someone will undoubtedly help you. Putting a reference to this question in your new one would also be helpful so people and read the history of your question and what you've already had answered.
HTH

How to best reach into MongoDb Mongoose Schema objects which use lists of other objects

I am learning my way around MongoDB data types and the best way to use documents and Schemas through Mongoose.
I've defined a couple of Schemas for a Navigation bar object which stores the navigation items as a list, and each item is defined by a schema with the properties name, type, url, and a list of drop downs if it has any (if the type is "dropdown").
Here are those Schemas:
var navSchema = new Schema({
id: String,
items: [Schema.ObjectId]
});
is the nav object, and
var navItemSchema = new Schema({
name: String,
type: {type: String, default: "link"},
url: {type: String, default: null},
dropdowns: {type: [Schema.ObjectId], default: null}
});
is the nav item schema, but each nav item can potentially have dropdowns, and so the dropdowns is a list of nav items, which can also potentially have dropdowns. But in this case, only a few do.
Now to create the data for these objects, I have to do something like this to create a nav item, example for "home"
var home = new navItem({
name: "home",
url: "/home"
});
but for items with dropdowns, I have to define all the items I know will be dropdowns before defining list which includes those items, and then defining the parent item and using the list with those items I just defined. Like this
var allAccessories = new navItem({
name: "all accessories",
url: "/accessories"
});
var cushions = new navItem({
name: "cushions",
url: "/accessories#cushions"
});
var cupHolders = new navItem({
name: "cup holders",
url: "/accessories#cupholders"
});
var accessoriesDropdownItems = [
allAccessories,
cushions,
cupHolders
];
var accessories = new navItem({
name: "accessories",
type: "dropdown",
dropdowns: accessoriesDropdownItems
});
So, I assume I'm doing that right..? My only gripe with this method is that in my nav.js file where I create this mongodb object/collection, I have to think about what items are going to be used in dropdowns of other items, and so I have to theoretically order them to be defined before the other variables are defined in the document.
But if I then want to use an item in two dropdown lists, and one of those dropdownlists I happened to have already defined above it in the document but now want to add to. I have to move the item definition above anywhere it's used, this ruins the organisation of the document..
But I'm okay with physically indenting to keep my work organised and sorted.
My question is how do I actually access properties of objects within lists of other objects.
Straight up I define Nav as simply an object with an id, "nav" because I don't want to use its _id ObjectId to reference it all the time...? And an items array which contains the navItemsSchema objects.
But when I try to reach into these objects through mongo shell using something like
db.navs.find({items: {$elemMatch: {name:"home"}}})
Or
db.navs.find({items: ObjectId("58d5d6df0789f718460ff278")})
Or
db.navs.find({items:{ "name" : "home"}})
I can't get any data back.. I have successfully manage to return all objects in the collection through the node app via responding found nav in navs
app.get('/nav', function(req, res) {
mongoose.model('nav').find(function(err, nav) {
res.send(nav);
});
});
But all this returns me is a data structure with object id's and not the actually data of the objects.
{"_id":"58d5d6df0789f718460ff287",
"id":"nav","__v":0,
"items":
["58d5d6df0789f718460ff278",
"58d5d6df0789f718460ff279",
"58d5d6df0789f718460ff286",
"58d5d6df0789f718460ff281",
"58d5d6df0789f718460ff282",
"58d5d6df0789f718460ff283",
"58d5d6df0789f718460ff284"
]
}
Could you please help me understand how I reach into these data hierarchies via say db.navs.nav("nav").items.findAll() and it lists all the items and their json?
Or is this not possible and you need to loop through all items, then. Wait, where are the objects stored corresponding to ObjectId's in the items list?
So I actually figured it out myself. I was using type: [Schema.ObjectId] (ie. type was a list of Schema.ObjectIds) but I didn't know that that automatically parses the inputted list of objects and extracts just their ObjectId's and stores them, all I need to do was make the type: [] (ie. just a list) and then I can traverse my nav object simply by... Oh wait, for some reason when mongoose nav.saves my nav it becomes an object inside a list. So when I mongoose.model('nav').find(function(err, nav) {} it gives me a nav object which I need to use via nav[0].items[0].name Or I can go nav = nav[0] but I wish I didn't need to do this step? Maybe there's an answer but yes. Otherwise. This is the solution I was looking for.

Mongoose 'static' methods vs. 'instance' methods

I believe this question is similar to this one but the terminology is different. From the Mongoose 4 documentation:
We may also define our own custom document instance methods too.
// define a schema
var animalSchema = new Schema({ name: String, type: String });
// assign a function to the "methods" object of our animalSchema
animalSchema.methods.findSimilarTypes = function (cb) {
return this.model('Animal').find({ type: this.type }, cb);
}
Now all of our animal instances have a findSimilarTypes method available to it.
And then:
Adding static methods to a Model is simple as well. Continuing with our animalSchema:
// assign a function to the "statics" object of our animalSchema
animalSchema.statics.findByName = function (name, cb) {
return this.find({ name: new RegExp(name, 'i') }, cb);
}
var Animal = mongoose.model('Animal', animalSchema);
Animal.findByName('fido', function (err, animals) {
console.log(animals);
});
It seems with static methods each of the animal instances would have the findByName method available to it as well. What are the statics and methods objects in a Schema? What is the difference and why would I use one over the other?
statics are the methods defined on the Model. methods are defined on the document (instance).
You might use a static method like Animal.findByName:
const fido = await Animal.findByName('fido');
// fido => { name: 'fido', type: 'dog' }
And you might use an instance method like fido.findSimilarTypes:
const dogs = await fido.findSimilarTypes();
// dogs => [ {name:'fido',type:'dog} , {name:'sheeba',type:'dog'} ]
But you wouldn't do Animals.findSimilarTypes() because Animals is a model, it has no "type". findSimilarTypes needs a this.type which wouldn't exist in Animals model, only a document instance would contain that property, as defined in the model.
Similarly you wouldn't¹ do fido.findByName because findByName would need to search through all documents and fido is just a document.
¹Well, technically you can, because instance does have access to the collection (this.constructor or this.model('Animal')) but it wouldn't make sense (at least in this case) to have an instance method that doesn't use any properties from the instance. (thanks to #AaronDufour for pointing this out)
Database logic should be encapsulated within the data model. Mongoose provides 2 ways of doing this, methods and statics. Methods adds an instance method to documents whereas Statics adds static “class” methods to the Models itself.The static keyword defines a static method for a model. Static methods aren't called on instances of the model. Instead, they're called on the model itself. These are often utility functions, such as functions to create or clone objects. like example below:
const bookSchema = mongoose.Schema({
title: {
type : String,
required : [true, 'Book name required']
},
publisher : {
type : String,
required : [true, 'Publisher name required']
},
thumbnail : {
type : String
}
type : {
type : String
},
hasAward : {
type : Boolean
}
});
//method
bookSchema.methods.findByType = function (callback) {
return this.model('Book').find({ type: this.type }, callback);
};
// statics
bookSchema.statics.findBooksWithAward = function (callback) {
Book.find({ hasAward: true }, callback);
};
const Book = mongoose.model('Book', bookSchema);
export default Book;
for more info: https://osmangoni.info/posts/separating-methods-schema-statics-mongoose/
Well to me it doesn't mean add anythings by adding Mongoose in front of 'static' or even in front 'instance' keyword.
What I believe meaning and purpose of static is same everywhere, even it's also true for an alien language or some sort of driver which represents Model for building the block like another object-oriented programming. The same also goes for instance.
From Wikipedia:
A method in object-oriented programming (OOP) is a procedure associated with a message and an object. An object consists of data and behavior. The data and behavior comprise an interface, which specifies how the object may be utilized by any of various consumers[1] of the object.
Data is represented as properties of the object and behaviors are represented as methods of the object. For example, a Window object could have methods such as open and close, while its state (whether it is opened or closed at any given point in time) would be a property.
Static methods are meant to be relevant to all the instances of a class rather than to any specific instance. They are similar to static variables in that sense. An example would be a static method to sum the values of all the variables of every instance of a class. For example, if there were a Product class it might have a static method to compute the average price of all products.
Math.max(double a, double b)
This static method has no owning object and does not run on an instance. It receives all information from its arguments.[7]
A static method can be invoked even if no instances of the class exist yet. Static methods are called "static" because they are resolved at compile time based on the class they are called on and not dynamically as in the case with instance methods, which are resolved polymorphically based on the runtime type of the object.
https://en.wikipedia.org/wiki/Method_(computer_programming)
As everybody said, use methods when we want to operate on a single document, and we use statics when we want to operate on entire collection. But why?
Let's say, we have a schema:
var AnimalSchema = new Schema({
name: String,
type: String
});
now as mentioned in the docs, if you need to check the similar types of a particular document in the db
you can do:
AnimalSchema.methods.findSimilarType = function findSimilarType (cb) {
return this.model('Animal').find({ type: this.type }, cb);
};
Now, this here refers to the document itself. So, what that means is, this document
doesn't knows which model it belongs to, because methods has nothing to do with the model defaultly, It's only for that particular document obj.
But we can make it work with the model, using this.model('anyModelName').
Now why did we used methods in the case of finding similar types of animals?
For finding similar types of animals we must have an animal obj for which we'll find similar types of.
That animal obj we have let's say:
const Lion = await new Animal({name: Lion, type: "dangerous"});
Next, when we find similar types we need the Lion obj first, we must have it.
So simply, whenever we need the help of a particular obj/document for doing something, We'll use methods.
Now here by chance we also need whole model to search slimier types,
although it is not available directly in methods (remember this.modelName will return undefined). we can get it by setting this.model() to our preferred model, in this case Animal.
That's all for methods.
Now, statics has the whole model at its disposal.
1)Let's say you want to calculate the total price (assume the model has a price field) of all Animals you'll use statics [for that you don't need any particular Animal obj, so we won't use method]
2)You want to find the animals which have yellow skin (assume the model has a skin field), you'll use statics. [ for that we don't need any particular Animal obj, so we won't use method]
eg:
AnimalSchema.statics.findYellowSkin = function findSimilarType (cb) {
return this.find({ skin: "yellow" }, cb);
};
Remember, In methods this refers to the model so, this.modelName will return Animal (in our case).
but just like methods, here also we can (but we don't need to) set the model using.
AnimalSchema.methods.findSimilarType = function findSimilarType (cb) {
return this.model('Animal').find({ skin: "yellow" }, cb); //just like in methods
};
so as you can see both of statics and methods are very similar.
Whenever you have a document and you need something to do with that,
use methods. Whenever you need to do something with the whole
model/collection, use statics.
Static methods apply to the entire model on which they are defined whereas instance methods apply only to a specific document within the collection.
this in the context of a static method returns the entire model whereas this in the context of an instance method returns the document.
Lets say for example:
const pokemon = new mongoose.Schema({})
pokemon.statics.getAllWithType = function(type){
// Query the entire model and look for pokemon
// with the same type
// this.find({type : type})
}
pokemon.methods.sayName = function(){
// Say the name of a specific pokemon
// console.log(this.name)
}
const pokemonModel = mongoose.model('schema', pokemon)
const squirtle = new pokemonModel({name : "squirtle"})
// Get all water type pokemon from the model [static method]
pokemonModel.getAllWithType("water")
// Make squirtle say its name [instance method]
squirtle.sayName()
Use .statics for static methods.
Use .methods for instance methods.
//instance method
bookSchema.methods.findByType = function (callback) {
return this.model('Book').find({ type: this.type }, callback);
};
// static method
bookSchema.statics.findBooksWithAward = function (callback) {
Book.find({ hasAward: true }, callback);
};
statics are pretty much the same as methods but allow for defining functions that exist directly on your Model.
statics belongs to the Model and methods belongs to an Instance

Tracking changes to fields using mongoose.js

I'm trying to figure out the best way to track changes to fields when using mongoose.js. For example, every time the name field on an object is set, I want to add a new entry to that object's history (as an embedded document) that looks something like { field: 'name', previous: 'foo', current: 'bar', date: '3/06/2012 9:06 am' }.
I started by trying to use a plug-in that hooks .pre('save') but I can't figure out which fields have been modified without grabbing the old value from the database and comparing them myself. Then I thought I could use custom setters, but I ran into the same problem - I don't know which field was modified. Currently I'm left with doing something like this which hard codes the field name into to the setter:
var comment = new Schema({
name : { type: String, set: trackName },
history : [Change]
});
var trackName = function(val) {
var change = new Change;
change.field = 'name';
change.previous = this.name;
change.current = val;
change.date = Date.now();
this.history.push(change);
return val;
}
But this means I need a custom setter for each field name that I want to track. I'm guessing there must be a better way to accomplish this.
Looks like i missed 'Document.modifiedPaths'. This does exactly what I need to determine which fields have been modified.

Resources