NGRX/data how to modify / update additionalCollectionState - ngrx

The bounty expires in 3 days. Answers to this question are eligible for a +100 reputation bounty.
Parth Developer wants to draw more attention to this question.
I am facing one issue and unable to resolve:
I am using ngrx data with following:
const entityMetadata: EntityMetadataMap = {
forReports: {
entityDispatcherOptions: {
optimisticUpdate: true,
},
additionalCollectionState: {
selectedForReports: []
}
},
};
& registered:
export class ForReportsModule {
constructor(
private eds: EntityDefinitionService,
private entityDataService: EntityDataService,
private forReportsDataService: ForReportsDataService
) {
eds.registerMetadataMap(entityMetadata);
entityDataService.registerService("forReports", forReportsDataService);
}
}
Store:
What I want to achive is to simply modify selectedForReports in-store with a function.
for example:
function updateSelect(value) {
...
...
selectedForReports = value;
...
}
I tried to go through official docs of ngrx all stackoverflow post github posts but none of this works.

Related

Getting Cannot execute "delete" on "Article" in CASL JS

I'm learning CASL JS and trying to delete an article with a condition but getting this error Cannot execute "delete" on "Article". Here is the CodeSandBox Link.
Here is the sample code:
const { createMongoAbility, ForbiddenError } = require("#casl/ability");
const rules = [
{
action: "read",
subject: "Article"
},
{
inverted: true,
action: "delete",
subject: "Article",
conditions: { published: true },
reason: "You are not allowed to delete this article"
}
];
const ability = createMongoAbility(rules);
// this can be pulled from a database
class Article {
constructor(attrs) {
Object.assign(this, attrs);
}
}
const anotherArticle = new Article({
authorId: 2,
published: false,
content: "Lorem Ipsum"
});
try {
// checking ability before taking some action
ForbiddenError.from(ability).throwUnlessCan("delete", anotherArticle);
} catch (error) {
console.log(error.message); // throwing `Cannot execute "delete" on "Article"`
}
Please help me out. Thanks
The creator of CASL JS has answered this:
you declared that it's not possible to delete published articles but you have never said that it's possible to delete articles at all. That's why you get the error
So this means that I already declared inverted permission and it's not possible to delete articles.

Is there a way to show related model ids without sideloading or embedding data

My understanding is that using serializeIds: 'always' will give me this data, but it does not.
Here's what I'm expecting:
{
id="1"
title="some title"
customerId="2"
}
Instead the output I'm receiving is:
{
id="1"
title="some title"
}
My code looks something like this:
import {
Server,
Serializer,
Model,
belongsTo,
hasMany,
Factory
} from "miragejs";
import faker from "faker";
const ApplicationSerializer = Serializer.extend({
// don't want a root prop
root: false,
// true required to have root:false
embed: true,
// will always serialize the ids of all relationships for the model or collection in the response
serializeIds: "always"
});
export function makeServer() {
let server = newServer({
models: {
invoice: Model.extend({
customer: belongsTo()
}),
customer: Model.extend({
invoices: hasMany()
})
},
factories: {
invoice: Factory.extend({
title(i) {
return `Invoice ${i}`;
},
afterCreate(invoice, server) {
if (!invoice.customer) {
invoice.update({
customer: server.create("customer")
});
}
}
}),
customer: Factory.extend({
name() {
let fullName = () =>
`${faker.name.firstName()} ${faker.name.lastName()}`;
return fullName;
}
})
},
seeds(server) {
server.createList("invoice", 10);
},
serializers: {
application: ApplicationSerializer,
invoice: ApplicationSerializer.extend({
include: ["customer"]
})
},
routes() {
this.namespace = "api";
this.get("/auth");
}
});
}
Changing the config to root: true, embed: false, provides the correct output in the invoice models, but adds the root and sideloads the customer, which I don't want.
You've run into some strange behavior with how how serializeIds interacts with embed.
First, it's confusing why you need to set embed: true when you're just trying to disable the root. The reason is because embed defaults to false, so if you remove the root and try to include related resources, Mirage doesn't know where to put them. This is a confusing mix of options and Mirage should really have different "modes" that take this into account.
Second, it seems that when embed is true, Mirage basically ignores the serializeIds option, since it thinks your resources will always be embedded. (The idea here is that a foreign key is used to fetch related resources separately, but when they're embedded they always come over together.) This is also confusing and doesn't need to be the case. I've opened a tracking issue in Mirage to help address these points.
As for you today, the best way to solve this is to leave root to true and embed false, which are both the defaults, so that serializeIds works properly, and then just write your own serialize() function to remove the key for you:
const ApplicationSerializer = Serializer.extend({
// will always serialize the ids of all relationships for the model or collection in the response
serializeIds: "always",
serialize(resource, request) {
let json = Serializer.prototype.serialize.apply(this, arguments);
let root = resource.models ? this.keyForCollection(resource.modelName) : this.keyForModel(resource.modelName)
return json[root];
}
});
You should be able to test this out on both /invoices and /invoices/1.
Check out this REPL example and try making a request to each URL.
Here's the config from the example:
import {
Server,
Serializer,
Model,
belongsTo,
hasMany,
Factory,
} from "miragejs";
import faker from "faker";
const ApplicationSerializer = Serializer.extend({
// will always serialize the ids of all relationships for the model or collection in the response
serializeIds: "always",
serialize(resource, request) {
let json = Serializer.prototype.serialize.apply(this, arguments);
let root = resource.models ? this.keyForCollection(resource.modelName) : this.keyForModel(resource.modelName)
return json[root];
}
});
export default new Server({
models: {
invoice: Model.extend({
customer: belongsTo(),
}),
customer: Model.extend({
invoices: hasMany(),
}),
},
factories: {
invoice: Factory.extend({
title(i) {
return "Invoice " + i;
},
afterCreate(invoice, server) {
if (!invoice.customer) {
invoice.update({
customer: server.create("customer"),
});
}
},
}),
customer: Factory.extend({
name() {
return faker.name.firstName() + " " + faker.name.lastName();
},
}),
},
seeds(server) {
server.createList("invoice", 10);
},
serializers: {
application: ApplicationSerializer,
},
routes() {
this.resource("invoice");
},
});
Hopefully that clears things up + sorry for the confusing APIs!

Modify data in Firebase with Vuejs

I have just started learning Vuejs. After attempting to follow https://alligator.io/vuejs/vuefire-firebase/ (modifying data section at the bottom of the page), I tried to update information on Firebase and I get the error:
cannot read property child of undefined. How can I fix this?
<script>
import {linesRef} from '../../firebase'
export default {
firebase: {
lines: linesRef
},
data() {
return {
status: 'available'
}
},
methods: {
hold(key, e) {
if (confirm("Hold the line?")) {
function updateStatus(line, sold) {
linesRef.lines.child(line['.key'])
.child('status').set(sold)
}
e.currentTarget.style.backgroundColor = "yellow"
}
else{
e.currentTarget.style.backgroundColor = "transparent"
}
}
}
}
</script>
You have to write linesRef.child(line['.key']) to access the document you want

Firebase denormalizing many to many

I have a pretty basic data structure
events
topics
I would like to be able to easily show (query)
what topics are owned by an event
what events cover a topics
what are the most popular topics this month
I am pretty comfortable with my events structure like
/events/880088/topics.json *
["Firebase", "Cloud"]
but I struggle with how to structure the /topics nodes. I partially get the idea of going with something like
/topics/Firebase
{"12345":true,"88088":true}
and then if when I update an events's topic collection I would have to iterate over all the /topics/ nodes and update /topics/{{topic}}/{{eventid}} to {true | null}. Which seems rather ham fisted.
ALSO, then I am still at a loss of how to query say, what are the topics covered by events this month.
Example JSBin from comments below http://jsbin.com/dumumu/edit?js,output
* I know, I know, arrays are evil, https://www.firebase.com/blog/2014-04-28-best-practices-arrays-in-firebase.html, but I think they fit in this scenaris
Here's one way to add an event:
function addEvent(title, topics) {
var event =ref.child('events').push({ title: title });
topics.forEach(function(topic) {
event.child('topics').child(topic).set(true);
ref.child('topics').child(topic).child(event.key()).set(true);
});
}
Seems pretty simple for me. For an interesting twist, you can use the new multi-location updates we launched yesterday (September 2015):
function addEvent(title, topics) {
var updates = {};
var eventId = ref.push().key();
updates['events/'+eventId+'/title'] = title;
topics.forEach(function(topic) {
updates['events/'+eventId+'/topics/'+topic] = true;
updates['topic/'+topic+'/'+eventId] = true;
});
ref.update(updates);
}
The latter is a bit more code. But it's a single write operation to Firebase, so there's no chance of the user closing the app between write operations.
You invoke both the same of course:
addEvent('Learn all about Firebase', ['Firebase']);
addEvent('Cloudspin', ['Firebase', 'Google', 'Cloud']);
And the data structure becomes:
{
"events": {
"-K-4HCzj_ziHkZq3Fpat": {
"title": "Learn all about Firebase",
"topics": {
"Firebase": true
}
},
"-K-4HCzlBFDIwaA8Ajb7": {
"title": "Cloudspin",
"topics": {
"Cloud": true,
"Firebase": true,
"Google": true
}
}
},
"topic": {
"Cloud": {
"-K-4HCzlBFDIwaA8Ajb7": true
},
"Firebase": {
"-K-4HCzj_ziHkZq3Fpat": true,
"-K-4HCzlBFDIwaA8Ajb7": true
},
"Google": {
"-K-4HCzlBFDIwaA8Ajb7": true
}
}
}
Querying/reporting
With Firebase (and most NoSQL databases), you typically have to adapt your data structure for the reporting you want to do on it.
Abe wrote a great answer on this recently, so go read that for sure: Firebase Data Structure Advice Required
Update: change the topics for an event
If you want to change the topics for an existing event, this function is once way to accomplish that:
function updateEventTopics(event, newTopics) {
newTopics.sort();
var eventId = event.key();
var updates = {};
event.once('value', function(snapshot) {
var oldTopics = Object.keys(snapshot.val().topics).sort();
var added = newTopics.filter(function(t) { return oldTopics.indexOf(t) < 0; }),
removed = oldTopics.filter(function(t) { return newTopics.indexOf(t) < 0; });
added.forEach(function(topic) {
updates['events/'+eventId+'/topics/'+topic] = true;
updates['topic/'+topic+'/'+eventId] = true;
});
removed.forEach(function(topic) {
updates['events/'+eventId+'/topics/'+topic] = null;
updates['topic/'+topic+'/'+eventId] = null;
});
ref.update(updates);
});
}
The code is indeed a bit long, but that's mostly to determine the delta between the current topics and the new topics.
In case you're curious, if we run these API calls now:
var event = addEvent('Cloudspin', Date.now() - month, ['Firebase', 'Google', 'Cloud']);
updateEventTopics(event, ['Firebase', 'Google', 'GCP']);
The changeEventTopics() call will result in this update():
{
"events/-K-93CxuCrFDxM6k0B14/topics/Cloud": null,
"events/-K-93CxuCrFDxM6k0B14/topics/GCP": true,
"topic/Cloud/-K-93CxuCrFDxM6k0B14": null,
"topic/GCP/-K-93CxuCrFDxM6k0B14": true
}

modifying angularFire resolve

I would like to modify the angularFire code below (taken from the docs:
https://www.firebase.com/docs/web/libraries/angular/guide.html#section-angular-authentication)
so that if the user is not logged in it will also log the user in before page loads and the user data will be ready to use straight away.
This is the original:
resolve: {
"currentUser": ["simpleLogin", function(simpleLogin) {
return simpleLogin.$getCurrentUser();
}]
}
and this is what I have so far:
resolve: {
"currentUser": ["simpleLogin", function(simpleLogin) {
return simpleLogin.$getCurrentUser();
}],
"loginUser": ["simpleLogin", function(simpleLogin) {
return simpleLogin.$login("anonymous", {rememberMe : true} );
}]
}
but this will cause the user to be logged in each time thus resetting the ID (I think?). How do I do it conditionally so that they are only logged in if not already?
Rather than utilizing two resolve methods, I'd just chain them together. Since $login returns a promise, this is pretty smooth sailing:
resolve: {
"currentUser": ["simpleLogin", function(simpleLogin) {
return simpleLogin.$getCurrentUser().then(function(user) {
if( user === null ) {
// log in now...
return simpleLogin.$login('anonymous', {rememberMe: true});
}
else {
// logged in!
return user;
}
});
}]
}

Resources