How to work with db models in functional programming (js for example).
If i have regular orm the code will be similar to:
let users = await usersRepo.where({active: 1})
.limit(10)
.orderBy('dateCreated, 'asc')
.get()
// activate the users
let mails = []
for(let u of users) {
mails.push(u.activate());
}
In functional programming:
let users = users.get(db, {where: {active: 1}, order: 'dateadded'})
for(u of users){
usersFn.activate(db, u)
}
I have to pass db and each instance it causes confusion because i have to pass many arguments(db for example) since no state are being pass to the constructors.
Is there more convenient way because the OOP variant is way more pretty.
I want to use functional programming but not to sacrifice code beauty.
This is OOP:
class User {
constructor(db) {
this.db = db
}
get(constraints) {
}
}
let users = new User(db)
let result = users.get({where: {active: 1}, order: 'date'})
This is fp with higher order function:
const bindUsersGet = (db) => {
return (constraints) => {
// db is available here
// constraints as well
}
}
let boundUserGet = bindUsersGet(db)
let result = boundUserGet({ where: { active: 1 }, order: 'dateadded' })
I dont see how fp with higher order function is better than OOP variant.
It is the same except the syntax? What's so special about it?
Thanks
You could use the concept of a higher order function to generate a "bound" users that already knows the DB instance. Some rough code:
const bindUsersGet = (db) => {
return (constraints) => {
// db is available here
// constraints as well
}
}
let boundUserGet = bindUsersGet(db)
let result = boundUserGet({ where: { active: 1 }, order: 'dateadded' })
This is an example for a single function, but you can obviously also return an object with multiple functions all having access to the db given to the "outer" function.
Related
I have a simple vanilla JS Firebase app that is, among other things, designed to allow users to store the number of ounces of a given 'commodity' they possess, for example silver or gold. On the client side, the ounces are used in another function getCommodityValue to get the total value of each commodity from an API (that's why the function being async).
However, when I attempt to run updateCommodities (shown below) to update the values in my supportedCommodities object, for some reason only the value for the last commodity listed is changed. The values are accurately represented in Firebase, so it looks like this function is at fault. What am I doing wrong?
Here's a simplified (still non-functioning) version of my code:
let supportedCommodities = {
SILVER: {name: "silver", value: 0},
GOLD: {name: "gold", value: 0}
}
async function updateCommodities() {
for (sc in supportedCommodities) {
let commodityName = supportedCommodities[sc]["name"];
firebaseDB.collection("commodities").where("name", "==", commodityName)
.onSnapshot(async (querySnapshot) => {
if (querySnapshot.docs.length != 0) {
const commodityData = querySnapshot.docs[0].data();
supportedCommodities[sc]["value"] = await getCommodityValue(commodityData.ounces, commodityData.name);
}
});
}
}
The OP function passes an async function as the onSnapshot() callback, the getCommodityValue promise contained therein can only resolve once. Replace the onSnapshot call with a get...
async function updateCommodities() {
for (sc in supportedCommodities) {
const commodityName = supportedCommodities[sc]["name"];
const query = firebaseDB.collection("commodities").where("name", "==", commodityName);
const doc = await query.get();
const commodityData = doc.data();
supportedCommodities[sc]["value"] = await getCommodityValue(commodityData.ounces, commodityData.name);
}
}
I'm going to begin by saying that this is purely a matter of syntax candy-
I have a number of javascript functions with this general signature:
const someFn = async (context, args) => {}
Each implementation deconstructs those two objects. For example:
const myHttpFn = async({req, res}, {id, name, potato}) => { ... }
Within the implementation, I'd like to be able to deconstruct the object in the signature (this makes it easy for users to see what arguments are truly required) but still have a reference to the rest of the object (some properties are pass-through). The closest I've come has been by wrapping both arguments in an object like so:
const { isCold, isDry } = require('./some/utilities');
const lunch({context, context:{fridge, pantry, sandwitchMaker}, {ingredientList, shouldHeat}) => {
const coldList = ingredientList.filter(isCold);
const dryList = ingredientList.filter(isDry);
const [cold, dry] = await Promise.all(
fridge.fetch(context, { list: coldList }),
pantry.fetch(context, { list: dryList })
);
return sandwitchMaker.cook(context, { cold, dry, shouldHeat });
}
const sandwitch = await lunch({context, {
ingredientList: ["bread", "cheese", "tomato", "bacon", "bacon", "lettuce", "mayo"],
shouldHeat: true
}});
Adding the wrapper object gives me access to both context as a whole as well as the pieces that I've deconstructed. Is there a way to do this without a wrapper object?
You can't do it without your so-called wrapper object, but you can have just the wrapper object in the function definition - something like
const lunch({context: {fridge, pantry, sandwitchMaker, ...other}, {ingredientList, shouldHeat}) => {
const context = {fridge, pantry, sandwitchMaker, ...other};
//... rest of your code
}
I feel like I'm missing something obvious. I have IDs stored as [String] that I want to be able to resolve to the full objects they represent.
Background
This is what I want to enable. The missing ingredient is the resolvers:
const bookstore = `
type Author {
id: ID!
books: [Book]
}
type Book {
id: ID!
title: String
}
type Query {
getAuthor(id: ID!): Author
}
`;
const my_query = `
query {
getAuthor(id: 1) {
books { /* <-- should resolve bookIds to actual books I can query */
title
}
}
}
`;
const REAL_AUTHOR_DATA = [
{
id: 1,
books: ['a', 'b'],
},
];
const REAL_BOOK_DATA = [
{
id: 'a',
title: 'First Book',
},
{
id: 'b',
title: 'Second Book',
},
];
Desired result
I want to be able to drop a [Book] in the SCHEMA anywhere a [String] exists in the DATA and have Books load themselves from those Strings. Something like this:
const resolve = {
Book: id => fetchToJson(`/some/external/api/${id}`),
};
What I've Tried
This resolver does nothing, the console.log doesn't even get called
const resolve = {
Book(...args) {
console.log(args);
}
}
HOWEVER, this does get some results...
const resolve = {
Book: {
id(id) {
console.log(id)
return id;
}
}
}
Where the console.log does emit 'a' and 'b'. But I obviously can't scale that up to X number of fields and that'd be ridiculous.
What my team currently does is tackle it from the parent:
const resolve = {
Author: {
books: ({ books }) => books.map(id => fetchBookById(id)),
}
}
This isn't ideal because maybe I have a type Publisher { books: [Book]} or a type User { favoriteBooks: [Book] } or a type Bookstore { newBooks: [Book] }. In each of these cases, the data under the hood is actually [String] and I do not want to have to repeat this code:
const resolve = {
X: {
books: ({ books }) => books.map(id => fetchBookById(id)),
}
};
The fact that defining the Book.id resolver lead to console.log actually firing is making me think this should be possible, but I'm not finding my answer anywhere online and this seems like it'd be a pretty common use case, but I'm not finding implementation details anywhere.
What I've Investigated
Schema Directives seems like overkill to get what I want, and I just want to be able to plug [Books] anywhere a [String] actually exists in the data without having to do [Books] #rest('/external/api') in every single place.
Schema Delegation. In my use case, making Books publicly queryable isn't really appropriate and just clutters my Public schema with unused Queries.
Thanks for reading this far. Hopefully there's a simple solution I'm overlooking. If not, then GQL why are you like this...
If it helps, you can think of this way: types describe the kind of data returned in the response, while fields describe the actual value of the data. With this in mind, only a field can have a resolver (i.e. a function to tell it what kind of value to resolve to). A resolver for a type doesn't make sense in GraphQL.
So, you can either:
1. Deal with the repetition. Even if you have ten different types that all have a books field that needs to be resolved the same way, it doesn't have to be a big deal. Obviously in a production app, you wouldn't be storing your data in a variable and your code would be potentially more complex. However, the common logic can easily be extracted into a function that can be reused across multiple resolvers:
const mapIdsToBooks = ({ books }) => books.map(id => fetchBookById(id))
const resolvers = {
Author: {
books: mapIdsToBooks,
},
Library: {
books: mapIdsToBooks,
}
}
2. Fetch all the data at the root level instead. Rather than writing a separate resolver for the books field, you can return the author along with their books inside the getAuthor resolver:
function resolve(root, args) {
const author = REAL_AUTHOR_DATA.find(row => row.id === args.id)
if (!author) {
return null
}
return {
...author,
books: author.books.map(id => fetchBookById(id)),
}
}
When dealing with databases, this is often the better approach anyway because it reduces the number of requests you make to the database. However, if you're wrapping an existing API (which is what it sounds like you're doing), you won't really gain anything by going this route.
In one of my API endpoints I fetch a json resource (1) from the web and edit it to fit my needs. In the "lowest" or "deepest" part of the tree I'm trying to fetch another resource and add it to the final json object. I'm relatively new to async/await but am trying to move away from the "old" Promises since I see the advantage (or the gain) of using async/await.
The object from (1) looks like;
const json = {
date,
time,
trips: [{
name,
legs: [{
id
},
{
id
}
]
}]
};
Here's how I "reformat" and change the json object;
{
date,
time,
trips: json.trips.map(trip => formatTrip(trip))
};
function formatTrip(trip) {
return {
name,
legs: trip.legs.map(leg => formatLeg(leg))
};
};
async function formatLeg(leg) {
const data = await fetch();
return {
id,
data
};
};
The problem with this is that after I've "reformatted/edited" the original json to look how I want it (and ran through all format... functions) the legs objects are empty {}.
I figured this might be due to the async/await promises not finishing. I've also read that if a child-function uses async/await all the higher functions has to use async/await as well.
Why? How can I rewrite my code to work and look good? Thanks!
EDIT:
I updated my code according to Randy's answer. getLegStops(leg) is still undefined/empty.
function formatLeg(leg) {
return {
other,
stops: getLegStops(leg)
};
};
function getLegStops(leg) {
Promise.all(getLegStopRequests(leg)).then(([r1, r2]) => {
/* do stuff here */
return [ /* with data */ ];
});
};
function getLegStopRequests(leg) {
return [ url1, url2 ].map(async url => await axios.request({ url }));
};
Two things lead you to want to nest these Promises:
The old way of thinking about callbacks and then Promises
Believing the software process must match the data structure
It appears you only need to deal with the Promises once if I understand correctly.
Like this:
async function getLegs(){
return trip.legs.map(async leg => await fetch(...)); // produces an array of Promises
}
const legs = Promise.all(getLegs());
function formatLegs(legs) {
// do something with array of legs
};
function formatTrip(){
//format final output
}
EDIT: per your comment below, this snippet represents what I've demonstrated and what your goal should look like. Please review your code carefully.
const arr = [1, 2, 3, ];
const arrPromises = arr.map(async v => await new Promise((res) => res(v)));
const finalPromise = Promise.all(arrPromises);
console.log(finalPromise.then(console.log));
I am wondering if there is a way I can construct mongo's queries to take advantage of es6 default parameters. I have the following method. I want to return all the data if make, model and year is not specified. I am trying to find an elegant solution but so far all I can think of is manual if else.
getStyles({ make = '', model = '', year = '-1' }) {
return this.db
.collection('styles')
.find({ 'make.niceName': make, 'model.niceName': model, 'year.year': parseInt(year) })
.toArray();
}
Note:
This is causing some confusion. I am using destructing on purpose. The problem is not how to write this function. The problem is how to construct a mongo query so it would ignore empty values.
Assuming getStyles is your own method, sure, you can give make, model, and year defaults. You can also give a default for the whole object you're destructuring so caller doesn't have to pass anything:
function getStyles({make = '', model = '', year = '-1'} = {}) {
// Overall default ------------------------------------^^^^^
return // ...
}
The question is not how to organize/write my function but how to use es6 features to write a cleaner code that would work with mongo. I.E if the user didn't pass anything I want to return all the styles but mongo actually looks for empty fields so it doesn't return anything.
It sounds to me like you don't want default parameters (except perhaps the overall default). Instead, you want to automate how you build the object you pass find.
Given your code example, you can readily do that with Object.keys on your object. So accept as an object, e.g.:
function getStyles(options = {}) {
...an then build your find options based on options:
const findParams = {};
Object.keys(options).forEach(key => {
findParams[key + ".niceName"] = options[key];
});
Live example:
function getStyles(options = {}) {
const findParams = {};
Object.keys(options).forEach(key => {
findParams[key + ".niceName"] = options[key];
});
console.log(`find options: ${JSON.stringify(findParams)}`);
}
let results = getStyles({make: "Ford", model: "Mustang"});
results = getStyles({make: "Ford", model: "Mustang", year: 2017});
If the mapping of the name you accept (make) to the name you need for find (make.niceName) isn't as easy as just appending .niceName, it's easy enough to have a Map (or just object) you build once:
const paramNames = new Map([
["make", "make.niceName"],
["model", "model.niceName"],
["year", "year.niceName"]
]);
...and then use:
const findParams = {};
Object.keys(options).forEach(key => {
const paramName = paramNames.get(key);
if (paramName) {
findParams[paramName] = options[key];
}
});
Live example:
const paramNames = new Map([
["make", "make.niceName"],
["model", "model.niceName"],
["year", "year.niceName"]
]);
function getStyles(options = {}) {
const findParams = {};
Object.keys(options).forEach(key => {
const paramName = paramNames.get(key);
if (paramName) {
findParams[paramName] = options[key];
}
});
console.log(`find options: ${JSON.stringify(findParams)}`);
}
let results = getStyles({make: "Ford", model: "Mustang"});
results = getStyles({make: "Ford", model: "Mustang", year: 2017});
Side note: Defaults don't have to be strings, so if you use numbers for year rather than strings, your default would just be -1, not '-1'.