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

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.

Related

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!

ExtJS 6.01 How to update tpl after store loaded

Let's say I have a component with tpl in HTML format, which has a variable named testname, default value is null.
A listener of beforerender calls to a function in viewController, and in this function, I need to load a store and a callback function to update the variable testname in the view, based on the store record returned.
The problem I met is the order of code execution. The function in the viewController always ended first before the store.load callback get executed. Therefore the front view will never get updated. I have tried to setup the asynchronousLoad to false but it doesn't help.
Here is the code in the viewController.
onViewRendering: function(theView) {
console.log('setp 1');
var theStore = Ext.create('App.store.Test');
theStore.load({
asynchronousLoad: false,
callback: function(record) {
console.log('setp 2');
if (record) {
theView.data.testname = 'updated';
}
}
});
console.log('setp 3');
}
Console log displays in step 1, 3, 2.
And here is the code in the View:
Ext.define('App.view.TestView', {
extend: 'Ext.component',
data:{
testname:null
},
tpl:'<p>{testname}</p>',
listeners: {
beforerender: 'onViewRendering'
}
})
Here is the store code:
Ext.define('App.store.Test', {
extend: 'Ext.data.Store',
alias: 'store.test',
autoLoad: true,
remoteFilter: true,
fields: ['id', 'name'],
proxy: {
type: 'direct',
directFn: 'test.getTest'
}
})
I am new to Extjs and really need some help here, thanks in advance!
To update tpl after store load, you have to call setData method like below:
Code snippet:
onViewRendering: function(theView) {
console.log('setp 1');
var theStore = Ext.create('App.store.Test');
theStore.load({
asynchronousLoad: false,
callback: function(record) {
console.log('setp 2');
if (record) {
theView.setData({testname: 'updated'}); //setData method
}
}
});
console.log('setp 3');
}

Meteor, creating update form and filling fields

I have the following structure:
{
id: 23423-dsfsdf-32423,
name: Proj1,
services: [
{
id:sdfs-24423-sdf,
name:P1_Service1,
products:[{},{},{}]
},
{
id:sdfs-24jhh-sdf,
name:P1_Service2,
products:[{},{},{}]
},
{
id:sdfs-2jnbn3-sdf,
name:P1_Service3,
products:[{},{},{}]
}
]
},
{
id: 23423-cxcvx-32423,
name: Proj2,
services: [
{
id:sdfs-xvxcv-sdf,
name:P2_Service1,
characteristics:[{},{},{}]
},
{
id:sdfs-xvwqw-sdf,
name:P2_Service2,
characteristics:[{},{},{}]
},
{
id:sdfs-erdfd-sdf,
name:P2_Service3,
characteristics:[{},{},{}]
}
]
}
I have no problem creating a form this schema an insert form with quickForm.
But I cant figure out (and tried to read every tutorial and instruction and nothing worked) how to create an update form with all fields filled and (need to expand and fill the services and the characteristics arrays also:
of course, as i said, in update i need the services and characteristics to expend to the right size with all the fields.
But if i could understand how to fill the form fields i could understand myself how to expend the arrays...
i've tried:
{{> quickForm collection="Projects" id="updateProjectForm" collection="Projects" type="method" class="update-project-form" doc=project }}
with:
import SimpleSchema from 'simpl-schema';
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
// Attaching the subscription to the template so we can reuse it
Template.ProjectSingle.onCreated(function(){
var self = this;
self.autorun(function(){
var id = FlowRouter.getParam('id');
self.subscribe('projectSingle', id);
});
});
Template.ProjectSingle.helpers({
project: ()=> {
var id = FlowRouter.getParam('id');
console.log(Projects.findOne({_id: id}));
return Projects.findOne({_id: id});
}
});
I can't even see the console.log() printing.
This solution at list didn't crash the meteor server... everything else i've tried crashed the server on many errors
Maybe i need to mention that i'm using partials so maybe there is a problem with the JS files but i don't think so as the onCreated method is being read.
10x.
EDIT:
I've removed the partial for the update template and it is now in the root Template with its own JS with the method:
projectDoc: ()=> {
var id = FlowRouter.getParam('id');
console.log("Update: " + Projects.findOne({_id: id}));
return Projects.findOne({_id: id});
}
Now i can see this method is being called but for some reason it is being called twice. First with the correct data and then getting undefined so i've still not getting the fields showing anything but if i could find why it is being called twice i will solve the first level form (no services and so on)
Solved it (Not sure this is the best way as i'm still having two calls to the method but this is working for now:
projectDoc: ()=> {
var id = FlowRouter.getParam('id');
if(Projects.findOne({_id: id}) != null){
console.log(Projects.findOne({_id: id}));
thisProject = Projects.findOne({_id: id});
return Projects.findOne({_id: id});
} else {
return thisProject;
}
}

Why I can see view1 after I triggering navigation to view2 (integration testing)

I have some questions about integration testing (OPA5, sapui5): I have created simple project which has 2 views (View1.view.xml and View2.view.xml), and I have sap.m.Button on the first view. After I press this button I'll navigate to the second view. So I have implemented OPA5 tests to check this functionality. So now I'm simulating user's click at button and Navigation is working properly. But If I want to check visibility View1.view.xml after triggering navigation I will get "test passed" result! I guess that tests are executed asynchronously that is why I have had this results. And how can I catch moment when test will finish to execute next?
UnexpectedbehaviorJourney.js
QUnit.module("Unexpected behavior");
opaTest("Why do I see View1.view after Navigation was pressed?",
function (Given, When, Then) {
// Arrangements
Given.iStartMyApp();
//Actions
When.onTheAppView.iLookAtTheScreen().
and.iPressNavigateButton();
// Assertions
Then.onTheAppView.iShouldSeeView1AfterTriggeringNavigation();
// and.iTeardownMyAppFrame();
});
UnexpectedBehavior.js
actions : {
iPressNavigateButton : function () {
return this.waitFor({
id : "idButton",
viewName : "View1",
actions: new Press(),
errorMessage : "Button control wasn't pressed"
});
}
},
assertions : {
iShouldSeeView1AfterTriggeringNavigation : function () {
return this.waitFor({
id : "idView1",
viewName : "View1",
success : function () {
ok( true, "Why I can see View1.view?");
},
errorMessage : "Navigation has worked as expected!"
});
}
}
View1.controller.js
return Controller.extend("InvestigateOPA.controller.View1", {
onPress: function() {
this.getOwnerComponent().getRouter().navTo("appView2");
}
});
Do you have any idea how can I prevent this behavior?
Shouldn't the assertion be a check for View2 visibility?
else try something like below
nb. you can use Qunit and Sinon functions like a delay/timer or a native promise etc.
iShouldNotSeeView1AfterTriggeringNavigation : function () {
return this.waitFor({
id: "app",
viewName: "App",
check: function(oApp) {
return oApp.getCurrentPage().getId() !== "__component0---View1"
},
success: function() {
ok( true, "View1 not current page");
},
errorMessage: "Navigation has not worked as expected!"
});
I have found that the one way to solve my problem is set "transition" property to "show":
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"viewType": "XML",
"transition": "show",
"viewPath": "InvestigateOPA.view",
"controlAggregation": "pages",
"controlId": "app"
},
In this case there is no visual delay when one view change another, so my OPA tests will work correctly.
p.s. thx a lot for your ideas!

In Meteor when trying to access an attribute, I get TypeError: Cannot read property in the console. But the site is working

When trying to read an attribute, meteor gives me a TypeError: Cannot read property 'featuredImage' of undefined error in the browser console. But it reads featuredImage and the site is working fine. How can I get rid of this error? Is it happening because my subscriptions are not yet ready? Is that's the case, how to fix it? (PS : Im using the flow router so I can't wait for subscriptions in the router)
My template code :
Template.About.helpers({
page: () => {
return findPage();
},
featuredImage: () => {
var thisPage = findPage();
return Images.findOne({
"_id": thisPage.featuredImage
});
}
});
function findPage() {
return Pages.findOne({
slug: 'about'
});
}
The router code :
FlowRouter.route('/about', {
name: 'about',
subscriptions: function() {
this.register('page', Meteor.subscribe('pages', 'about'));
this.register('image', Meteor.subscribe('images'));
},
action() {
BlazeLayout.render('MainLayout', {
content: 'About'
});
setTitle('About Us');
},
fastRender: true
});
The subscription is probably not ready yet. FlowRouter provides a utility for dealing with this, your helpers should look like this:
Template.About.helpers({
page: () => {
// If you only need a specific subscription to be ready
return FlowRouter.subsReady('page') && findPage() || null;
},
featuredImage: () => {
// Ensure ALL subscriptions are ready
if ( FlowRouter.subsReady() ) {
var thisPage = findPage();
return Images.findOne({
"_id": thisPage.featuredImage // Probably should be thisPage.featuredImage._id
});
}
return null;
}
});
However, for maximum performance, you should use if (FlowRouter.subsReady('page') && Flowrouter.subsReady('image')) rather than FlowRouter.subsReady() since if you have other pending subscriptions which are large, it will wait for those even though you don't need them.

Resources