I have a way that works, it just seems like a stupid way of doing things.
I have an Invoice object ($resource) that has a client value, which, because of the way Mongoose works, is assigned to an ID. But, I want to use the client values (e.g. client.name, client.address, etc.) in the invoice views in Angular. Here's the code I have, which takes invoice.client (an ID) and reassigns the whole client object:
Invoices.query(function(invoices) {
angular.forEach(invoices, function(invoice){
Clients.get({
clientId: invoice.client
}, function(client) {
invoice.client = client;
})
});
$scope.invoices = invoices;
});
This seems very redundant, because I already have all the clients loaded in $scope.clients, but I couldn't think of a way to use those instead, which would require far fewer database calls.
$scope.clients is a promise object, since it is populated like this:
Clients.query(function(clients) {
$scope.clients = clients;
});
Related
I understand that this question may have more than one DBMS-related answer, but maybe not, so here it goes.
So, I need my app to asynchronously grab some data from the backend, say something like a User and his Posts. I am using Vue2 with Axios on the frontend, so that it would be nice to write something like:
mounted: function() {
axios.get('/users')
.then((response) => {
this.users = response.data;
})
.catch(function (err) {
// Handle this
});
}
The outcome I'd like to have is the users object in my VueVM's data to contain also an array of posts for each User. Currently I build a custom associative array in my backend-side controller as follows:
$posts = Post::where('user_id', $id)->get();
return json_encode(['user' => $user, 'posts' => $posts]);
Or, when feasible, I use the has_many method, which is also pretty handy, but I find this solution pretty ugly and lacking reusability.
With Mongoose and MongoDB I would use the populate() method, but I cannot find (that is I actually don't know how to search for it) an equivalent to this with Eloquent.
Any suggestion?
If you have defined a posts() relationship in your User model, when retrieving the users, you can eager load the related data:
$users = User::with('posts')->get();
or a specific User:
$user = User::with('posts')->find($id);
Then $user->posts would contain a collection of the related Post models.
I suggest you read through https://laravel.com/docs/5.5/eloquent-relationships
I am using iron:router in my app and I have a controller that subscribes to one document in a collection by using a parameter in the path. I can access all of the documents in my collection on the server, so I know that there is stuff in there, but when I try to access the data that I subscribe to in the waitOn method of the controller, the data is undefined. Here is the relevant code for this problem.
Router code:
this.route('unit', { path: 'unit/:unitId', template: 'unit', controller: 'UnitController' });
UnitController = BaseController.extend({
waitOn: function () {
return Meteor.subscribe('getUnit', this.params.unitId);
},
data: function () {
var id = this.params.unitId;
templateData = {
unit: Collections.units.model(Collections.units.getUnit(id))
};
return templateData;
}
});
Publication:
Meteor.publish('getUnit', function(id) {
return Collections.units.data.find({ unitId: id });
});
Here I have created an object for various things to do with my collection(I only included the important parts here):
Collections.units = {
data: new Mongo.Collection("units"),
getUnit: function (id) {
return this.data.findOne({ unitId: id });
},
model: function(unitEntity) {
return {
unitId: unitEntity.unitId,
createdAt: unitId.createdAt,
packets: unitEntity.packets,
getLastPacket: function (id) {
return _.last(this.packets);
}
};
}
};
I have been trying to debug this for quite a while and I can do all the things to the collection I want to on the server and in the publish method, but when it gets to the controller, I can't access any of the info. In the data method this.params.unitId returns exactly what I want so that isn't the issue. Where the exception gets thrown is when I try to read properties of unitEntity when I'm making the model but that is just because it is undefined.
Have any ideas what I am doing wrong? Thanks in advance for any responses.
The main problem that I was trying to solve's solution was to wrap the code inside the data method inside of if (this.ready()){ ... } and add an action hook with if (this.data()) { this.render(); }. After I got the subscription to work, I found that Christian was right in the comments with saying that my controller setup might mess things up. It was causing other strange exceptions which I fixed by just moving the hooks to each route instead of using the controller. As for my Collections setup, it may be unconventional, but all of that is working fine (as of right now). I may want to set them up the standard way at a later point, but as of right now its pretty handy for me to do things with the collections with the methods already written in the object.
I am trying to work with Ember.js
Can I expose my data model as JSON through a route or controller?
I have an object like this saved in the store:
this.store.createRecord('Person', {
id: 1,
name: this.get('name'),
email: this.get('email')
});
I want to expose this data from a route or controller as JSON object. I don't want to use any view.
Is it possible to do this?
Thanks for help!
EDIT
My route is:
App.ResultRoute = Ember.Route.extend({
model: function() {
return this.store.find('person', 1);
}
});
There is '1' because I want only this record.
In this way It works and I see in the view the {{name}} and the {{email} of the Person object.
I want to see only the JSON, I tried to do how you suggest me :
App.ResultRoute = Ember.Route.extend({
afterModel: function (model) {
model.get('content').forEach(function (item) {
console.log(item.get('content'));
});
}
});
But I receive this error:
Uncaught Error: Assertion Failed: Error: More context objects were passed than there are dynamic segments for the route: error
What is my error?
The way I would do this would be, I would have an api in my model which would return a plain json object to whoever asked it. So the Person model would have a getPersonDetails method which will hide all the internal details, including the attributes and associations and whatever else, and return the state of the person object it is invoked upon.
So, for example, if you wanted to display a table of persons or something, you would do a createRecord, and just ask the newly created person object for it's details.
Start from the beginning of this guide. http://emberjs.com/guides/routing/specifying-a-routes-model/ It will show you how to specify a model for a route.
Then, read this entire guide on controllers: http://emberjs.com/guides/controllers/
In general, you would access that data from the route's model hook with:
this.store.find('person') // All records
If you wanted to access that first object as JSON, you could do:
var person_JSON = this.store.find('person').then(function (persons) {
//The persons records are now available so you can do whatever you want with them
console.log(persons.objectAt(0).get('content'));
});
You could also iterate over all records and strip out the content to produce raw json without the Ember wrapping... Just depends on what you need to really do.
Really the best place to put this would be the route's afterModel hook, though. You wouldn't be working with a promise, as Ember would have dealt with that for you:
afterModel: function (model) {
model.get('content').forEach(function (item) {
console.log(item.get('content'));
});
}
Hope that helps.
Edit: Since you have one record try this:
afterModel: function (model) {
console.log(model.get('content'));
}
When saving a model, Backbone determines whether to send an HTTP POST or PUT request by whether or not the model's ID attribute is set. If there is an ID, the model is considered to already exist.
For my application, this logic is incorrect because I must allow the user to specify an ID (as I interact with a poorly designed legacy system).
How should I handle this problem? I still would like to use PUT if the model is changed.
I am considering the following options:
Override isNew, which is the Backbone method that simply checks if an ID is present.
Override sync.
Determine if the concept of cid would somehow solve the problem.
One solution is to address the symptoms rather than the cause. Consider adding to your model a new create method:
var FooModel = Backbone.Model.extend({
urlRoot: '/api/foo',
create: function () {
return this.save(null, {
type: 'post', // make it a POST rather than PUT
url: this.urlRoot // send the request to /api/foo rather than /api/foo/:id
});
}
});
This is the solution I use, but I don't consider it ideal because the view logic/caller now needs to call create rather than save when creating (which is rather easy to do). This extended API bothers me for my use-case (despite working and being rather small), but perhaps it'll work for yours.
I'd love to see some additional answers to this question.
So I went down the path of trying to change up isNew.
I came up with new criteria that would answer whether a model is new:
Was the model created via a fetch from a collection? Then it's definitely not new.
Was the model created with an ID attribute? This is a choice I made for my case, see disadvantages below for the effect of doing this, but I wanted to make new Model({ id: 1, name: 'bob' }) not be considered new, while setting the ID later on (new Model({ name:
bob'}).set('id', 1)) would be.
Was the model ever synced? If the model was successfully synced at any point, it's definitely not new because the server knows about it.
Here's what this looks like:
var UserDefinedIDModel = Backbone.Model.extend({
// Properties
_wasCreatedWithID: false,
_wasConstructedByFetch: false,
_wasSynced: false,
// Backbone Overrides
idAttribute: 'some_id',
urlRoot: '/api/foo',
constructor: function (obj, options) {
this._wasCreatedWithID = !!obj[this.idAttribute];
this._wasConstructedByFetch = options && options.xhr && options.xhr.status === 200;
// Preserve default constructor
return Backbone.Model.prototype.constructor.apply(this, arguments);
},
initialize: function () {
this.on('sync', this.onSync.bind(this));
},
isNew: function () {
// We definitely know it's not new
if (this._wasSynced || this._wasConstructedByFetch) return false;
// It might be new based on this. Take your pick as to whether its new or not.
return !this._wasCreatedWithID;
},
// Backbone Events
onSync: function () {
this._wasSynced = true;
}
});
Advantages over the other answers
No logic outside of the backbone model for handling this odd usecase.
No server-side changes to support this
No new pseudo properties
Disadvantages
This is a lot of code when you could just create a new create method as per my other answer.
Currently myCollection.create({ some_id: 'something' }); issues a PUT. I think if you need support for this you'll have to do myCollection.create({ some_id: 'something' }, { url: '/api/foo', type: 'post' }); You can remove the _wasCreatedWithoutID check to fix this, but then any construction of a new model that derives its data from an existing one will be treated as new (in my case, this is undesirable).
Here's another solution :
In your model define an idAttribute that don't exists in your server model/table/... and that wouldn't be displayed to the DOM.
So let's suppose that the JSON that you send to the server is as follows :
{
'id': 1,
'name': 'My name',
'description': 'a description'
}
Your model should look like :
var MyModel = Backbone.Model.extend({
idAttribute: 'fakeId'
});
Now, when you create a new model and try to save it to the server, no one would initialize the fakeId and it would be considered a new object (POST).
When you fetch your model from the server you have to set the fakeId in your model, and your server must duplicate the id in the fakeId like this your model will be considered as an existing (PUT)
First off, some background
My client has a kind of a "split-view", meaning- a side-panel displaying a list of objects and a main view displaying the selected object's details. Every time the user clicks on an Object in the list, a Backbone's route is called to navigate to the id which updates a "selected" property on the Session, what causes the main view to update- pretty standard stuff.
The problem
I want the client to be as responsive as possible, therefore i'm trying to utilize Meteor's abillity to update the client immediately without waiting for a server confirmation.
My goal is that every time an Object is created, the list and the main view will be instantly updated to reflect the newly added Object. To achieve this I created a Meteor.method, create(), that uses Collection.insert and returns the id so I can use it with my Route. The method is shared across the client and server and is being called from within a template's event handler.
My first try was to store the returned id in a variable in the event handler and update the Route in the next line; For some reason, that didn't work because the method returned an undefined value. So I tried a different approach, instead of returning the id, I used it within the method to update the Route directly (if Meteor.isClient of course). That didn't work either because the id returned by Collection.insert in the client's version of the method was different from the one in the server's version.
First approach
Template.createDialog.events({
'click #btn-dialog-create': function (event, template) {
var objectId = Meteor.call('create');
appRouter.navigate("object/id/" + objectId, {trigger:true});
}
});
Second approach
Meteor.methods({
create: function () {
var ObjectId = Objects.insert({name:'test'});
if(Meteor.isClient){
appRouter.navigate("object/id/" + objectId, {trigger:true});
}
}
});
If anyone knows what's going on and can give me some directions that would be great.
Any different approaches to the problem or suggestions would be much appreciated as well.
Thanks
Update
So I tried #Pent's suggestion and I got the same result as with my second approach. For some odd reason Meteor decides to ignore my id (created with Random.id()) and inserts the object with a different one.
So I tried another approach, I used just a simple string value instead of Random.id() and voila - it worked. Riddle me that.
Answer updated:
This will be both a client and server method:
Meteor.methods({
create: function () {
var id = Random.id();
Objects.insert({_id: id, name:'test'});
if(this.isSimulation) {
appRouter.navigate("object/id/" + id, {trigger:true});
}
}
});
You can view a similar pattern from Meteor's party example: https://github.com/meteor/meteor/blob/b28c81724101f84547c6c6b9c203353f2e05fbb7/examples/parties/model.js#L56
Your problem is coused by the fact that remote methods, i.e. those which will be called on the server, don't simply return any value. Instead, they accept a callback that will be used to process the returned value (see docs). So in your first example you should probably do something like this:
Template.createDialog.events({
'click #btn-dialog-create': function (event, template) {
Meteor.call('create', function (error, result) {
if (!error)
appRouter.navigate("object/id/" + result, {trigger:true});
});
}
});
You also said:
I want the client to be as responsive as possible, therefore i'm trying to utilize Meteor's abillity to update the client immediately without waiting for a server confirmation.
I think that in this case you should definitely wait for server response. Note, that there is no chance you get the correct object id unless this is given to you by the server.
One possible way to get around this issue is to create a local (client-side) collection:
// only on client
var temporary = new Meteor.Collection(null); // null name
in which you could store your "temporary" newly created objects, and then save them to the "real" collection after the user clicks the save button. You could implement your router to respond to urls like object/new/* to get access to these objects before they're saved to your database.
The correct answer for this question is defining a client side method that's responsible for creating the unique id (preferably using Random.id() ) and calling the Meteor.methods' create(). That way, you can have the id available immediately without waiting for the server to generate one. The trick here is to generate the id outside of the Meteor.method so that the id generation happens only once for both the stub and the actual server method.
create = function(){
var id = Random.id();
Meteor.call('create', id);
return id;
}
Meteor.methods({
create: function (id) {
Objects.insert({_id: id, name:'test'});
//more code...
}
});
//and in the Template...
Template.createDialog.events({
'click #btn-dialog-create': function (event, template) {
var objectId = create();
appRouter.navigate("object/id/" + objectId, {trigger:true});
}
});