Using Objection JS How can I select specific columns with withGraphFetched - mysql

I have this question:
How can I indicate which columns I want get from database with the withGraphFetched method, I have a BelongsToOneRelation and I want exclude some columns, this is my model:
module.exports = class ProveedorModel extends Model {
...
static relationMappings = {
empresa: {
relation: Model.BelongsToOneRelation,
modelClass: EmpresaModel,
join: {
from: 'proveedor.empresa_id',
to: 'empresa.id'
}
}
};
...
}
and in my controller I have this:
const payload = await ProveedorModel.query().withGraphFetched('empresa');
but table empresa has to many columns which I won't, so how can I filter?

you can specify filter property for your relationship
class Person extends Model {
static relationMappings = {
pets: {
relation: Model.OneToManyRelation,
modelClass: Animal,
filter: query => query.select('id', 'ownerId', 'name'),
join: {
from: 'Person.id',
to: 'Animal.ownerId'
}
}
}
}
ref: https://github.com/Vincit/objection.js/issues/70#issuecomment-175143072
Just wondering why objection doesn't query only columns mapped in tableMetadata (https://vincit.github.io/objection.js/api/model/static-methods.html#static-tablemetadata) when used withGraphFetched like it does for withGraphJoined
Alternatively, you could map just the properties you want with parsedatabasejson
(https://vincit.github.io/objection.js/api/model/instance-methods.html#parsedatabasejson) but your SQL query will bring them all.

Related

Connecting 3 separate tables with a single through table in Sequelize

I'm trying to figure out why this is not working as intended. Any insights would be appreciated. Here's my situation:
I have a legacy connection of two tables with a through table. Something like:
Product model
const Product = sequelize.define('Product', {
// model attributes
});
Product.associate = (models) => {
Product.belongsToMany(models.Client, { through: models.ProductOwner, as: 'Owners'});
}
Client model
const Client = sequelize.define('Client', {
// model attributes
});
Client.associate = (models) => {
Client.belongsToMany(models.Product, { through: models.ProductOwner, as: 'OwnedProducts'});
}
ProductOwner
const ProductOwner = sequelize.define('Product', {
// no attributes
});
These form the N:M association with the through table ProductOwner.
This allows me, for example, to easily add a client to a product or getting all products that an existing client owns.
product.addOwner(client);
client.getOwnedProducts();
Now, I have the need to establish another chain of ownership to products that is unrelated to client. However, since this is still ownership, I would like to use the ProductOwner through table. So I add the new model:
Company model
const Company = sequelize.define('Company', {
// model attributes
});
Company.associate = (models) => {
Company.belongsToMany(models.Product, { through: models.ProductOwner, as: 'OwnedProducts'})
}
And the new association to the Products model. I also write a migration to add the CompanyId to the ProductOwners table and verify that the new reference is built into the database.
Product model
const Product = sequelize.define('Product', {
// model attributes
});
Product.associate = (models) => {
Product.belongsToMany(models.Client, { through: models.ProductOwner, as: 'Owners'});
Product.belongsToMany(models.Company, {
through: models.ProductOwner, as: 'CompanyOwners'
});
}
Now, on my code, I should be able to write:
product.addCompanyOwner(company);
company.getOwnedProducts();
And indeed, using the product instance method to add a new company does not throw any errors. However, the CompanyId column in the ProductOwners through table is still NULL.
Logging the query generated by Sequelize I see that the references to ProductId and ClientId are there, but there is no mention of CompanyId. Looks as if it is not recognizing that a new reference exists from Sequelize's point of view. However, the instance methods do work...
Which brings me to the question of why do they work? I assume that, by working, Sequelize is indeed creating the associations. But if that is the case, then why does the value for CompanyId is not set with the query?
Even writing it explicitly does not produce the expect result of setting CompanyId...
db.ProductOwner.create({
ProductId: 1,
ClientId: 1
}) // works to set all values
db.ProductOwner.create({
ProductId: 1,
CompanyId: 1
}) // sets ProductId to 1, but CompanyId is still NULL
What am I missing?

Returning Array of objects using RxJS map operator

I have one third Party API which returns data as below - It has been called from Angular Service using HttpClient.
const someObject = {
employees:[
{name:"XYZ",age:30},
{name:"ABC",age:28},
]
}
Now I have one interface with structure as below -
interface EmployeeData{
campus:{
property1:string;
property2:string;
};
details:{
name:string;
age:number
}
}
So EmployeeData.details exactly mimics someObject.employees structure
Now I want to return data from my service as below -
getData():Observable<EmployeeData[]>{
}
So , employees array from someObject should map to EmployeeData.details
and this should be returned as EmployeeData[].
How can I achieve this ?
I have tried below approach but it is giving different results.
getData():Observable<EmployeeData[]> {
return this.http.get<any>(url).pipe(
tap(value => console.log(value)),
map(data => data.employees),
map(employees =>{
return employees.map(employee =>{
return{
details:{
name:employee.name,
age:employee.age,
}
}
}
}
)
}
What it returns is -
details:{}
details:{}
But what I want is :
{
details:{}
}
{
details:{}
}
Can anybody please help here ?
If you want to type the function with Observable<EmployeeData>, your returned Observable must contain an array of objects implementing the EmployeeData interface, meaning that you will need to define them fully, not just the details property.
If you can't define all properties at this time, you have to use a type assertion with the as keyword. Be aware that you will lose type safety and might run into errors because missing properties will be undefined.
getData(): Observable<EmployeeData[]> {
return this.http.get<any>(url)
.pipe(
map(data => data.employees),
map(employees => employees.map(employee => ({ details: employee } as EmployeeData)))
);
}
See it in action here : https://stackblitz.com/edit/angular-msd6ym

How to use map(ES6) to return a list of functions?(ES6)

How can I use map to return this output
get funcone() { return this.Form.get(funcone"); }
get functwo() { return this.Form.get("functwo"); }
get functhree() { return this.Form.get("functhree"); }
get funcfour() { return this.Form.get("funcfour"); }
I used this array
FormValues=['funcone','functwo','functhree','funcfour'];
And this map
FormValues.map(Value=>{
get Value() { return this.Form.get(Value); }
})
I would appreciate any help!
Presumably you want to define those functions as getters on some object, or class. Let's assume it's a class.
That syntax can't work - it creates a Value getter, rather than funcone getter. Now, while you can define a getter using a variable for the getter's name:
let propName = "foo";
class Foo {
get [propName]() { return "You're getting a foo"; }
}
new Foo().foo
// => "You're getting a foo"
as far as I know, there's no way to make a loop inside the class declaration, nor a way to keep reopening a class and adding new stuff to it like in Ruby, so class definition inside a loop also won't work.
However, class syntax is just a sugar for the older prototypal inheritance, so everything we can do with the class syntax, we can also do without it (though vice versa does not hold). In order to add new stuff to the class, we just need to stick it to the class's prototype object. We can explicitly define a getter method using Object.defineProperty.
class Foo {
constructor() {
this.Form = {
one: 1,
two: 2,
three: 3,
four: 4
}
}
}
let props = ['one', 'two', 'three', 'four'];
props.forEach(propName =>
Object.defineProperty(Foo.prototype, propName, {
get: function() { return this.Form[propName]; }
})
);
new Foo().three
// => 3
It would be almost the same code to give the getters to an object rather than a class; you'd just be defining properties on the object itself, rather than on a prototype.

GraphQL - return calculated type dependent on argument

Overview (simplified):
In my NodeJS server I've implemented the following GraphQL schema:
type Item {
name: String,
value: Float
}
type Query {
items(names: [String]!): [Item]
}
The client query then passes an array of names, as an argument:
{
items(names: ["total","active"] ) {
name
value
}
}
The backend API queries a mysql DB, for the "total" and "active" fields (columns on my DB table) and reduces the response like so:
[{"name":"total" , value:100} , {"name":"active" , value:50}]
I would like my graphQL API to support "ratio" Item, I.E: I would like to send the following query:
{
items(names: ["ratio"] ) {
name
value
}
}
or
{
items(names: ["total","active","ratio"] ) {
name
value
}
}
And return active / total as the calculated result of that new field ([{"name":"ratio" , value:0.5}]). What would be a generic way to handle the "ratio" field differently?
Should it be a new type in my schema or should I implement the logic in the reducer?
Joe's answer (append {"name":"ratio" , value:data.active/data.total} to the result once the result is fetched from database) would do it without making any schema changes.
As an alternative method or as a more elegant way to do it in GraphQL, the field names can be specified in the type itself instead of passing them as arguments. And compute ratio by writing a resolver.
So, the GraphQL schema would be:
Item {
total: Int,
active: Int,
ratio: Float
}
type Query {
items: [Item]
}
The client specifies the fields:
{
items {
total
active
ratio
}
}
And ratio can be calculated inside the resolver.
Here is the code:
const express = require('express');
const graphqlHTTP = require('express-graphql');
const { graphql } = require('graphql');
const { makeExecutableSchema } = require('graphql-tools');
const getFieldNames = require('graphql-list-fields');
const typeDefs = `
type Item {
total: Int,
active: Int,
ratio: Float
}
type Query {
items: [Item]
}
`;
const resolvers = {
Query: {
items(obj, args, context, info) {
const fields = getFieldNames(info) // get the array of field names specified by the client
return context.db.getItems(fields)
}
},
Item: {
ratio: (obj) => obj.active / obj.total // resolver for finding ratio
}
};
const schema = makeExecutableSchema({ typeDefs, resolvers });
const db = {
getItems: (fields) => // table.select(fields)
[{total: 10, active: 5},{total: 5, active: 5},{total: 15, active: 5}] // dummy data
}
graphql(
schema,
`query{
items{
total,
active,
ratio
}
}`,
{}, // rootValue
{ db } // context
).then(data => console.log(JSON.stringify(data)))
You could set your resolver function up so it uses the second parameter - the arguments - to see if the name "ratio" is in your names array:
resolve: (root, { names }, context, fieldASTs) => {
let arrayOfItems;
// Contact DB, populate arrayOfItems with your total / active items
// if 'ratio' is within your name array argument, calculate it:
if (names.indexOf("ratio") > -1){
// Calculate ratio
arrayOfItems.push({ name: "ratio", value: calculatedRatio });
}
return(arrayOfItems);
}
I hope I understood your question correctly

Laravel many-to-many relationship OrderBy

I have 2 models in a Many to Many relationship. Let's say User and Role.
I want to sort my users based on the ASC/DESC of a field in Role.
My User & Role classes:
class User extends Model
{
public function roles()
{
return $this->belongsToMany('App\Role','role_user');
}
class Role extends Model
{
public function users()
{
return $this->belongsToMany('App\User','role_user');
}
I can sort the roles in each user but I cant sort the users
$query = User::with(array('roles'=> function ($query)
{
$query->select('role_name')->orderBy('role_name','asc');
}))->get();
I have also tried:
$query = User::with(roles)->orderBy('role_name','asc')->get();
But the error says column role_name does not exist.
Ideal result should look like this:
[
{
user_id:6
roles: [
"Admin",
"Baby"
]
},
{
user_id:2
roles: [
"Baby"
]
},
{
user_id:11
roles: [
"Baby",
"Cowboy"
]
}
]
I'd appreciate any help.
As user can have many roles i think you can concatenate role names and then order users by concatenated string. Try this:
User::selectRaw('group_concat(roles.name order by roles.name asc) as role_names, users.id')->
join('role_user','users.id','=','role_user.user_id')->
join('roles', 'roles.id','=','role_user.role_id')->
groupBy('user_id')->
orderBy('role_names','desc')->
get()
Please try the below modification in roles() function in User Class and then fetch it with User.
class User extends Model
{
public function roles()
{
return $this->belongsToMany('App\Role','role_user')
->selectRaw('id,'role_name')
->orderby('role_name');
}
}
$query = User::with(roles)->get();
Hope this will be useful for you.