Get model with both id and query parameters - ember-data

I have a route that should load a model (BatchDetail) and a number of related items (BatchItems). Since there are a great number of items I should be able to do pagination with the help of two request parameters, limit and offset.
Here is the route I set up:
App.BatchDetailRoute = Ember.Route.extend({
model: function(params) {
var store = this.get('store');
var adapter = store.get('adapter');
var id = params.batch_detail_id;
var rejectionHandler = function(reason) {
Ember.Logger.error(reason, reason.message);
throw reason
}
return adapter.ajax("/batch_details/" + id, "GET", {
data: { limit: 50, offset: 100 }
}).then(function(json) {
adapter.didFindRecord(store, App.BatchDetail, json, id);
}).then(null, rejectionHandler);
},
setupController: function(controller, model) {
return this.controllerFor('batchItems').set('model', model.get('items'));
}
})
This way, when I go to /batch_details/1 my REST adapter will fetch the correct data which I receive in json in the above code.
Now, the model hook should return a model object or a promise that can be resolved to a model object, and that's where the problem lies. In setupController (which runs after the model hook) model is set to undefined and so my code explodes.
That means that whatever adapter.ajax returns does not resolve correctly but instead returns undefined. I'm baffled, since the above mechanism is exactly how the different find methods in ember-data (findById, findByQuery, etc.) work and that's where I got my idea from.
Can you shed some light on what I'm not getting?
Thank you.

Related

Odoo 10 - Javascript Query to a Model

I'm doing:
var callback = new $.Deferred();
new Model('pos.order').query(['invoice_id']).filter([['id', '=', '100']])
.first().then(function (order) {
if (order) {
callback.resolve(order);
} else {
callback.reject({code:400, message:'Missing Order', data:{}});
}
});
It works fine, and returns an Order object. But my issue is that i want to access the relation objects (many2many, many2one), but the order object has only the ID's of his relations. For example if i want to access the company or invoice object from the Order that i just fetched i need to do another query and i want to get all in a single query.
Use below js code to call method in py to get your required data.
new Model("pos.order")
.call("method_in_pos_order_model", [100])
.then(function (result) {
// Result is having what you want..
});
Method in Py under pos.order model
#api.model
def method_in_pos_order_model(self,id):
return self.search([('id','=',id)])
I hope this will work for you.

Ajax call for JSON using EF Core is failing/aborting

I'm at a loss with why this isn't working...
I have a .NET Core app using EF Core, and I'm making an Ajax call via jQuery to my controller to retrieve some data from the database via EF Core. Debugging the call via my developer tools in the browser (IE/Chrome) results in a status of failed/aborted. Yet when I step through my method in my controller, the method seems to be able to retrieve the data from the database via EF Core just fine.
Here's my controller:
public ActionResult GetInfo(string term)
{
using (var dbContext = new DatabaseContext())
{
// use DbContext to get data from the database
var retrievedData = dbContext.TableName.Where(...);
return Json(retrievedData.Select(data => new {
id = data.id,
text = data.text
}));
}
}
And here's the jQuery:
$(#element).select2({
...
ajax: {
url: $(#element).attr("data-getinfo"),
dataType: 'json', // tried this with jsonp and application/JSON with no luck
contentType: 'application/json; charset=utf-8',
delay: 250,
data: function (params) {
return: { term: params.term};
},
processResults: function (data) {
return {
results: $.map(data, function (item) {
return {
id: item.id, text: item.text
}
})
}
},
},
....
});
The Ajax calls work with previous apps I've worked on, but they used MVC 5 and EF 6. This also works if I retrieve dummy data, IE instead of using EF Core to get the data, I return fake data built into the controller. What gives?
To clarify the root of your problem: You are querying your database and returning an IEnumerable as a JsonResult. But first, you need to understand one step before. Calling .Where returns an IQueryable. You can think of an IQueryable as if it is a TSQL command that was not yet execute on the database. Only calls that will enumerate the results will trigger the materialization of the query.
So you did this:
// .Where returns an IQueryable. You can "chain" more wheres later.
// the query will still not be executed
var retrievedData = dbContext.TableName.Where(...);
// This then returns an IEnumerable to the client.
// The Select will materialize (execute) the query
return Json(retrievedData.Select(data => new {
id = data.id,
text = data.text
}));
The problem with your code is: .Select returns an IEnumerable which enumerates the results. But, by the time the browser or whatever client you are dealing with starts to enumerate the results, your database connection is already closed, because you used using block around your dbContext (which is kind of correct.. see comments in the end).
So, to fix it, you need basically to enumerate the results yourself or not close the connection (let the framework close for you when the request is finished..). This minor change fix the problem:
// ToList() will enumerate all the results in memory
var retrievedData = dbContext.TableName.Where(...).ToList();
Other comments:
You don't need (also shouldn't) manage the creation of the dbContext by yourself. You can register it in the DI container and the framework will do the rest for you. You can take a look in the EF Core docs to have an idea on how it is done.
Not an ideal solution, but I got it working. I suspect it might have to do with how .NET Core or EF Core was returning data to the browser, but I can't say for sure yet.
I ended up using Json.NET for a workaround. Performance isn't bad (I tried a query with hundreds of records and it only took a couple of seconds at most), and I was already using it for an external API call.
public ActionResult GetInfo(string term)
{
using (var dbContext = new DatabaseContext())
{
// use DbContext to get data from the database
var retrievedData = dbContext.TableName.Where(...);
var initJson = Json(retrievedData.Select(data => new {
id = data.id,
text = data.text
}));
var serializedJson = Newtonsoft.Json.JsonConvert.SerializeObject(initJson);
var deserializedJson = Newtonsoft.Json.JsonConvert.DeserializeObject(serializedJson);
return Json(deserializedJson);
}
}

Ember data 1.0 Application -en AdapterSerializer not working?

My question is in line with this one: Ember data 1.0.0: confused with per-type adapters and serializers
Problem is that I cannot initialize RESTSerializer per type because I need to set ActiveModelSerializer :
App.FooSerializer = DS.ActiveModelSerializer.extend({attrs: {}});
to set relationships as embedded.
So I want to set 1 serializer for all models. I tried to set ApplicationSerializer, but this did not call any hooks when getting response from server (And I'm sure my server gives correct response):
App.ApplicationSerializer = DS.RESTSerializer.extend({
extractSingle: function(store, type, payload, id, requestType) {
return this._super(store, type, payload, id, requestType);
},
extractArray: function(store, type, payload, id, requestType) {
return this._super(store, type, payload, id, requestType);
},
normalize: function(type, property, hash) {
return this._super(type, property, hash);
}
});
Setting my adapter doesn't seem to work:
var adapter = require('init/adapter');
App.ApplicationAdapter = adapter.extend({
defaultSerializer: myAdapter //I QUESS THIS IS WRONG?
});
Did I make a syntax error? Any other suggestions?
EDIT:
Ok, I found my mistake but not a great solution. Seems that my FooSerializer overrides the general ApplicationSerializer..
Is there a way where I can set both? :/
You can't use both per say, but you can have your FooSerializer extend your ApplicationSerializer and then override the methods that you'd like to use differently.
App.FooSerializer = App.ApplicationSerializer.extend({
extractArray: function(store, type, payload, id, requestType) {
// alert each record to annoy the user
return this._super(store, type, payload, id, requestType);
},
});

Unwrapping breeze Entity properties

I'm very new to breeze/knockout, but I'm 99% of the way to doing what I need to do. I'm using the Hot Towel template, and I'm successfully retrieving a list of items via breeze. The entity (ITBAL) is a database first Entity Framework entity. When I look at the JSON coming back in Fiddler, I see the correct data. The problem is that all of the properties of data.results are dependentobservables, and not the raw values themselves.
We have a custom grid control that is trying to display the data.results array. Because it is not expecting observables, it is simply displaying "function dependentobservable" instead of the value.
I tried to unwrap the object, but keep getting the circular reference error. I don't know why that is, since ITBAL isn't associated with anything.
The data as Fiddler reports it:
[{"$id":"1","$type":"WebUIHtml5HotTowel.Models.ITBAL, WebUIHtml5HotTowel","IBITNO":"A100 ","IBWHID":"1 ","IBITCL":"50","IBITSC":"3 ","IBSUSP":" ","IBVNNO":"100 ","IBPRLC":" ","IBSCLC":" ","IBCCCD":"P","IBPICD":" ","IBSAFL":"Y","IBSTCS":399.99000,"IBUSCS":0.00000,"IBAVCS":414.95214,"IBLCST":7.00000,"IBLCCC":20.0,"IBLCDT":110923.0,"IBLSCC":20.0,"IBLSDT":130111.0,"IBLXCC":19.0,"IBLXDT":990102.0,"IBMXO1":2100.000,"IBMXO2":0.000,"IBMXO3":0.000,"IBMNO1":5.000,"IBMNO2":0.000,"IBMNO3":0.000,"IBFOQ1":0.000,"IBFOQ2":0.000,"IBFOQ3":0.000,"IBOHQ1":327.000,"IBOHQ2":0.000,"IBOHQ3":0.000,"IBAQT1":1576.000,"IBAQT2":0.000,"IBAQT3":0.000,"IBBOQ1":50.000,"IBBOQ2":0.000,"IBBOQ3":0.000,"IBPOQ1":448.000,"IBPOQ2":0.000,"IBPOQ3":0.000,"IBIQT1":1446.000,"IBIQT2":0.000,"IBIQT3":0.000,"IBRMD1":10.000,"IBRMD2":0.000,"IBRMD3":0.000,"IBRYD1":10.000,"IBRYD2":0.000,"IBRYD3":0.000,"IBISM1":0.000,"IBISM2":0.000,"IBISM3":0.000,"IBISY1":0.000,"IBISY2":0.000,"IBISY3":0.000,"IBAMD1":0.000,"IBAMD2":0.000,"IBAMD3":0.000,"IBAYD1":0.000,"IBAYD2":0.000,"IBAYD3":0.000,"IBMMD1":0.000,"IBMMD2":0.000,"IBMMD3":0.000,"IBMYD1":0.000,"IBMYD2":0.000,"IBMYD3":0.000,"IBSMD1":1.0,"IBSMD2":0.0,"IBSMD3":0.0,"IBSYD1":1.0,"IBSYD2":0.0,"IBSYD3":0.0,"IBBLME":335.000,"IBBLYO":2680.000,"IBBLLY":1441.000,"IBNMTY":8.0,"IBNMLY":11.0,"IBQSMD":21.000,"IBQSYD":21.000,"IBQSLY":20.000,"IBISMD":16318.19,"IBISYD":16318.19,"IBISLY":45714.87,"IBCSMD":373.46,"IBCSYD":373.46,"IBCSLY":67.00,"IBDQMD":0.000,"IBDQYD":0.000,"IBDQLY":0.000,"IBDSMD":0.00,"IBDSYD":0.00,"IBDSLY":0.00,"IBDCMD":0.00,"IBDCYD":0.00,"IBDCLY":0.00,"IBNOMD":18.0,"IBNOYD":18.0,"IBNOLY":18.0,"IBPKMD":15.0,"IBPKYD":15.0,"IBPKLY":14.0,"IBINUS":" ","IBIAID":0.0,"IBSAID":0.0,"IBCQT1":1527.000,"IBCQT2":0.000,"IBCQT3":0.000,"IBFCST":"Y","IBDRSH":" ","IBWMIU":"JP","IBFL15":" ","IBUS20":" ","IBLPR1":0.00000,"IBLPR2":0.00000,"IBLPR3":0.00000,"IBLPR4":0.00000,"IBLPR5":0.00000,"IBLPCD":" ","IBABCC":"B","IBPRCL":0.0,"IBQBCL":" ","IBACDC":"Y","IBTDCD":" ","IBDOUM":" ","IBTP01":0.0,"IBTP02":0.0,"IBTP03":0.0,"IBTP04":0.0,"IBLMCC":20.0,"IBLMDT":130513.0,"IBTMPH":"Y","IBCOMC":" ","IBCOMF":0.00000,"IBITCT":" ","IBEOQT":0.000,"IBITCM":0.0,"IBBRVW":" ","IBPTID":" ","IBQTLT":0.0000,"IBCTY1":"AUS","IBCTY2":"AUS","IBTXCD":"1","IBREVS":"Y","IBITXC":" ","IBMNOQ":0.000,"IBSTUS":0.000,"IBUS30":" ","IBPSLN":" ","IBPLIN":"N","IBUPDP":"Y","IBDFII":"2011-08-11T00:00:00.000","IBLHRK":"A","IBPLNC":" "}]
My Controller:
[BreezeController]
public class ItemInquiryController : ApiController
{
readonly EFContextProvider<AplusEntities> _contextProvider = new EFContextProvider<AplusEntities>();
[System.Web.Http.HttpGet]
public string Metadata()
{
return _contextProvider.Metadata();
}
[HttpGet]
public IQueryable<ITBAL> ItemBalances(string itemNumber, string warehouse)
{
return _contextProvider.Context.ITBALs.Where(i => i.IBITNO == itemNumber && i.IBWHID == warehouse)
.OrderBy(i => i.IBWHID)
.ThenBy(i => i.IBITNO);
}
}
The relevant portion from the viewmodel:
var manager = new breeze.EntityManager("api/ItemInquiry");
var store = manager.metadataStore;
var itbalInitializer = function (itbal) {
itbal.CompositeKey = ko.computed(function () {
return itbal.IBITNO() + itbal.IBWHID();
});
};
store.registerEntityTypeCtor("ITBAL", null, itbalInitializer);
var index = "0" + (args.pageNum * args.pageSize);
var query = new breeze.EntityQuery("ItemBalances")
.withParameters({ itemNumber: "A100", warehouse: "1" })
.take(args.pageSize);
if (index > 0) {
query = query.skip(index);
}
manager.executeQuery(query).then(function (data) {
vm.itbals.removeAll();
var itbals = data.results;//[0].Data;
itbals.forEach(function (itbal) {
vm.itbals.push(itbal);
});
vm.totalRecords(1);
itemBalancesGrid.mergeData(vm.itbals(), args.pageNum, parseInt(vm.totalRecords()));
}).fail(function (e) {
logger.log(e, null, loggerSource, true, 'error');
});
I figure I must be missing something fairly simple, but it is escaping me.
UPDATE: I removed the BreezeController attribute from the ApiController, and it works correctly.
Jon, removing the [Breeze] attribute effectively disables breeze for your application so I don't think that is the long term answer to your problem.
If you don't actually want entities for this scenario - you just want data - than a Breeze projection that mentions just the data to display in the grid would seem to be the best choice. Projections return raw data that are not wrapped in KO observables and are not held in the Breeze EntityManager cache.
If you want the data as cached entities and you also want to display them in a grid that doesn't like KO observable properties ... read on.
You can unwrap a KO'd object with ko.toJS. However, the grid is likely to complain about circular references (or throw an "out of memory" exception as some grids do) ... even if the entity has no circular navigation paths. The difficulty stems from the fact that every Breeze entity has an inherent circularity by way of its entityAspect property:
something.entityAspect.entity //'entity' points back to 'something'
Because you are using Knockout for your model library and because you say ITBAL has no navigation properties ("is not related to anything"), I think the following will work for you:
manager.executeQuery(query).then(success) ...
function success(data) {
var unwrapped = ko.toJS(data.results).map(
function(entity) {
delete entity.entityAspect;
return entity;
});
vm.itbals(unwrapped);
vm.totalRecords(1); // huh? What is that "parseInt ..." stuff?
itemBalancesGrid.mergeData(vm.itbals(), args.pageNum, parseInt(vm.totalRecords()));
})
ko.toJS is a Knockout function that recursively unwraps an object or collection of objects, returning copies of values. Then we iterate over the copied object graphs, deleting their entityAspect properties. The array of results is stuffed into the vm.itbals observable and handed along.
You can imagine how to generalize this to remove anything that is giving you trouble.
Extra
What the heck is vm.totalRecords? I sense that this is supposed to be the total number of matching records before paging. You can get that from Breeze by adding .inlineCount() to the breeze query definition. You get the value after the query returns from the data.inlineCount property.
Do you really need vm.itbals()? If all you do here is pass values to the grid, why not do that and cut out the middle man?
The following success callback combines these thoughts
function success(data) {
var unwrapped = ko.toJS(data.results).map(
function(entity) {
delete entity.entityAspect;
return entity;
});
itemBalancesGrid.mergeData(unwrapped, args.pageNum, data.inlineCount);
})

ExtJS: Model.save() error "cannot read property 'data' of undefined" when server response is simple success

(ExtJS 4.0.7)
I'm using Model.save() to PUT an update to a server. Everything works fine and the server returns a simple JSON response {success: true} (HTTP status 200). Model.save() throws the following error, however:
Uncaught TypeError: Cannot read property 'data' of undefined
Here's where this is happening in the ExtJS code (src/data/Model.js):
save: function(options) {
...
callback = function(operation) {
if (operation.wasSuccessful()) {
record = operation.getRecords()[0]; <-- getRecords() return an empty array
me.set(record.data); <-- record is undefined, so .data causes error
...
}
I've figured out this is happening because Model.save() expects the server to respond with JSON for the entire object that was just updated (or created).
Does anyone know of a clever way to make Model.save() work when the server responds with a simple success message?
I was able to come up with a work-around by using a custom proxy for the model, and overriding the update function:
Ext.define('kpc.util.CustomRestProxy', {
extend: 'Ext.data.proxy.Rest',
alias: 'proxy.kpc.util.CustomRestProxy',
type: 'rest',
reader : {
root: 'data',
type: 'json',
messageProperty: 'message'
},
// Model.save() will call this function, passing in its own callback
update: function(operation, callback, scope) {
// Wrap the callback from Model.save() with our own logic
var mycallback = function(oper) {
// Delete the resultSet from the operation before letting
// Model.save's callback use it; this will
oper.resultSet = undefined;
callback(op);
};
return this.doRequest(operation, mycallback, scope);
}
});
In a nutshell, when my proxy is asked to do an update it makes sure operation.resultSet == undefined. This changes the return value for operation.getRecords() (which you can see in the code sample from my question). Here's what that function looks like (src/data/Operation.js):
getRecords: function() {
var resultSet = this.getResultSet();
return (resultSet === undefined ? this.records : resultSet.records);
}
By ensuring that resultSet == undefined, operation.getRecords returns the model's current data instead of the empty result set (since the server isn't returning a result, only a simple success message). So when the callback defined in save() runs, the model sets its data to its current data.
I investigate this problem and found truly simple answer. Your result must be like this:
{
success: true,
items: { id: '', otherOpt: '' }
}
And items property MUST be equal Model->Reader->root property (children in tree for example).
If you want to use items instead children you can use defaultRootProperty property in Store and configure your nested collections as you want.
PS
Object in items property must be fully defined because it replaces actual record in store.