Ember: change attribute of nullable relationship - javascript

I have a model company and a model address. company.address belongs to address. So when I fetch a company record model(params) {return this.store.findRecord('company', params.companyID);}I can access the address attributes with model.address.street. But when my backend retruns address: null because the company dosnt have a relationship with an address and I want that the user can set the street by a input field, I get the error Assertion Failed: Cannot delegate set('street', a) to the 'content' property of object proxy <(subclass of Ember.ObjectProxy):ember1458>: its 'content' is undefined."
Here is the json with the null relationship:
{"data": {
"attributes": {"name": "test-company"}, "id": "5",
"relationships": {
"address": {
"data": null
}
},
"type": "companies"
}
}
So how can I avoid this error? I could proof the model.address after fetching the company record if it has an id but doing this in every route isnt really smart.

Well initially your route model hook should make sure that organization has address with a valid ember-data record. If not, then create an empty one.
model: function(params) {
const store = this.get('store');
return Ember.RSVP.hash({
org: store.findRecord('orgnanization', params.id).then(org => {
org.get('address').then(address => {
if(Ember.isNone(address)) {
org.set('address', store.createRecrord('organization.address', {});
}
})
}),
});
},
Now your input can have direct binding to organization.address.street because it is now related to ember-data record at all times.
{{input type="text" value=model.org.address.street}}
And when editing You would first need to save address record, so it will have an id and then save the original organization.
editOrganization: function() {
const org = this.get('model.org')
const saveAddressPromise = org.get('address').then(address => {
return Ember.isPresent(address.get('street')) ? address.save() : EMPTY_PROMISE(null);
}); // If street is present save the address, other wise send empty promise that resolves to null
return saveAddressPromise.then(address => {
if(Ember.isNone(address) {
org.set('address', null); // The street is not present still. dont save address record
}
return org.save();
}).catch(sendError).finally(doSomething);
}
Another way would be to save an empty record everytime, then you would not have this .street check.
Nb! Empty_promise code example
return new Ember.RSVP.Promise(function(resolve, reject) {
resolve(value);
});

Related

Parse server javascript pointer data extraction

I have parse server which I have this query on
const empList = Parse.Object.extend("EmpList");
const query = new Parse.Query(empList);
query.equalTo("relation", Parse.User.current());
query.find({
success: (results) => {
// results.map((each)=>this.data = each.id)
this.data = results
},
Parse server has 2 classes ( users and EmpList), there is a pointer in EmpList which points correctly to the current user submitted the employee.The returned data also includes the pointer ( which has a name "relation" ) and I can see the username exists as an object in this fashion
Array [
Object {
"createdAt" "2018-07-05T20 17 45.173Z",
"name" "1the latter",
"objectId" "D2kThKcg9z",
"phone" "111",
"relation" Object {
"ACL" Object {
"*" Object {
"read" true,
},
"H2rQJxNyTD" Object {
"read" true,
"write" true,
},
},
"__type" "Object",
"className" "_User",
"createdAt" "2018-07-05T18 43 40.536Z",
"objectId" "H2rQJxNyTD",
"password" undefined,
"sessionToken" "r c45063a034dd81d646bef51ae2055c85",
"updatedAt" "2018-07-05T20 17 35.976Z",
"username" "1",
},
"shift" "tue",
"updatedAt" "2018-07-05T20 22 11.158Z",
},
]
Yet I am unable to extract the username in relation object.
please help me do so by data[0].relation.username or data[0].relation().username
There are two things you can do. The simplest is to eagerly fetch the related object as part of the query using include() ...
const query = new Parse.Query(empList);
query.equalTo("relation", Parse.User.current());
query.include("relation");
(Incidentally, "relation" isn't a great name for that column. A better choice would be something relating to it's meaning, like submittedByUser. Calling it relation is like naming your poodle "Poodle").
The drawback of using include is that it will eagerly fetch all of the related objects on the query, making the query take longer and potentially produce data that you don't need. If you only want the related object on one or a handful of the query results, skip the include() and query the relations individually...
const query = new Parse.Query(empList);
query.equalTo("relation", Parse.User.current());
query.find({
success: results => {
// for one or some of the results...
let submittedByUserRelation = user.relation("relation");
submittedByUserRelation.query().find({
success: user => {
// user.username will be the username
}
});
Check the query() function on the relation that you can use to get all the object in the relation .

How to add additional fields to user documents (not in profile)?

I know that the classic way to add data to user collection is in profile array, but according to this document, it is not the best way to store data.
Is there an alternative to that, for example to create a field in the root of user collection at the same level with default fields (_id, username, etc.)?
There is nothing wrong per-se with the profile field, other than the fact that a users can (currently) directly update their own profile by default.
I don't find this behavior desired, as a user could store arbitrary data in the profile.
This may become a real security risk if the developer uses that field as a source of authority; for example, stores the user's groups or roles in it.
In this case, users could set their own permissions and roles.
This is caused by this code:
users.allow({
// clients can modify the profile field of their own document, and
// nothing else.
update: function (userId, user, fields, modifier) {
// make sure it is our record
if (user._id !== userId)
return false;
// user can only modify the 'profile' field. sets to multiple
// sub-keys (eg profile.foo and profile.bar) are merged into entry
// in the fields list.
if (fields.length !== 1 || fields[0] !== 'profile')
return false;
return true;
}
});
The first thing to do is to restrict writes to it:
Meteor.users.deny({
update() {
return true;
}
});
It could then be updated using methods and other authorized code.
If you add your own fields and want to publish them to the currently logged-in user, you can do so by using an automatic publication:
Meteor.publish(null, function () {
if (this.userId) {
return Meteor.users.find({
_id: this.userId
}, {
fields: {
yourCustomField1: 1,
yourCustomField2: 1
}
});
} else {
return this.ready();
}
});
Meteor.users is just a normal Mongo.Collection, so modifying it is done just like any other Collection. There is also the creation hook, Accounts.onCreateUser which allows you to add custom data to the user object when it is first created, as mentioned in #MatthiasEckhart's answer.
You could add extra fields to user documents via the accountsServer.onCreateUser(func) function.
For example:
if (Meteor.isServer) {
Accounts.onCreateUser(function(options, user) {
_.extend(user, {
myValue: "value",
myArray: [],
myObject: {
key: "value"
}
});
});
}
Please note: By default, the following Meteor.users fields are published to the client username, emails and profile. As a consequence, you need to publish any additional fields.
For instance:
if (Meteor.isServer) {
Meteor.publish("user", function() {
if (this.userId) return Meteor.users.find({
_id: this.userId
}, {
fields: {
'myValue': 1,
'myArray': 1,
'myObject': 1
}
});
else this.ready();
});
}
if (Meteor.isClient) {
Meteor.subscribe("user");
}

How to update user.services for Jasmine testing in Meteor

I am trying to mock out a user for testing out my application, and I have gotten to the point where I can create a test user and log them into the mirror instance of my app.
I need to compare the gmail addresses for peoples accounts, and to test this functionality, I want to add a test email address under user.services.google.email within the Meteor users account database (which is where the accounts-google package stores it, I don't need to mock out an entire user account yet).
What I can't figure out is how to append this information, instead of just overwriting what is already there, this is what my code looks like:
if (Meteor.users.find().count() === 0) {
var testUserDetails = {
email: 'testEmail#gmail.com',
password: 'testPassword'
};
console.log("Creating the Test User");
var newUserId = Accounts.createUser(testUserDetails);
Meteor.users.update({
_id: newUserId
}, {
$set: {
services: {
google: {
email: "testEmail#gmail.com"
}
}
}
});
} else {
console.log("There are already users in the Test database");
}
console.log('***** Finished loading default fixtures *****');
},
And this is what a user looks like:
{
"_id" : "Dw2xQPDwKp58RozC4",
"createdAt" : ISODate("2015-07-30T04:02:03.261Z"),
"services" : {
"password" : {
"bcrypt" : "asdfasdfasdfdsafsadfasdsdsawf"
},
"resume" : {
"loginTokens" : [ ]
}
},
"emails" : [
{
"address" : "testEmail#gmail.com",
"verified" : false
}
]
}
Now $set just rewrites everything within services, and there is no $push operation for mongo or for js, so how should I go about doing this? Should I consume the object and parse it manually?
*Note I have also tried using Meteor's Accounts.onCreateUser(function(options, user) but facing the same issue.
[...] there is no $push operation for mongo [...]
Sure, there is a $push operator, which appends a specified value to an array.
However, I think what you are trying to do is to update a document and keep all values which are already set.
Here is how you can do that:
Query the document first to get the object you want to set.
Update the respective object.
Run the MongoDB update operation to set the new object.
For instance:
var user = Meteor.users.findOne({
_id: newUserId
});
var servicesUserData = user.services;
servicesUserData.google.email = "your_new_email#gmail.com";
Meteor.users.update({
_id: newUserId
}, {
$set: {
"services": {
servicesUserData
}
}
});

How to force ember data to reset all fields of a record on reload?

I am using ember with ember data and i have a scenario where I need to reload a model. On reload, when the data is fetched and some fields of that model are null, the older data still persists. For eg., if I have a Post model
App.Post = DS.Model.extend({
name: DS.attr('string'),
description: DS.attr('string'),
});
Now the first time the server returns the following data:
{
"post" : {
"id" : "1",
"name" : "new post",
"description": "some description"
}
}
After reload is called, the server returns the following data:
{
"post" : {
"id" : "1",
"name" : "new post",
}
}
So after the reload, "description" field should be set to null for that record. But the old data ie., "some description" still persists in that field.
How can I force ember data to reset all fields on reload?
Unfortunately the exact feature you want was deprecated a few months ago. At the bottom it mentions a way in the serializer to replace missing properties with null.
// app/serializers/application.js
// or App.ApplicationSerializer
export default DS.RESTSerializer.extend({
normalize: function(type, hash, prop) {
hash = this._super(type, hash, prop);
// Find missing attributes and replace them with `null`
type.eachAttribute(function(key) {
if (!hash.hasOwnProperty(key)) {
hash[key] = null;
}
});
return hash;
}
});

Emberjs - Calling directly nested resource's URL

I have merged together from this two problem (How to pass model in Nested routes - emberjs and Embedded data from RestApi) a JsBin example: http://jsbin.com/OxIDiVU/544
It works fine if you navigate customers-> info -> contact, but it will break if one calls directly a customer's contact eg.:http://jsbin.com/OxIDiVU/544#/customers/3/contact
Error while loading route: customer.contact Cannot set property 'store' of undefined TypeError: Cannot set property 'store' of undefined
When you do a request for a single record, it uses a different serializer endpoint and expects the data in a different format. The format it expects is:
{
customer: {
id: 1,
currency:1
},
currencies: [
{
id:1,
prop: 'foo'
}
]
}
And the endpoint in the serializer is extractSingle. Feel free to extract out the portions of extractArray that are similar and share those.
Pretending your payload is:
{
customer:{
id:3,
name:"Joue",
currency:{
id:5,
iso_code:"BDT"
}
}
}
Your extractSingle would be
extractSingle: function(store, type, payload, id) {
var customer = payload.customer,
currencies = [];
var currency = customer.currency;
delete customer.currency;
if(currency){
currencies.push(currency);
customer.currency = currency.id;
}
payload = { customer:customer, currencies: currencies };
return this._super(store, type, payload, id);
}
Here's the example, with a response for customer 3
http://jsbin.com/OxIDiVU/545#/customers/3/contact
your property name should match inside the model, and the root name (currencies here) should be the plural version of the type of record it is.
{
customer: {
id: 1,
default_currency:1
},
currencies: [
{
id:1,
prop: 'foo'
}
]
}

Categories