Order Bookshelf.js fetch by related column value - node.js

I'm using Bookshelf.js/Knex.js, fetching a model (call it user) with a related child model (call it company).Can I order by a field on the child model - company.name?
Also, if that's possible, can I multi sort, say company.name descending then lastName ascending
Here's my current code, which only works on root model fields. qb.orderBy('company.name', 'desc') doesn't work.
users.query(function(qb) {
qb.orderBy('lastName', 'asc');
})
.fetch({withRelated: ['company']})
.then(success, error);

Try the following:
users
.fetch({withRelated: [
{
'company': function(qb) {
qb.orderBy("name");
}
}
]})
.then(success, error);
I got the idea from https://github.com/tgriesser/bookshelf/issues/361

You can do it like this without the need of a function:
users.query(function(qb) {
qb.query('orderBy', 'lastName', 'asc');
})
.fetch({withRelated: ['company']})
.then(success, error);
Found here: Sort Bookshelf.js results with .orderBy()

I think I solved it by doing this:
let postHits =
await posts
.query(qb => qb
.innerJoin('post_actor_rel', function () {
this.on('post.id', '=', 'post_actor_rel.post_id');
})
.innerJoin('actor', function () {
this.on('post_actor_rel.actor_id', '=', 'actor.id');
})
.orderByRaw('actor.name ASC')
.groupBy('id')
)
.fetchPage({
withRelated: ['roles', 'themes', 'activity_types', 'subjects', 'educational_stages', 'images', 'documents', 'actors'],
limit,
offset
},
);
I modify the query by inner joining with the desired tables and after sorting (using orderByRaw since I will need to add some more sorting that I think is not possible with orderBy) I group by the post's id to get rid of the duplicate rows. The only problem is that it's not defined which actor name (of several possible) is used for the sorting.

Related

bookshelf js – how to query records by matching criteria on associated records?

Let's say I have a bookshelf one to many relation Person => Cars, where
Pseudocode
# Person
hasMany: cars
id
name
# Car
belongsTo: person
id
make
Now I'd like to find all persons who own a 'Rover'.
What I'd was naively hoping for was something like this, which is obviously not working:
Person.query({ where: { cars: { make: 'Rover' } } } )
It looks like there is no really elegant solution. I solved my problem finally, by using joins.
Which looks like this:
Person.query((qb) => {
qb.join('cars', 'cars.person_id', 'person.id');
// no that the cars are joined,
// they are available for querying
qb.where({'cars.make': 'Rover'});
})
.then((result) => {
// do stuff
})
.catch((err) => {
// always catch errors
console.error(err);
});
It gets a little more complicated, when you want to query against the counterpart of a of many-to-many relation with pivot/intermediate table but maybe this helps to get started.
You need to use withRelated option to fetch a related model. Try this,
Person.forge()
.fetchAll({
withRelated: [{
cars: function(qb) {
qb.where({make: 'Rover'})
}
}]
})
.then(function(persons) {
//do something
});
More from the docs here

Trying to create an instance and multiple related instances in a many to many relationship

I am attempting to create an instance and multiple related instances with a many to many relation using a junction table.
While creating the multiple related instances, I need to add a value to a property on the junction table as well. I don't know if it is my lack of knowledge of sequelize or promises that is causing my problem.
The code I am using is below. This code does add the items to the database, but I need to redirect after the operation has completed, which is not working.
Basically, I need to create a Recipe. Once that is created, I need to create Ingredients and relate them to that Recipe. The ingredients are stored in an array coming from a form on an HTML page. While relating the Ingredients, I need to add the ingredient_quantity to the RecipeIngredients table, which is the through part of the relationship (the junction table).
global.db.Recipe.belongsToMany(
global.db.Ingredient,
{
as: 'Ingredients',
through: global.db.RecipeIngredients,
foreignKey: 'recipe_id'
});
global.db.Ingredient.belongsToMany(
global.db.Recipe,
{
as: 'Recipes',
through: global.db.RecipeIngredients,
foreignKey: 'ingredient_id'
});
router.post('/new', ensureLoggedIn, bodyParser.json(), function (req, res) {
var recipeName = req.body.recipe_name;
var steps = req.body.steps;
var ingredients = req.body.ingredients;
var ingredientQty = {};
var currentIngredient;
var ingredientsToAdd = [];
db.Recipe.create({
recipe_name: recipeName,
directions: steps,
FamilyId: req.user.FamilyId,
CreatedBy: req.user._id
})
.then(function (recipe) {
for (var i = 0; i < ingredients.length; i++) {
currentIngredient = ingredients[i];
ingredientQty[currentIngredient.ingredient_name] =
currentIngredient.quantity;
db.Ingredient.findOrCreate({
where: {
ingredient_name: currentIngredient.ingredient_name,
FamilyId: req.user.FamilyId
}
})
.spread(function (ingredient, created) {
if (created) {
console.log("Added Ingredient to DB: " +
currentIngredient.ingredient_name);
}
ingredient.Recipes = {
ingredient_quantity:
ingredientQty[ingredient.ingredient_name]
};
ingredient.CreatedBy = req.user._id;
recipe.addIngredient(ingredient)
.then(function () {
console.log("Added Ingredient " + ingredient.ingredient_name
+ " to Recipe " + recipe.recipe_name);
});
})
}
})
.finally(function(recipe){
res.redirect('/recipes');
});
});
Any help would be greatly appreciated. I know that I am running into issues because of trying to use promises inside of a loop, I just don't know how else I can accomplish this.
Using sequelize, you can create objects along with its associated objects in one step, provided all objects that you're creating are new. This is also called nested creation. See this link and scroll down to section titled "Creating with associations"
Coming to your issue, you've a many-to-many relationship between Recipe and Ingredient, with RecipeIngredients being the join table.
Suppose you've a new Recipe object which you want to create, like:
var myRecipe = {
recipe_name: 'MyRecipe',
directions: 'Easy peasy',
FamilyId: 'someId',
CreatedBy: 'someUserId'
}
And an array of Ingredient objects, like:
var myRecipeIngredients = [
{ ingredient_name: 'ABC', FamilyId: 'someId'},
{ ingredient_name: 'DEF', FamilyId: 'someId'},
{ ingredient_name: 'GHI', FamilyId: 'someId'}]
// associate the 2 to create in 1 step
myRecipe.Ingredients = myRecipeIngredients;
Now, you can create myRecipe and its associated myRecipeIngredients in one step as shown below:
Recipe.create(myRecipe, {include: {model: Ingredient}})
.then(function(createdObjects){
res.json(createdObjects);
})
.catch(function(err){
next(err);
});
And that is all !!
Sequelize will create 1 row in Recipe, 3 rows in Ingredient and 3 rows in RecipeIngredients to associate them.
I was able to fix the problem that I was having. The answers here helped me come up with my solution.
I am posting the solution below in case anyone else runs into a similar issue. I created a variable to store the Promise from Recipe.create(), I used Promise.map to findOrCreate all of the ingredients from the form data. Because findOrCreate returns an array containing Promise and a boolean for if the item was created, I then had to get the actual ingredients out of the results of the Promise.map function. So I used the JavaScript array.map() function to get the first item from the arrays. And finally, use Promise.map again to add each Ingredient to the Recipe
var ingredients = req.body.ingredients,
recipeName = req.body.recipeName,
ingredientsQty = {}; // Used to map the ingredient and quantity for the
// relationship, because of the Junction Table
var recipe = models.Recipe.create({recipe_name: recipeName});
// Use Promise.map to findOrCreate all ingredients from the form data
Promise.map(ingredients, function(ing){
ingredientsQty[ing.ingredient_name] = ing.ingredient_quantity;
return models.Ingredient.findOrCreate({ where: { ingredient_name: ing.ingredient_name}});
})
// Use JavaScript's Array.prototype.map function to return the ingredient
// instance from the array returned by findOrCreate
.then(function(results){
return results.map(function(result){
return result[0];
});
})
// Return the promises for the new Recipe and Ingredients
.then(function(ingredientsInDB){
return Promise.all([recipe, ingredientsInDB]);
})
// Now I can use Promise.map again to create the relationship between the /
// Recipe and each Ingredient
.spread(function(addedRecipe, ingredientsToAdd){
recipe = addedRecipe;
return Promise.map(ingredientsToAdd, function(ingredientToAdd){
ingredientToAdd.RecipeIngredients = {
ingredient_quantity: ingredientsQty[ingredientToAdd.ingredient_name]
};
return recipe.addIngredient(ingredientToAdd);
});
})
// And here, everything is finished
.then(function(recipeWithIngredients){
res.end
});

How do I query a document by a nested value in RethinkDB?

The ReThinkDB docs mention:
Documents can be filtered in a variety of ways—ranges, nested values, boolean conditions, and the results of anonymous functions.
Say I have:
{
name: 'Joe'
orders: [
{
id: 123456,
date: '2016-01-19T09:12:48.898000+00:00'
}
]
}
And I would like to retrieve users who have an order with id 123456 in their orders
Per the docs, I have tried using...
(long list of things cut out, now I've found the actual answer)
But I get no results.
This works too:
r.db('myapp').table('people').filter(function(person) {
return person('orders').map(function(order){
return order('id')
}).contains(123456)
})
Got it:
r.db('myapp').table('people').filter(function(person){
return person("orders").contains(function (order) {
return order('id').eq(123456);
})
})

Sequelize.js - Find on model

I have three PostgreSQL tables defined in Sequelize: AccountTypes, Accounts and Users.
Using a base AccountType, I want to find all Accounts (hasMany) that ALSO belongs to a certain user. The appropriate table associations are defined.
In order words, I want to turn this...
return db.instances.AccountType.getLedger('USD').then(ledger => {
return ledger.getAccounts().then(accounts => {
accounts.forEach(account => {
account.getUser() // <-- horribly inefficient
})
});
});
Into some like this...
// assume 'user' contains a retrieved User model
return db.instances.AccountType.getLedger('USD').then(ledger => {
return ledger.getAccounts({where: user}).then(accounts => {
// now 'accounts' will be filtered by Accounts.UserId === user.id
});
});
My model associations allow user.getAccounts(), but then I'm stuck with the same issue -- wanting to filter a record's associations by model, without resorting to raw SQL.
I can do an ugly/hacky include, like:
return db.instances.AccountType.getLedger('USD').then(ledger => {
return ledger.getAccounts({include:{model: db.models.User, where: {UserId: user.id}}}).then(accounts => {
// now 'accounts' is joined by user, but I've also loaded a bunch of fields I don't need
});
});
But this is intended to join another table, which I don't want. I just want to return any Accounts row where Accounts.UserID === user.id, without looking up any other table.
Is there any function in Sequelize where I could just throw at it a ready-made model, and it'll figure out which fields should wire up to which?

BookshelfJS limiting withRelated in FetchAll doesn't work

I have a hasMany to belongsTo relation between boxes and box_states.
I want to grab all my boxes with their latest state, so this is my query:
new Box()
.orderBy('id', 'ASC')
.fetchAll({
withRelated: [{
'states' : function(qb) {
qb.limit(1)
.orderBy('id', 'DESC');
}
}]
})
.then(function (boxes) {
res.json(boxes);
});
This returns all the boxes, but only the last box has one relation; everything has no relation.
Help would be appreciated.
Well, this works pretty much as expected. If you debug your solution, you'll see something like this (I'm giving you my example, which has the same relation as yours - one town, many addresses):
select "addresses".* from "addresses" where "addresses"."town_id" in (1, 2, 3, 4) group by "id" limit 1
What this does is basically select just ONE(!) related model (address) for ALL instances (the addresses.town_id in (1,2,3,4) segment).
So basically what you need to do is foreach the main results and go withRelated upon each.
new Town().fetchAll().then(function(towns) {
var my_towns_with_only_last_address = [];
Promise.map(towns.toJSON(), function(town) {
return new Town({
id: town.id
}).fetch({
withRelated: [{
'address': function(qb) {
qb.limit(1);
qb.orderBy('id', 'desc');
}
}]
}).then(function(result) {
return my_towns_with_only_last_address.push(result.toJSON());
});
}).then(function() {
res.json(my_towns_with_only_last_address);
});
});
I'm not sure if there are any better options... this works.
The best way to do this now (newest release of Bookshelf) is to use a Collection. Although withRelated is not available in .fetchAll(), it is available when using .fetch() on a Collection. See http://bookshelfjs.org/#Collection-instance-fetch

Resources