Best(?) practice for loading & saving relational data in Polymer - javascript

What is the best, or just a good, practice for loading and saving relational data in Polymer elements' published properties?
I've used https://github.com/PaulUithol/Backbone-relational to load and save relational data. It depends on Backbone. But now with Polymer's use of Object.observe(), I mostly don't need Backbone's complex model objects (at least I don't need their get() and set() methods), but I can't figure out how I can best get rid of Backbone's complex model objects and just use plain JavaScript objects AND load and save relational data to my data store.
Is there a Polymer-compatible library/web component out there which already implements this? Or a native way to do it?
Here are a couple ways I could do it myself without a library, but I expect I'm missing lots of edge cases.
Load relational data:
// Load from server
model = store.get('model-id');
// model == {'id':'abc', 'name':'Parent', child_ids:['child-id1', 'child-id2']}
for (child_id in model.child_ids){
model.children[child_id] = store.get(child_id);
}
// Use model in Polymer element's published property here
Save relational data:
// Get model from Polymer published property here
model.child_ids = [];
for (child in model.children){
model.child_ids.push(child.id);
}
delete model.children;
store.set(JSON.stringify(model));
// or just store.set(model);

Related

JavaScript Object vs minimongo efficiency

My Meteor client receives data from the server and stores it in minimongo. This data is guaranteed not to change during their session, so I don't need Meteor's reactivity. The static data just happens to arrive by that route; let's just take that as a given.
The data looks like this:
{_id: 'abc...', val: {...}}
On the client, is it more efficient for me to look up values using:
val_I_need = Collection.findOne({id})
or to create a JavaScript object:
data = {}
Collection.find().fetch().map((x) => {data[x._id] = x.val})
and use it for look ups:
val_I_need = data[id]
Is there a tipping point, either in terms of the size of the data or the number of look ups, where the more efficient method changes, or outweighs the initial cost of building the object?
FindOne may be more efficient on larger datasets because it looks up using cursors where _id is an indexed key while your find().fetch() approach requires to get all docs and then iterate manually by mapping.
Note, that findOne could also be replaced by .find({_id:desiredId}).fetch()[0](assuming it returns the desired doc).
More on this in the mongo documentation on query performance.
However, if it concerns only one object that is afterwards not reactively tracked, I would rather load it via a "findOne"-returning method from the server:
export const getOne = new ValidatedMethod({
name: "getOne",
validate(query) {
// validate query schema
// ...
},
run(query) {
// CHECK PERMISSIONS
// ...
return MyCollection.findOne(query);
});
This avoids using publications / subscriptions and thus minimongo for this collection on the current client template. Think about that pub/sub has already some reactivity initialized to observe the collection and thus eats up some computation somewhere.
My gut feeling is that you'll never hit a point where the performance gain of putting it in an object makes a noticeable difference.
It's more likely that your bottleneck will be in the pub/sub mechanism, as it can take a while to send all documents to the client.
You'll see a much more noticeable difference for a large dataset by retrieving the data using a Meteor method.
At which point you've got it in a plain old javascript object anyway and so end up with the small performance gain of native object lookups as well.

Binding data into view - Is it better to pass data down from parent views to child/grandchild views or initialize/query data whenever creating views?

I am working on a Backbone/Marionette project. This project implements a way to cache data on local memory after loading them from server. Therefore data can be access anytime, anywhere within the project.
This makes me wonder what is the better way to populate data to view in my case:
const ChildView = marionette.View.extend({/*...*/});
const ParentView = marionette.View.extend({
// ...
onRender() {
// 1: pass data to child view from parent view
const childView = new ChildView({
data: this.options.data,
}));
// 2: initialize data when creating new child view
const childView = new ChildView({
data: SomeModel.new({/* some properties */}),
}));
},
// ...
});
new ParentView({
data: SomeModel.new({/* some properties */}),
}).render();
Both methods work correctly. However, the project view structure is pretty deep and complicated so I prefer the second way because with the first one I would need to go up and down a lot to check what data is and where it comes from.
Do you think if there are any possible problems with this method?
I prefer the 1st way, passing data from parent to child, but it depends on what your views are doing.
For me, a big advantage of sharing a data object is that updating it within one view updates it in all other views (this will work if you pass an existing backbone Model, or any object as data). This can save a lot of work... when a user updates their background color (for example), you can update it once in your BackgroundColorChoose view, and know that it is already updated everywhere else that data is in use.
In a sense, it doesn't matter where the data came from, only what it represents (because it can be accessed/modified from within any of your views).
I can imagine scenarios where this approach is not good, but I've found it makes a good baseline to start from (and avoids the need to trust browser-caching)

Advice on structuring Backbone array data

I'm in the middle of a process where I'm retrieving data from a REST API for use in my Backbone.js application. At this point I don't need to do any manipulations to the data except for the fact that the data returned is an array. However, the future might bring such requests, so I've already created a Backbone model and collection for this type of data.
I've read that you could map your Array data into an object inside your Backbone collection, but I'm wondering since I already have a model, if it would be better practise to already map each element inside my Backbone model.
Since I'm not an expert in the Backbone.js framework, any links with more documentation about this section would be greatly appreciated.
UPDATE: I was actually looking for the parse method that is provided by the BackboneJS framework. By transforming the Array into an Object in the parse function I was able to solve the question.
You can use the parse method to parse any kind of transformation you'd like to do, like e.g. copying attributes, modifying attributes etc.
More information : http://backbonejs.org/#Collection-parse
Just as in the question you mentioned, this can achieved using parse, either on the Collection or the Model.
var UserModel = Backbone.Model.extend({
// String name is mapped to an object with the name property
parse: function(name) {
return {
name: name
};
}
});
var UserCollection = Backbone.Collection.extend({
model: UserModel
});
var collection = new UserCollection(['Ann', 'Joe', 'Jim', 'Bob'], {parse: true});
console.log(collection.at(0).get('name'));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
In the example above, the collection is instantiated with data, in this case, parse doesn't run by default, so it needs to be set in the options, however if the collection normally gets its data from fetch, this will by default always parse.

Why is Backbone model sending duplicate attributes to server on save?

I'm writing a practice Backbone app, with Rails backend API, and I'm confused about the behavior of save on Backbone models.
Let's say a Team has many Players, and I want to save a team with numerous players in a single POST.
So in Rails I have:
class Team < ActiveRecord::Base
has_many :players
accepts_nested_attributes_for :players
end
class Player < ActiveRecod::Base
belongs_to :team
end
and for backbone client, I have a Player model and a Players collection defined (not shown)
and then the containing Team model (NOTE: no Teams collection)
Demo.Models.Team = Backbone.Model.extend({
urlRoot: '/teams',
defaults: {
'team_size': 12
},
initialize: function() {
this.players = new Demo.Collections.Players());
},
toJSON: function() {
var json = _.clone(this.attributes);
json.players_attributes = this.players.map(function(player) {
return player.toJSON();
});
return json;
}
}
When I examine my stringified JSON in the browser, everything looks good:
{"team_size":12, "players_attributes":[{"name":"Fred"},{"name":"Jim" },{"name":"Mark"}]}
Checking the server logs, the lone top level attribute ('team size') is repeated, once at the top level, and then repeated under a root key.
Started POST "/teams" for 127.0.0.1 at 2012-06-07 13:39:40 -0400
Processing by TeamsController#create as JSON
Parameters: {
"team_size"=>12, "players_attributes":[{"name":"Fred"},{"name":"Jim" },{"name":"Mark"}]},
"team"=>{"team_size"=>12}
}
I have a few questions:
What's the best way to ensure the player_attributes are nested inside the root key? I (So that I can do a nested save inside TeamController, in the standard rails manner: (i.e. Team.create(params[:team]) ) I can accomplish this with some javascript hackery inside toJSON, but I'm guessing there's an easier, cleaner way.
Is this standard, desirable behaviour? To send duplicates of attributes like this? I guess there's no harm, but it doesn't smell right.
Am I not defining the url / urlRoot correctly or some such?
thanks
1- You have to override the toJSON method in order to include the model name as the root of the JSON element sent to the server.
toJSON: function() {
return { team: _.clone( this.attributes ) }
},
Since you are already messing and overriding this method I don't see any reasons not to go this way.
2- This is a very strange behavior you're describing. Try:
class Team < ActiveRecord::Base
self.include_root_in_json = false
end
It will probably eliminate Rails duplicate params parsing. Another advantage you get from this is that Rails won't include the team as a root element of its generated JSON to the client.
3- Your definition of urlRoot is just fine.
I arrived here while looking for same issue. So even it's an old question I think it's worth giving the answer.
I actually found a Rails setting that explain these duplicate attributes: wrap_parameters
http://apidock.com/rails/v3.2.13/ActionController/ParamsWrapper/ClassMethods/wrap_parameters
Just set it to an empty array, and rails won't try to wrap parameters coming from your JSON requests.
Although you can use the toJSON hack mentioned by others, this is actually not such a good idea. For one, it produces an inconsistent result between sync and save with {patch: true} (this inconsistency is because the sync method calls toJSON if you don't patch, but doesn't call toJSON if you have patch set to true)
Instead, a better solution is to use a patched version of Backbone that overload the sync method itself. The backbone-rails gem does this automatically, or you can pull backbone_rails_sync.js into your own app. A more complete answer to this question can be found here: Backbone.js and Rails - How to handle params from Backbone models?

Backbone.js Collection and Tastypie filters

Instead of retrieving the entire table of records for a Backbone collection I've defined, I reckon it's much more efficient to make use of the tasty-pie filters I've created. How would I let Backbone use them? As far as I understand, Backbone models/collections only point to the top-level URI of the model.
e.g. I have a Bookings model defined in Django that can be accessed via "/api/booking" but i want to populate the Backbone Collection with "/api/booking?room=3" where the room number is context specific.
I've found out that backbone's .fetch() method accepts the "data" attribute just like JQuery .ajax().
booking.fetch({ data : { "room" : 3 } });

Categories

Resources