I'm using Backbone-Relational in my project, and I have a model with another nested model in a HasOne relation.
I required the nested-model in my client-side even if it doesn't exist in my server.
If my server sends the nested modal data everything is fine.
If it doesn't, I want the HasOne relation to create an empty nested-model by default.
The only way I found to achieve this is like so:
parse: function(parentModelData) {
if (!this.has('nestedModel') && !parentModelData.nestedModel) {
parentModelData.nestedModel = {};
}
return parentModelData;
}
This is clearly messy. Is there any other simpler way?
Thanks.
Related
In a Laravel environment (v 5.7) I've two tables: films and versions related in a 1 to Many relationship defined in Models as so:
class Film extends Model {
...
public function versions() {
return $this->hasMany(Version::class);
}
}
class Version extends Model {
...
public function film() {
return $this->belongsTo(Film::class);
}
}
I want to pick a random Version of a Film and its related parent Film, so in a controller I do this:
...
use App\Film;
use App\Version;
class HomeController extends Controller {
...
public function index() {
$rndVersion = Version::inRandomOrder()->first();
$relatedFilm = $rndVersion->film->first();
All works fine, except the fact that the returned film is always the first of the entire recordset, and not the parent of the version I previously loaded from the DB.
I've tried to retrieve all together with:
$rndVersion = Version::inRandomOrder()->with('film')->first();
and it works, but it packs all in a single object stored in $rndVersion, and I'd rather prefer to keep the two things separate.
So, what am I doing wrong?
What shall I generally do to get the parent record of a selected/loaded one?
Thank you in advance.
I found the issue...
The way you call the relationship method defined in the Model changes the returned result.
In general, if you call:
$model->relationship() //with parenthesis
an instance of the relationship is returned. You are calling the method as a method and, as it is for Eloquent model classes, it "serves as powerful query builder" and "provides powerful method chaining and querying capabilities" (see here).
Otherwise if you call:
$model->relationship //without parenthesis
you get directly an instance of the Collection. This way you are calling the method as a property and getting a Laravel Collection thus letting you loop on the collection, get record (model) properties and use all the methods of the Collection class.
So, in my case, writing
$rndVersione = Version::inRandomOrder()->first();
$rndFilm = $rndVersione->film()->first(); //film() WITH parenthesis
works, I guess because the first() method is called on the relationship as a method, therefore maintaining the correctness of query building.
My problem is this. I have a controller "FacsController", and a method.
public function access()
{
$facs = $this->Facs->find()->all();
return $facs;
}
This method works perfectly, data is returned properly.
But what I need is to call this method within another controller, "PagesController".
public function display()
{
$var = new FacsController();
$var->access();
$this->set('vars', $var);
$this->set('_serialize', ['vars']);
}
Unfortunately here I do not get the data returned from the FacsController.
Can someone help me? What am I doing wrong.
If you want data from a model, then you use the model, not a controller! If you ever feel the need that one controller needs to access another controller, then this is almost always an indicator for a failure in your application design. Also you never instantiate controllers yourself (unless in unit tests maybe)!
If you want to keep things DRY, create proper custom methods in your model (table class) and use them to encapsulate further logic.
That being said, like in every other controller, load the model via $this->loadModel(), or even TableRegistry::get().
$var = $this->loadModel('Facs')->find()->all();
See also
Cookbook > Controllers > Loading Additional Models
Cookbook > Database Access & ORM > Table Objects > Getting Instances of a Table Class
I have a Model where some of the data returned from the server is not editable by the client but is used for display purposes. However, there is some data that is editable and should be sent to the server. How should I separate these? I feel like this involves some sort of combination of overriding parse and toJSON or maybe save but I'm not sure how to go about this the right way. How would you do this?
My first reaction would be "don't mix the view state with the model", it would be much better to create a specific view-model, pass your data-model into initialize and set the fields you need on the view-model from the data-model there.
Overriding parse won't do anything since it's only used when parsing responses from the server.
Overriding toJSON and have it return only the attributes you're interrested in sending to the server should work. If you look at the Backbone source
// Ensure that we have the appropriate request data.
if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
params.contentType = 'application/json';
params.data = JSON.stringify(options.attrs || model.toJSON(options));
}
It should be using your model's toJSON function as long as you didn't pass any attributes to save.
Let say you are defining a Backbone.js Model. From the documentation we have ...
new Model([attributes], [options])
This seems great for passing some default attributes to a model. When passed the model automatically inherits those attributes and their respective values. Nothing to do. Awesome!
On they other hand lets say we have a Collection of that model.
new Backbone.Collection([models], [options])
Okay, cool we can pass some initial models and some options. But, I have no initial models and no options I need to pass so let's continue. I am going to fetch the models from the server.
collection.fetch([options])
Well I don't have any options, but I want to pass some attributes to add to each models as it is fetched. I could do this by passing them as options and then adding them to the attributes hash in the initialize for the model, but this seems messy.
Is their a Backbone.js native way to do this?
You can pass the attributes as options to fetch and over-ride the collection's parse method to extend the passed options (attributes) on the response.
The solution would look like the following:
var Collection = Backbone.Collection.extend({
url:'someUrl',
parse:function(resp,options) {
if (options.attributesToAdd) {
for (var i=0;i<resp.length;i++)
_.extend(resp[i],options.attributesToAdd);
}
return resp;
}
});
Then to add attributes when you call fetch on the collection, you can:
var collection = new Collection();
collection.fetch({
attributesToAdd:{foo:'bar',more:'foobar'}
});
You may have to tweak the code a bit to work with your JSON structure, but hopefully this will get you started in the correct direction.
I'm trying to minimize server calls by avoiding any requests I can.
Let's say, for the sake of a example, I have a collection of Matchboxes which belong to Users and have Tags assigned, and then also have a collection of Tags and a collection of Users as part of other pages. Getting matchboxes retrieves the user and tag info, so that I can instantiate all required models with one request, accessing the Tags and Users pages retrieves similar collections (only they deal only with their respective models).
My problem: if matchboxes is one page, and tags and users are two other pages, what's a good way to make sure only one model is ever instantiated for any given entity, ie. if I go into users or tags and edit an entry associated with a matchbox the matchbox entry should have the same entry assigned allowing it to listen and react to the updates with out requiring sending requests when going back to the matchbox page in the example.
I've looked over Backbone.relational but it doesn't seem to do what I need, and would rather not wall myself into a framework. So solutions involving patterns are preferable.
Ended up using http://pathable.github.io/supermodel/ which uses the pattern of overwriting the model attribute on collections with a custom function which calls a special Model.create that itself returns an existing (updated with the new values if necessary) instance of said model. The Model.create call has to be used everywhere else in code for unique models.
So essentially every model has a all() method which is a collection of all instances by id. Whenever a model is added it checks it against the collection and returns an existing object if it exists; the data used to instantiate the duplicate is used to update the existing object ensuring data is not stale (which is a nice bonus to the uniqueness I wanted).
The cleanest method seems to be to just wrap the model function into a function that returns it for clearer use; then for every collection that needs to have unique models wrap said model in the function. I came up with this at the moment:
app.single = function (modelPrototype) {
return function (attrs, options) {
return modelPrototype.create(attrs, options);
};
};
(app there is just a scope global, tied to a particular namespace)
So in collections instead of,
model: app.Model
I would then use
model: app.single(app.Model),
Whenever I update a entry in one part of the application the change will trickle down to every other collection/model since if it's the same instance from the user's perspective it's the same instance in code too.
That's about all I could tell from reading the pattern though the code and documentation. Which is sufficient for my own uses.
I suspect this solution would still have some issues if you're caching renders but I haven't found a use for that (prefer to re-render whenever I can to avoid dealing with various artifacts) so it's all good for me.
Unfortunately the codebase seems to be partially abandoned, so while it works with Backbone 1.0.0 (as far as unique models go), I may need to re-create/fork the pattern in future projects.
I think you should think twice about nesting your models and collections in this way, especially if it's primarily for the purpose of easing the bootstrapping of your app. Instead, try to use id's for inter-referencing between models as much as possible. This design problem you have is most likely only the first of many to come if you structure your model/collection tree in a certain way now, only to find it too inflexible later.
That being said, If all you need is for models referencing other models/collections to be able to refer to the same model/collection instance, then simply instantiating them during bootstrap and passing them in to their respective parent models would be sufficient. You could either load some bootstrap data in one request, or preferably inline that data in the HTML:
<script>
var bs_data = {
users : [
...
],
tags : [
...
],
matchboxes : [
...
]
};
</script>
And then instantiate the corresponding models or collections using the bootstap data.
var matchboxes = new Matchboxes();
matchboxes.set(bs_data.matchboxes);
var users = new Users({matchboxes:matchboxes});
users.set(bs_data.users);
The bootstrap data would come from the same backend so your models and collections would already be in sync without having to fetch anything.
As for design patterns; passing dependencies as constructor arguments is actually the dependency injection pattern, albeit more automated solutions to do so exist.
To make sure only one model is ever instantiated, and it is shared among the other elements that use it, being able to listen and update when any of the elements make a change to it, you can use a Singleton pattern. You can read more about it here
If you use Requirejs you can get same effect if you always return the model instantiated. For example:
// the shared model
define([
'jquery',
'underscore',
'backbone'
], function ($, _, Backbone) {
'use strict';
var Model = Backbone.Model.extend({
// ...
});
// return instantiated, so we'll get the same object back whenever we use this model (singleton)
return new Model();
});
// a view using the model
define([
'jquery',
'underscore',
'backbone',
'model'
], function ($, _, Backbone, modelInstance) {
'use strict';
var View = Backbone.View.extend({
initialize: function () {
// listen to what other elements do
this.listenTo(modelInstance, 'eventFromOtherElement', this.doSomething);
// when this element does something, other elements should be listening to that event
modelInstance.trigger('thisViewEvent');
},
doSomething: function () {
// ...
}
});
return View;
});