Add payload in Ember deleteRecord - ember.js

I have a requirement to include remarks from user in the payload whenever he tries to delete an item. So far, I have this:
let remarks = this.get('remarks');
let id = this.get('itemID');
this.store.findRecord('item', id).then(function (selectedItem) {
// TODO - DELETE doesn't accept payload in body?
selectedItem.destroyRecord({remarks:remarks}).then(function(response){
Ember.debug('delete successful:'+JSON.stringify(response));
Ember.$('#confirmDelete').modal('hide');
Ember.$('#remarks').val('');
context.set('successful', true);
context.set('message', context.get('i18n').t('success.role.delete'));
}).catch(function(error){
Ember.debug('delete failed:'+JSON.stringify(error));
Ember.$('#confirmDelete').modal('hide');
Ember.$('#remarks').val('');
context.send('showErrors', error);
});
});
It doesn't work. So does setting the remarks value in the model like:
...
this.store.findRecord('item', id).then(function (selectedItem) {
selectedItem.set('remarks', remarks);
selectedItem.destroyRecord().then(function(response){
...
I am trying to override the deleteRecord but I don't know where to start or how to do it.
Anyone have ideas? Thanks!

You can easily achieve this kind of behaviour by extending your application adapter with the following mixin:
/* app/mixins/delete-with-playload.js */
import Ember from 'ember';
export default Ember.Mixin.create({
deleteRecord(store, type, snapshot) {
var id = snapshot.id;
var data = {};
var serializer = store.serializerFor(type.modelName);
serializer.serializeIntoHash(data, type, snapshot);
return this.ajax(this.buildURL(type.modelName, id, snapshot, 'deleteRecord'), "DELETE", {
data
});
}
});
Then just add it to your application adapter
/* app/adapters/application.js */
import RestAdapter from 'ember-data/adapters/rest';
import DeleteWithPayloadMixin from '../mixins/delete-with-payload';
export default RestAdapter.extend(DeleteWithPayloadMixin);
This will result a payload identical to the payload of PUT method, meaning a payload of the form:
{
"<model-name>": {
// model's serialized attributes
}
}
Now all you have to do is to set the desired attributes on the record before deleting, and destroy the record.
model.setProperties({
deleteReason: 'whatever'
});
model.destroyRecord();
/*
results a DELETE request when requestBody is "{
"<model-name>": {
...
"deleteReason": "whatever"
...
}
}"
*/

Related

Trouble Writing to Jest Mocked Prisma Database

I have two databases that I need to interact with in my code. I have a simple function that takes an object and writes it to my PostgreSQL database using Prisma. I've tested the function with Postman, and it works perfectly, but when I try to execute it using a Jest mock (using the singleton pattern found in the Prisma unit testing guide), it returns undefined indicating that it didn't interact with the database and create the new record. Here's my code:
/prisma/clinical-schema.prisma
generator client {
provider = "prisma-client-js"
output = "./generated/clinical"
}
datasource clinicalDatabase {
provider = "postgresql"
url = "postgresql://postgres:postgres#localhost:5432/clinical-data?schema=public"
}
model pcc_webhook_update {
id Int #id #default(autoincrement())
event_type String
organization_id Int
facility_id Int
patient_id Int
resource_id String?
webhook_date DateTime #default(now()) #clinicalDatabase.Timestamptz(6)
status pcc_webhook_update_status #default(pending)
status_changed_date DateTime? #clinicalDatabase.Timestamptz(6)
error_count Int #default(0)
##unique([organization_id, facility_id, patient_id, resource_id, event_type, status])
}
enum pcc_webhook_update_status {
pending
processing
processed
error
}
/prisma/clinical-client.ts
import { PrismaClient } from './generated/clinical';
const prismaClinical = new PrismaClient();
export default prismaClinical;
/testing/prisma-clinical-mock.ts
import { PrismaClient } from '../prisma/generated/clinical';
import { mockDeep, mockReset, DeepMockProxy } from 'jest-mock-extended';
import prisma from '../prisma/clinical-client';
jest.mock('../prisma/clinical-client', () => ({
__esModule: true,
default: mockDeep<PrismaClient>()
}));
beforeEach(() => {
mockReset(prismaClinicalMock);
});
export const prismaClinicalMock = prisma as unknown as DeepMockProxy<PrismaClient>;
Everything up to this point follows the conventions outlined by the Prisma unit testing docs. The only modification I made was to make it database specific. Below is my function and tests. The request object in handle-pcc-webhooks.ts is a sample http request object, the body of which contains the webhook data I care about.
/functions/handle-pcc-webhooks/handler.ts
import prismaClinical from '../../../prisma/clinical-client';
import { pcc_webhook_update } from '../../../prisma/generated/clinical';
import { requestObject } from './handler.types';
export const handlePccWebhook = async (request: requestObject) => {
try {
const webhook = JSON.parse(request.body);
// if the webhook doesn't include a resource id array, set it to an array with an empty string to ensure processing and avoid violating
// the multi-column unique constraint on the table
const { resourceId: resourceIds = [''] } = webhook;
let records = [];
for (const resourceId of resourceIds) {
// update an existing record if one exists in the pending state, otherwise create a new entry
const record: pcc_webhook_update = await prismaClinical.pcc_webhook_update.upsert({
where: {
organization_id_facility_id_patient_id_resource_id_event_type_status: {
organization_id: webhook.orgId,
facility_id: webhook.facId,
patient_id: webhook.patientId,
resource_id: resourceId,
event_type: webhook.eventType,
status: 'pending'
}
},
update: {
webhook_date: new Date()
},
create: {
event_type: webhook.eventType,
organization_id: webhook.orgId,
facility_id: webhook.facId,
patient_id: webhook.patientId,
resource_id: resourceId,
status: 'pending' // not needed
}
});
records.push(record);
}
return records;
} catch (error) {
console.error(error);
}
};
/functions/handle-pcc-webhooks/handler.spec.ts
import fs from 'fs';
import path from 'path';
import MockDate from 'mockdate';
import { prismaClinicalMock } from '../../../testing/prisma-clinical-mock';
import { createAllergyAddRecord } from './__mocks__/allergy';
import { requestObject } from './handler.types';
import { handlePccWebhook } from './handler';
describe('allergy.add', () => {
let requestObject: requestObject;
let allergyAddRecord: any;
beforeAll(() => {
requestObject = getRequestObject('allergy.add');
});
beforeEach(() => {
MockDate.set(new Date('1/1/2022'));
allergyAddRecord = createAllergyAddRecord(new Date());
});
afterEach(() => {
MockDate.reset();
});
test('should create an allergy.add database entry', async() => {
prismaClinicalMock.pcc_webhook_update.create.mockResolvedValue(allergyAddRecord);
// this is where I would expect handlePccWebhook to return the newly created database
// record, but instead it returns undefined. If I run the function outside of this
// unit test, with the same input value, it functions perfectly
await expect(handlePccWebhook(requestObject)).resolves.toEqual([allergyAddRecord]);
});
});
// This just builds a request object with the current webhook being tested
function getRequestObject(webhookType: string) {
// read the contents of request object file as a buffer, then convert it to JSON
const rawRequestObject = fs.readFileSync(path.resolve(__dirname, '../../sample-data/handle-pcc-webhook-request.json'));
const requestObject: requestObject = JSON.parse(rawRequestObject.toString());
// read the contents of the webhook file as a buffer, then convert it to a string
const rawWebhook = fs.readFileSync(path.resolve(__dirname, `../../sample-data/${webhookType}.json`));
const webhookString = rawWebhook.toString();
// set the body of the request object to the contents of the target webhook
requestObject.body = webhookString;
return requestObject;
}
Finally, here is the result of running the unit test:
So after banging my had against the wall for a few hours, I figured out the issue. In my handler.spec.ts file, I had the following line:
prismaClinicalMock.pcc_webhook_update.create.mockResolvedValue(allergyAddRecord);
what that does is mock the value returned for any create functions run using Prisma. The issue is that my function is using an upsert function, which I wasn't explicitly mocking, thus returning undefined. I changed the above line to
prismaClinicalMock.pcc_webhook_update.upsert.mockResolvedValue(allergyAddRecord);
and it started working.

Unit testing sessionStorage value in emberJS

I'm new to ember and trying to figure out how to unit test, using sinon, the sessionStorage based on url parameters when that page is visited. I've tried a few things but still can't get the desired result. It passes even if I change the 'sessionValue' without editing the query param.
Thank you in advance.
ember component
beforeModel(transition) {
//transition will contain an object containing a query parameter. '?userid=1234' and is set in the sessionStorage.
if(transition.queryparam.hasOwnProperty('userid')){
sessionStorage.setItem('user:id', transition.queryparam)
}
}
Ember test
test('Session Storage contains query param value', async assert => {
let sessionKey = "user:id";
let sessionValue = "1234"
let store = {};
const mockLocalStorage = {
getItem: (key) => {
return key in store ? store[key] : null;
},
setItem: (key, value) => {
store[key] = `${value}`;
},
clear: () => {
store = {};
}
};
asserts.expect(1);
let spy = sinon.spy(sessionStorage, "setItem");
spy.calledWith(mockLocalStorage.setItem);
let stub = sinon.stub(sessionStorage, "getItem");
stub.calledWith(mockLocalStorage.getItem);
stub.returns(sessionValue);
await visit('/page?userid=1234');
mockLocalStorage.setItem(sessionKey, sessionValue);
assert.equal(mockLocalStorage.getItem(sessionKey), sessionValue, 'storage contains value');
})
Welcome to Ember!
There are many ways to test, and the below suggestion is one way (how I would approach interacting with the SessionStorage).
Instead of re-creating the SessionStorage API in your test, how do you feel about using a pre-made proxy around the Session Storage? (ie: "Don't mock what you don't own")
Using: https://github.com/CrowdStrike/ember-browser-services/#sessionstorage
Your app code would look like:
#service('browser/session-storage') sessionStorage;
beforeModel(transition) {
// ... details omitted ...
// note the addition of `this` -- the apis are entirely the same
// as SessionStorage
this.sessionStorage.setItem('user:id', ...)
}
then in your test:
module('Scenario Name', function (hooks) {
setupApplicationTest(hooks);
setupBrowserFakes(hooks, { sessionStorage: true });
test('Session Storage contains query param value', async assert => {
let sessionKey = "user:id";
let sessionValue = "1234"
let sessionStorage = this.owner.lookup('browser/session-storage');
await visit('/page?userid=1234');
assert.equal(sessionStorage.getItem(sessionKey), '1234', 'storage contains value');
});
})
With this approach, sinon isn't even needed :)

RESTSerializer extend not affecting Ember data objects

JSON from Sails responses don't have root elements. Ember data requires that the JSON be wrapped in a root element with the same name as the object. The plan is to extend RESTSerializer to munge the JSON on the client side, because I don't have similar control over the server that I'm getting this data from. I'm not sure what I'm doing wrong, but it doesn't seem like my Accounts model is using my AccountSerializer...
This is my Serializer:
var AccountSerializer = DS.RESTSerializer.extend({
/**
The current ID index of generated IDs
#property
#private
*/
_generatedIds: 0,
/**
Sideload a JSON object to the payload
#method sideloadItem
#param {Object} payload JSON object representing the payload
#param {subclass of DS.Model} type The DS.Model class of the item to be sideloaded
#param {Object} item JSON object representing the record to sideload to the payload
*/
sideloadItem: function(payload, type, item){
var sideloadKey = type.typeKey.pluralize(), // The key for the sideload array
sideloadArr = payload[sideloadKey] || [], // The sideload array for this item
primaryKey = Ember.get(this, 'primaryKey'), // the key to this record's ID
id = item[primaryKey];
// Missing an ID, generate one
if (typeof id == 'undefined') {
id = 'generated-'+ (++this._generatedIds);
item[primaryKey] = id;
}
// Don't add if already side loaded
if (sideloadArr.findBy("id", id) != undefined){
return payload;
}
// Add to sideloaded array
sideloadArr.push(item);
payload[sideloadKey] = sideloadArr;
return payload;
},
/**
Extract relationships from the payload and sideload them. This function recursively
walks down the JSON tree
#method sideloadItem
#param {Object} payload JSON object representing the payload
#paraam {Object} recordJSON JSON object representing the current record in the payload to look for relationships
#param {Object} recordType The DS.Model class of the record object
*/
extractRelationships: function(payload, recordJSON, recordType){
// Loop through each relationship in this record type
recordType.eachRelationship(function(key, relationship) {
var related = recordJSON[key], // The record at this relationship
type = relationship.type; // belongsTo or hasMany
if (typeof related === "object" && related !== null){
// One-to-one
if (relationship.kind == "belongsTo") {
// TODO: figure out when we need to only sideload 1 item we don't need to pluralize
// Sideload the object to the payload
this.sideloadItem(payload, type, related);
// Replace object with ID
recordJSON[key] = related.id;
// Find relationships in this record
this.extractRelationships(payload, related, type);
}
// Many
else if (relationship.kind == "hasMany") {
// Loop through each object
related.forEach(function(item, index){
// Sideload the object to the payload
this.sideloadItem(payload, type, item);
// Replace object with ID
related[index] = item.id;
// Find relationships in this record
this.extractRelationships(payload, item, type);
}, this);
}
}
}, this);
return payload;
},
/**
Overrided method
*/
extractArray: function(store, type, payload, id, requestType) {
var typeKey = type.typeKey,
typeKeyPlural = typeKey.pluralize(),
newPayload = {};
newPayload[typeKeyPlural] = payload;
payload = newPayload;
console.log(payload);
// Many items (findMany, findAll)
if (typeof payload[typeKeyPlural] != "undefined"){
payload[typeKeyPlural].forEach(function(item, index){
this.extractRelationships(payload, item, type);
}, this);
}
for(var key in payload) {
if(key === typeKeyPlural) {
for(var i =0; i < payload[key].length; i++) {
if(typeof payload[key][i] !== 'object') {
delete payload[key][i];
}
}
}
}
return this._super(store, type, payload, id, requestType);
},
extractSingle: function (store, type, payload, id, requestType) {
console.log('what is happening');
var typeKey = type.typeKey,
typeKeyPlural = typeKey.pluralize(),
newPayload = {};
if(typeof payload[typeKey] !== "object") {
newPayload[typeKey] = payload;
payload = newPayload;
if(payload[typeKey] instanceof Array) {
payload[typeKey] = payload[typeKey][0];
}
}
if (typeof payload[typeKey] === "object"){
this.extractRelationships(payload, payload[typeKey], type);
delete payload[typeKeyPlural];
}
console.log(payload);
return this._super(store, type, payload, id, requestType);
}
});
export default AccountSerializer;
In my adapters/account.js I have the following:
import DS from "ember-data";
var AccountAdapter = DS.RESTAdapter.extend({
namespace: 'api/v1',
host: 'http://localhost:5000',
pathForType: function(type) {
return type + '.json';
},
serializer: AccountSerializer
});
export default AccountAdapter;
I'm not 100% sure where you got the serializer property from, but I'm not sure that actually exists. There's a defaultSerializer property on the adapter which would work, but that comes with a bit of weird precedence rules. If I were you, I would declare the serializer in the Ember CLI way by putting it in serializers/account.js and remove the serializer property from your adapter.

Django Integer Field being read as undefined by Angular

I have a model in Django with the following two variables for an Event model:
notification = models.BooleanField(default=False)
priority_int = models.IntegerField(blank=True, default=0) # low priority
I get the data from the user using a controller called new-event.controller.js, and then I call create() in events.service.js like this:
Events.create(notification, priority_int).then(createEventSuccessFn, createEventErrorFn);
new-event.controller.js also broadcasts the following:
$rootScope.$broadcast('event.created', {
notification: notification,
priority_int: priority_int,
author: {
username: Authentication.getAuthenticatedAccount().username
}
});
My data is being saved correctly in the database, using the angular factory defined in events.service.js - factory is named Events
function create(notification, priority_int){
return $http.post('/api/v1/events/', {
notification: notification,
priority_int: priority_int
});}
I have an account.controller.js that is injected with Events and $scope, account.controller.js catches the broadcast with:
$scope.$on('event.created', function (event, pud) {
Events.get(username).then(eventsSuccessFn, eventsErrorFn);
});
function eventsSuccessFn(data, status, headers, config) {
console.log('event success: ');
vm.events = data.data; //already correctly instantiated
}
I have an events.directive.js, which has the controller events.controller.js. Inside events.controller.js, I am watching for the changes to vm.events with:
$scope.$watchCollection(function () {
return $scope.events;
}, render);
$scope.$watch(function () {
return $(window).width();
}, render);
This is the render function inside events.controller.js:
function render(current, original) {}
current refers to the array of events. Inside the render function, for created events and newly created events, if I retrieve each one's notification value, I receive the correct boolean value (the value that was entered by the user and saved in the database). But if I try to retrieve the priority_int value for any event, I get undefined, despite the database saving the user-inputted value correctly.
I'm incredibly confused. Any help is appreciated.
Here is my events.service.js:
(function () {
'use strict';
angular
.module('app.events.services')
.factory('Events', Events);
Events.$inject = ['$http'];
/**
* #namespace Events
* #returns {Factory}
*/
function Events($http) {
var Events = {
all: all,
create: create,
get: get
};
return Events;
//functions all, create and get below
Here is my events.directive.js:
(function () {
' use strict';
angular
.module('app.events.directives')
.directive('events', events);
/**
* #namespace events
*/
function events() {
console.log('in events directive');
/**
* #name directive
* #desc The directive to be returned
* #memberOf app.events.directives.Events
*/
var directive = {
controller: 'EventsController',
controllerAs: 'vm',
restrict: 'E',
scope: {
events: '='
},
templateUrl: '/static/templates/events/events.html'
};
return directive;
}
})();

How to commit related models

I'm working with ember-pre4 and ember-data rev 11.
Models:
A = DS.Model.extend({
//some other fields
b: DS.hasMany('B')
})
B = DS.Model.extend({
//some other fields
a: DS.hasOne('A')
})
In my router I create an instance of model A and create an instance of model B and connect them. They both don't have server-side id. Something like this:
var a = A.createRecord();
b = B.createRecord();
b.set('a', a)
a.get('b').addObject(b)
When I want to save those models I make:
a.transaction.commit()
And I expected to see:
Save a with empty b // []
After saving a pass a's id into b and save b
After saving b refetch a
But unfortunately ember does 2 request in parallel and a's request data is:
"{//some fields, b: [/url_to_b//]}" // we dont have b's id
b's request data is:
"{//some fields } // we dont have a's id
What is the best way to solve this problem, does new ember have a default solution for my situation or I should do all stuff manually?
Solution:
I wrapped createRecord function in waitForParents function:
waitForParents:function (record, callback, context) {
var observers = new Em.Set();
record.eachRelationship(function (name, meta) {
var relationship = get(record, name);
if (meta.kind === 'belongsTo' && relationship && get(relationship, 'isNew')) {
var observer = function () {
relationship.removeObserver('id', context, observer);
observers.remove(name);
finish();
};
relationship.addObserver('id', context, observer);
observers.add(name);
}
});
finish();
function finish() {
if (observers.length === 0) {
callback.call(context);
}
}
},
createRecord:function (store, type, record) {
this.waitForParents(record, function () {
// createRecord code here
}
}