Mikro-Orm - ManyToMany Relationship how can I delete reference in PivotTable? - orm

I'm using NestJS with mikro-Orm and have a weird behaviour on all of my manyToMany relations.
#ObjectType()
#Entity()
export class Realty {
#Field(() => ID)
#PrimaryKey({ columnType: "uuid" })
id: string = v4();
#Field(() => [Contact])
#ManyToMany(() => Contact, (contact) => contact.realties)
contacts: Collection<Contact>;
}
#ObjectType()
#Entity()
export class Contact {
#Field(() => ID)
#PrimaryKey({ columnType: "uuid" })
id: string = v4();
#Field(() => [Realty])
#ManyToMany(() => Realty, (realty) => realty.contacts, { owner: true })
realties: Collection<Realty>;
}
When I want to delete a realtyReference from a contact, that works fine and the row from the Contact_Realty PivotTable gets removed. But when I try to delete a contactReference from a realty, nothing happens. Does that only work on the owning side?
ContactsService (works):
async update(updateContactInput: UpdateContactInput) {
const { id, realtyIds } = updateContactInput;
const contact = await this.findOneOrFail(id);
const updated = this.contactsRepository.assign(contact, {
realties: await this.realtiesService.find(realtyIds),
});
await this.contactsRepository.persistAndFlush(updated);
return updated;
}
RealtiesService (returns correct updated entity but doesnt remove row in PivotTable):
async update(updateRealtyGeneralInput: UpdateRealtyGeneralInput) {
const { id, contactIds } = updateRealtyGeneralInput;
const realty = await this.realtiesService.findOneOrFail(id);
const updated = this.realtiesRepository.assign(realty, {
contacts: await this.contactsService.find(contactIds),
});
await this.realtiesRepository.persistAndFlush(updated);
return updated;
}
Both return the correct updated entity but only the ContactsService actually removes the row in the pivotTable.
Would really appreciate some help, thanks alot!
I want to remove one or more contacts from a realty and cannot get it to work. Am I doing something wrong?

You always need to have the owning side of your M:N collection initialized/populated, which you apparently don't in the second example. In your case it is Contact.realties, so if you want to manipulate this collection from the inverse side, all the entities you add/remove from the inverse need to have the owning side populated. Only owning side is what is taken into account when computing changesets. I will need to revisit this a bit, we might be able to improve on this thanks to the recent changes like the reference updates added in v5.5.
Also, there is some misunderstanding in your code. assign mutates the parameter, it does not return "modified entity", it mutates the one you pass in the first argument. If that entity is already managed (as in your case), there is no point in re-persisting it again, just flush.
async update(updateRealtyGeneralInput: UpdateRealtyGeneralInput) {
const { id, contactIds } = updateRealtyGeneralInput;
const realty = await this.em.findOneOrFail(Realty, id);
this.realtiesRepository.assign(realty, {
contacts: await this.em.find(Contact, contactIds, { populate: ['realties'] }),
});
await this.em.flush(updated);
return realty;
}

Related

How to get a collection from firestore using data from another collection in react-Native.....What Am i Doing Wrong?

I have tried searching everywhere, from stackoverflow to GitHub but i can get a solution. I am trying to get list of users by using their userid that I get from a collection of businesses. What Am i doing wrong?
componentWillMount() {
//Loading all the business collections.
firebase.firestore().collection("business").onSnapshot((snapshot) => {
var bizs = [];
snapshot.forEach((bdt) => {
var userdt = [];
//get document id of a certain user in the business collections
firebase.firestore().collection('users').where("userid", "==", bdt.data().userid).get()
.then((snap) => {
snap.forEach(dc => {
//loading details of the user from a specific ID
firebase.firestore().collection("users").doc(dc.id).onSnapshot((udt) => {
userdt.push({
name: udt.data().fullname,
photourl: udt.data().photoURL,
location: bdt.data().location,
openhrs: bdt.data().openHrs,
likes: '20',
reviews: '3002',
call: bdt.data().contacts
});
console.log(userdt); //this one works
})
console.log(userdt); // but this one doesnt diplay anything just []
})
}).catch((dterr) => {
console.log(dterr)
})
});
this.setState({bizdata: bizs,loading: false
});
});
}
I am using react-native and firestore
Put log with some number,
like,
console.log('1',userdt);
console.log('2',userdt);
and check weather which one is appearing first, Maybe '2' is executing before updating the data

Using map to reduce in Gun

I am new to Gun. I have existing code that very effectively reduces an array of objects based on a pattern. I am thinking I should tweak this to run in the context of Gun's .map and return undefined for non-matches. I think I will also have to provide two arguments, one of which is the where clause and the other the properties I want shown on returned objects. I also presume that if I use .on future matches will automagically get spit out! Am I on the right path?
const match = (object,key,value) => {
const type = typeof(value);
if(value && type==="object") {
return Object.keys(value).every(childkey =>
match(object[key],childkey,value[childkey]));
if(type==="function") return value(object[key]);
return object[key]===value;
}
const reduce = (objects,where) => {
const keys = Object.keys(where);
return objects.reduce((accumulator,current) => {
if(keys.every(key => match(current,key,where[key]))) {
accumulator.push(current);
}
return accumulator;
},[]);
}
let rows = reduce([{name: "Joe",address:{city: "Seattle"},age:25},
{name: "Mary",address:{city: "Seattle"},age:16},
{name: "Joe",address:{city: "New York"},age:20}],
{name: () => true,
address: {city: "Seattle"},
age: (age) => age > 10});
// results in
[{name: "Joe",address:{city: "Seattle"},age:25},
{name: "Mary",address:{city: "Seattle"},age:16}]
Further exploration of this resulted in the code below, which is stylistically different, but conforms to the immediate responsive nature of Gun. However, it is unclear how to deal with nested objects. The code below only works for primitives.
const match = (object,key,value) => {
const type = typeof(value);
if(!object || typeof(object)!=="object") return false;
if(value && type==="object") {
const child = gun.get(object[key]["#"]);
for(let key in value) {
const value = {};
child.get(key).val(v => value[key] = v,{wait:0});
if(!match(value,key,value[key])) return;
}
}
if(type==="function") return value(object[key]);
return object[key]===value;
}
const gun = Gun(["http://localhost:8080/gun"]),
users = [{name: "Joe",address:{city: "Seattle"},age:25},
{address:{city: "Seattle"},age:25},
{name: "Mary",address:{city: "Seattle"},age:16},
{name: "Joe",address:{city: "New York"},age:20}];
//gun.get("users").map().put(null);
for(let user of users) {
const object = gun.get(user.name).put(user);
gun.get("users").set(object);
}
gun.get("users").map(user => {
const pattern = {name: (value) => value!=null, age: (age) => age > 20}; //, address: {city: "Seattle"}
for(let key in pattern) {
if(!match(user,key,pattern[key])) return;
}
return user;
}).on(data => console.log(data));
Yes. GUN's .map method does more than what it seems.
Say we have var users = gun.get('users'). We can do:
users.map() with no callback acts like a forEach because the default callback is to return the data as-is.
users.map(user => user.age * 2) with a callback, it lets you transform the data like you would expect from a map, except where:
users.map(function(){ return }) if you return undefined, it will filter out that record.
WARNING: As of the current time, .map(transform) function is currently experimental and my have bugs with it. Please try it and report any you find.
Now we can combine it with some other methods, to get some cool behavior:
users.map().on(cb) will get current and future users as they are added to the table, and gets notified for updates on each of those users.
users.map().val(cb) will get current and future users as they are added to the table, but only gets each one once.
users.val().map().on(cb) gets only the current users (not future), but gets the updates to those users.
users.val().map().val(cb) gets only the current users (not future), and only gets them once.
So yes, you are on the right track. For instance, I have a test in gun core that does this:
list.map(user => user.age === 27? user.name + "thezombie" : u).on(function(data){
// verify
});
list.set({name: 'alice', age: 27});
list.set({name: 'bob', age: 27});
list.set({name: 'carl', age: 29});
list.set({name: 'dave', age: 25});
This creates a live map that filters the results and locally (view only) transforms the data.
In the future, this is how the SQL and MongoDB Mango query extensions will work for gun.
Note: GUN only loads the property you request on an object/node, so it is bandwidth efficient. If we do users.map().get('age') it will only load the age value on every user, nothing else.
So internally, you can do some efficient checks, and if all your conditionals match, only /then/ load the entire object. Additionally, there are two other options: (1) you can use an in-memory version of gun to create server-side request-response patterns, so you can have server-side filtering/querying that is efficient. (2) if you become an adapter developer and learn the simple wire spec and then write your own custom query language extensions!
Anything else? Hit me up! More than happy to answer.
Edit: My reply in the comments, comments apparently can't have code. Here is pseudo-code of how to "build up" more complex queries, which will be similar to how SQL/Mango query extensions will work:
mutli-value & nested value matching can be "built up" from this as the base, but yes, you are right, until we have SQL/Mango query examples, there isn't a simple/immediate "out of the box" example. This is pseudo code, but should get the idea across:
```
Gun.chain.match = function(query, cb){
var gun = this;
var fields = Object.keys(query);
var check = {};
fields.forEach(function(field){
check[field] = true;
gun.get(field).val(function(val){
if(val !== query[field]){ return }
check[field] = false;
//all checks done?
cb(results)
});
});
return gun;
}
```
Solution, the trick is to use map and not val:
Gun.chain.match = function(pattern,cb) {
let node = this,
passed = true,
keys = Object.keys(pattern);
keys.every(key => {
const test = pattern[key],
type = typeof(test);
if(test && type==="object") {
node.get(key).match(test);
} else if(type==="function") {
node.get(key).map(value => {
if(test(value[key])) {
return value;
} else {
passed = false;
}
});
} else {
node.get(key).map(value => {
if(value[key]===test) {
return value;
} else {
passed = false;
}
});
}
return passed;
});
if(passed && cb) this.val(value => cb(value))
return this;
}
const gun = new Gun();
gun.get("Joe").put({name:"Joe",address:{city:"Seattle"},age:20});
gun.get("Joe").match({age: value => value > 15,address:{ city: "Seattle"}},value => console.log("cb1",value));

Fetching records corresponding to a user, when queried from admin role/user which has access to all records

I need to fetch a particular class (say Class A) records corresponding to each user in my parse server, when queried with admin role which have access to all the records in that particular class (Class A).
How can I do that?
Quick help would be greatly appreciated. :-)
I'm assuming that you want these records on the client, but the client doesn't have "permission" to get all class a records?
If I've got the problem right, then here's a solution. Create a cloud code function that can use the master key to query objects of class a.
// this is the cloud function that you can call with
// whichever client SDK you are using....
const fetchClassA = function (request, response) {
const result = [];
const userId = request.params.fetchForUser;
// the test here should be against role, just an example....
if (request.user.get('username') !== 'admin') {
response.error('you are not authorized.');
return;
}
if (!userId) {
response.error('no user supplied');
return;
}
const user = new Parse.User();
user.id = userId;
new Parse.Query('ClassA')
.equalTo('user', user)
// depending on the use case, you may want to use
// find here instead?
.each((object) => {
result.push(object);
}, { useMasterKey: true })
.then(() => response.success(result))
.catch(response.error);
}
// the rest of this is just a unit test to "lightly" test
// our cloud function....
describe('fetch record with a cloud function', () => {
const userA = new Parse.User();
const userB = new Parse.User();
beforeEach((done) => {
userA.setUsername('userA');
userA.setPassword('abc');
userB.setUsername('userB');
userB.setPassword('def');
Parse.Object.saveAll([userA, userB])
.then(() => Parse.Object.saveAll([
new Parse.Object('ClassA').set('user', userA),
new Parse.Object('ClassA').set('user', userA),
new Parse.Object('ClassA').set('user', userA),
new Parse.Object('ClassA').set('user', userB),
new Parse.Object('ClassA').set('user', userB),
new Parse.Object('ClassA').set('user', userB),
]))
.then(() => Parse.User.signUp('admin', 'foo'))
.then(done)
.catch(done.fail);
});
it('should fetch class a', (done) => {
Parse.Cloud.define('fetchClassA', fetchClassA);
Parse.Cloud.run('fetchClassA', { foo: 'bar', fetchForUser: userA.id })
.then(result => expect(result.length).toBe(3))
.then(done)
.catch(done.fail);
});
});

Vue.js Vuex - Delete in Database not reflected in view

I have a number of companies in the Vuex Store. Each company object has several objects within it ... to simplify for my question, imagine
company{ id: 1, approval_notes: [{id: 1}, {id: 2}, {id: 3}], fins:
{total_rev: 10000} }
I want to remove the approval_note with id of 2 from the company with the id of 1.
In the view, I have
deleteNote(obj) {
if(confirm('Are you sure you want to delete this note?')) {
let path = '/api/approval_notes/' + obj.id;
axios.delete(path)
.then(function(rsp) {
this.$store.dispatch('delete_approval_note', obj);
}.bind(this))
.catch(function (err) {
console.log('AXIOS ERR', err);
}.bind(this));
}
}
The action is
export const delete_approval_note = ({commit}, payload) => {
commit('DELETE_APPROVAL_NOTE', payload);
};
And the mutation is
export const DELETE_APPROVAL_NOTE = (state, payload) => {
_.each(state.companies, function(co) {
if(co.id = payload.company_id) {
let notes = co.approval_notes;
notes.splice(notes.indexOf(payload), 1);
}
});
};
The note is deleted from the database but the view does not update without a page refresh.
This type of thing I will be doing a bunch so it's important to me to understand this process since I obviously don't get it right now.
Any help is appreciated.
Instead of IndexOf, using
notes.findIndex(obj => obj.id == payload.id)
It can get tricky when using IndexOf with complex object, I mostly rely on indexOf if dealing with simple values (integers, strings, booleans)

How do I run multiple queries in sailsjs controller?

It seems in sailsjs you can only run and pass one set of query data at a time. For example here is the controller for my homepage:
module.exports = {
index: function (req, res) {
Blog.find()
.limit(3)
.sort('createdAt desc')
.where({ isPublished: 1 })
.exec(function(err, posts) {
if (err) return next(err);
res.view({
layout: "homeLayout",
posts:posts
});
});
}
};
How would I query data from some other model and pass it to my view along with the blog data Im already passing?
You can use Promises to do so. It's actually an excellent usecase.
I use Q, which is what Waterline (Sail's ORM) use behind the scene.
You can see below an example of code where I retrieve data from a first model, and then, using the data I retrieved, I query other models to get some more data (in parallel), and in the end, I send the result back to the view.
SomeModel.findOne(criterias).then(function(result) {
Q.all([
SomeOtherModel.getSomething(result),
YetAnotherModel.getSomethingElse(result)
]).spread(function(someOtherResult, yetAnotherResult) {
var data = {
thing: result,
stuff: someOtherResult,
otherthing: yetAnotherResult
};
return res.view(data);
});
}).fail(function(reason) {
return res.view(reason);
});
The getSomething() function should return a promise, standard finder from Sails will work transparently (just don't pass the callback). As per this other question it appears that standard finder do not behave exactly like Q promises, the answer I gave there should help get a more consistant behavior.
More on Q and how it works in the doc !
You could also use async.auto (see below). Here's a link to the complete sails repo example.
var async = require('async'),
_ = require('lodash');
module.exports = {
index: function (req, res) {
async.auto({
// Get the blog posts
posts: function (cb) {
Blog.find()
.where({ isPublished: 1 })
.limit(5)
.sort('createdAt DESC')
.exec(cb);
},
// Get some more stuff
// (this will happen AT THE SAME TIME as `posts` above)
otherThings: function (cb) {
OtherThing.find()
.limit(30)
.exec(cb);
},
// Get comments
// (we'll wait until `posts` is finished first)
comments: ['posts', function (cb, async_data) {
// Get `posts`
// (the second argument to cb() back in `posts`)
// Used map to make sure posts are an array of ids and not just an object.
var posts = async_data.posts.map(function (item){ return item.id});
// Get comments that whose `post_id` is equal to
// the id of one of the posts we found earlier
Comment.find()
.where({ post_id: posts })
.exec(cb);
}]
},
function allDone (err, async_data) {
// If an error is passed as the first argument to cb
// in any of the functions above, then the async block
// will break, and this function will be called.
if (err) return res.serverError(err);
var posts = async_data.posts;
var comments = async_data.comments;
var otherThings = async_data.otherThings;
// Fold the comments into the appropriate post
// An in-memory join
_.map(posts, function (post) {
var theseComments =
_.where(comments, { post_id: post.id });
post.comments = theseComments;
});
// Show a view using our data
res.json({
// layout: 'homeLayout',
posts: posts,
otherThings: otherThings
});
});
}
};
I have figured out a few ways to accomplish this. The first way is to nest your queries, eg.
Blog.find()
.limit(30)
.sort('createdAt desc')
.where({ isPublished: 1 })
.exec(function(err, posts) {
SomeOtherModel.find()
.limit(5)
.sort('createdAt desc')
.where({ isPublished: 1 })
.exec(function(err, otherdata) {
res.view({
posts: posts,
otherdata: otherdata
});
});
});
The second way is to use promises (I wasnt aware of this previously)
User.findOne()
.where({ id: 2 })
.then(function(user){
var comments = Comment.find({userId: user.id}).then(function(comments){
return comments;
});
return [user.id, user.friendsList, comments];
}).spread(function(userId, friendsList, comments){
// Promises are awesome!
}).fail(function(err){
// An error occured
})
The third way (I ended up going with this) is to create a policy (specific to sailsjs but is express middleware)
// saved as /api/policies/recentPosts.js
// also need to add a rule to /config/policies.js
module.exports = function (req, res, ok) {
Blog.find()
.limit(3)
.sort('createdAt desc')
.where({ isPublished: 1 })
.exec(function(err, footerposts) {
res.footerposts = footerposts;
return ok();
});
};
Doing it this way you dont need to pass anything to your view however Im not sure if its good practice to randomly add data to the response object.
So here is how you can make 3 requests and pass all their data into your view:
first install Q
npm install q
Then use code below and substitute my models with yours:
// first import Q
var Q = require('q');
// Let's combine results of 3 queries
Q.all([
// let's find one user with name "Pavel"
User.findOne({name: 'Pavel'}).then(),
// let's find one Lexus car
Cars.findOne({brand: 'Lexus'}).then(),
// Finally let's get the first Apple phone
Phones.findOne({brand: 'Apple'}).then()
])
.spread(function (user, car, phone) {
// Output results as json, but you can do whatever you want here
res.json([user, car, phone]);
}).fail(function (reason) {
// output reason of failure
res.json(reason);
});