I create backbone collection from server JSON. Data is from mongo so each item has same objects and backbone remove this duplicates. It's unwanted behavior for me so, I can't find solution to keep this instances. After fetch my items has only 'section1' in secound object (id:2). I need the same section also in first object. For example my server response is:
items: [{
id:1,
sections: [{
id: 1.//this object is removed
name: 'section1'
}]
}, {
id: 2,
sections: [{
id:1.
name: 'section1'
}]
}]
My section model is just:
Section = Backbone.RelationalModel.extend({
});
and Item model:
Item = Backbone.RelationalModel.extend({
relations: [
{
'type': 'HasMany',
'key': 'sections',
'relatedModel': 'Section',
'includeInJSON': 'id',
'reverseRelation': {
'key': 'item',
'includeInJSON': 'id'
}
}
]
});
If I recall correctly, this plugin doesn't support many-to-many. So, what's happening is that it is attaching Section 1 to the first Item, then attaching it to the second and removing it from the first.
In fact, from the docs:
Backbone.HasMany
Defines a HasMany relation. When defining a reverseRelation, the type
will be HasOne.
Your options:
Create a SectionItem model that HasOne Section and HasOne Item. Someone posted a fiddle with this sort of setup http://jsfiddle.net/mmacaula/XaESG/2/
Use another library - or an extension of the one you use, like https://github.com/jj-studio/Backbone-JJRelational
Add a property to the Section model key that would make each one unique. This is not a good way to achieve what you are trying to do, though.
Here is a pretty good reference answer: Implementing a Many-to-Many relationship with Backbone-Relational
Related
I have a Node.js/Express/Sequelize project where I'm storing a library of images with related keywords, and I want to be able to search for images by those keywords.
I have an images table with a many-to-many relationship to keywords, and a keywords table with a many-to-many relationship to images.
Image.belongsToMany(Keywords, {
as: 'keywords',
through: {
model: ImageKeywords,
unique: false
},
foreignKey: 'image_id',
constraints: false
});
Keywords.belongsToMany(Image, {
as: 'image',
through: {
model: ImageKeywords,
unique: false
},
foreignKey: 'keyword_id',
constraints: false
});
I have an array of keyword IDs to search with using AND, and an array of keywords to exclude from the search using NOT. These are entered by the user via a search form, but essentially the following demonstrates the structure.
let keywordsAnd = [1,2,3]
let keywordsNot = [4,5,6]
For each image, I've associated one or many keywords through the join table, and can fetch images and display all of their keywords without any difficulty.
What I'm trying to accomplish is when a user enters a list of keywords they want to search for, and specifies some to exclude, I want to find all of the images using an AND search with the keywordsAnd array, and exclude those in the keywordsNot array.
For the above example, I want to return any image associated with all of the keywords with the IDs 1, 2 or 3, and excludes any of those images associated any of the keywords of ID 4, 5 or 6.
I was trying to use the following via the Keywords.findAll path:
Keywords.findAll({
include: [{
model: Image,
as: 'image',
include: [{
model: Keywords,
as: 'keywords',
attributes: ['id', 'name']
}]
}],
where: {
id: {
[Op.and]: [
keywordsAnd,
{ [Op.not]: keywordsNot }
]
}
}
})
This appears to do an OR search using the keywordsAnd array, and throws an error if keywordsNot is defined.
So with the above [1,2,3] array, I get all the images with the keyword IDs 1, 2 or 3.
It occurs to me that perhaps I should be searching through Image.findAll, including the Keywords model, but I'm not familiar enough with the mechanics of Sequelize at this point to know the correct approach and syntax, so any guidance would be appreciated.
First of all, you can use Image.findAll instead Keywords.findAll as first query and within use include in order to create a join with Keywords in SQL query level.
Inside include you can add a where clause as you can see above in order to alter Keywords research.
Finally you can use Op.in and Op.notIn, both of them are used to work with arrays.
See Sequelize documentation and put special attention to Relations / Associations and Operators sections.
I hope it helps!
Image.findAll({
include: [ {
model: Keywords,
as: 'Keywords',
attributes: ['id', 'name'],
where: {
id: {
[Op.and]: [
{ [Op.in]: keywordsAnd },
{ [Op.notIn]: keywordsNot }
]
}
}
} ]
});
In my Ember app, I have a complex model that looks like below (kind of contains 2-dimensional array)
[
[
{
id: 'Section1_123',
label: 'abc'
},
{
id: 'Section1_456',
label: 'xyz'
}
]
],
[
[
{
id: 'Section2_123',
label: 'abc'
},
{
id: 'Section2_456',
label: 'xyz'
}
]
]
There are a lot of other attributes, but this is the overall structure.
Now my question is can I drill-down & find a specific object. It has unique ids (as shown in the example above)
So I need something like model.findBy(Id)
I then need to change/set some values for that object. Say I want to change the obj.label from 'abc' to 'abc_NEW'
Just to add, The main model is actually a simple JS array...but the inside objects (e.g. those with id: 'Section1_123', etc) are actually Ember objects
Most common approach to work with data in Ember is EmberData. And because the main credo of Ember is "convention over configuration" then a common way in Ember is the best way, in my opinion.
There are many ways how to deal with your data format. I would recommend to create model for each item:
import DS from 'ember-data';
export default DS.Model.extend({
label: DS.attr()
// other properties
});
Then you can make a custom serializer according this article. The goal is to convert your arrays to list of EmberData models.
After this you can use standard EmberData functions to work with data (including access by object id, of course).
I am using Sequelize for Node and I have Users and items. A user can interact with an item in multiple ways, such as voting, or commending (two different things in this case).
My initial thought was to have two different many to many relationship but Sequelize only has one removeItem call, and I haven't figured out how to specify which.
Plan 2 was to build one many to many table with an extra element "type" so I could specify the kind. I can specify the type when the thing is added. This ran into two problems, one they are forign keys and by default that made the pair unique. I corrected this by removing the constraint manually from the DB, and that seemed to be sufficient (I set an id as primary key autoincrement). Secondly, I cannot seem to figure out how to make the delete specify where type = the type I want.
Code:
To build the table:
var UserActions = sequelize.define('userActions', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
type: DataTypes.STRING
});
To remove the item:
models.Item.findOne({where: {id : params.itemID}}).then(function (video){
models.User.findOne({where: {id : params.itemID}}).then(function (user){
if(params.applaud == "true") {
console.log("Add applaud");
user.addItem(video,{type:"commend"});
} else {
user.removeItem(item,{where: {type:"commend"}});
console.log("Remove, Commendation");
}
res.json({'response' : 'Success'});
});
});
The issue is this will delete all interactions between the item and the user, when I want it to only delete the commendation.
How do I best manage multiple many to many relationships between the same two tables in Sequelize?
OK I figured it out.
On the association you have to specify an alias via 'as'
User.belongsToMany(models.Item, {through: 'votes', as: 'votes'});
User.belongsToMany(models.Item, {through: 'commend', as: 'commend'});
Then you can add query with:
models.Item.findOne({where: {id: item.id},
include: [{
model: models.User,
as: 'votes'
}]
})
You can add with:
user.addVote(item);
or
user.addCommend(item);
And delete with:
user.removeVote(item);
or
user.removeCommend(item)
And it works as desired.
I successfully implemented loading and showing relations with 'Backbone Relational' from an API I created. I get how things work by trial and error. I do think the docs are lacking some clarity though since it took a lot of time to figure out how things work. Especially on how to map things to the API I think the docs are lacking a bit.
Problem
Adding a bookmark works, it's the editing and deletion that don't work. The PUT becomes a POST and the DELETE simply doesn't fire at all. When I set an id to the model hardcoded it does work. So the id is missing which makes sense for the PUT becoming a POST.
The problem seems to be that the id doesn't hold an actual id, but a collection. The view where the problem occurs does not requires the BookmarkBinding, it's used somewhere else. Simply the fact that it has Bookmark as a relation makes the DELETE and PUT break.
BookmarkBinding model:
App.Model.BookmarkBinding = Backbone.RelationalModel.extend({
defaults: {
set_id: null,
bookmark_id: null
},
relations: [{
type: Backbone.HasOne,
key: 'bookmark',
relatedModel: 'App.Model.Bookmark',
reverseRelation: {
type: Backbone.HasOne,
key: 'id'
}
}],
urlRoot: 'http://api.testapi.com/api/v1/bookmark-bindings'
});
Bookmark model:
App.Model.Bookmark = Backbone.RelationalModel.extend({
defaults: {
url: 'undefined',
description: 'undefined',
visits: 0,
},
relations: [{
type: Backbone.HasMany,
key: 'termbindings',
relatedModel: 'App.Model.TermBinding',
reverseRelation: {
key: 'bookmark_id',
}
}],
urlRoot: 'http://api.testapi.com/api/v1/bookmarks'
});
From Backbonejs.org
The default sync handler maps CRUD to RESTful HTTP methods like so:
create → POST /collection
read → GET /collection[/id]
update → PUT /collection/id
delete → DELETE /collection/id
Your question suggests that you're making an HTTP PUT request, and therefore a Backbone update. If you want to make an HTTP POST, use Backbone create. The PUT request maps onto update, and requires that an id be sent in the URL, which isn't happening according to your server log. If your're creating a new object, then most server-side frameworks such as Rails / Sinatra / Zend will create an id for the object
Another possible source of error is the keys that you chose for the relations, like you suspected.
A Bookmark has many BookmarkBindings, and it seems that Backbone-relational will store them in the field that you specify in BookmarkBindings.relations.reverseRelation.key, which is currently defined as 'id'.
So the collection of related BookmarkBindings ids will to be stored on the same attribute as the Bookmark.id, creating a collision. Backbone.sync will send an undefined value to the server (which you see in your logs), because it finds a collection there instead of an integer.
First suggestion - You may not need a bidirectional relation, in which case drop it from the BookmarkBinding model.
Second suggestion - define the reverse relation on another key, so that it doesn't collide with Bookmark.id, such as BookmarkBindings.relations.reversRelation.key : 'binding_ids'
due disclosure - I've never used Backbone-relational.js, only Backbone.js.
The problem was that on editing or deleting the bookmark model, the bookmark binding model wanted to do it's work too since it is related too the bookmark from it's side. I already tried to remove the reverse relation which didn't prove to be a solution since in the other part of my application where I used the bookmark bindings things wouldn't work anymore.
Solution
I did end up removing the reverse relation (#jarede +1 for that!), but the crux was how to implement the foreign key to fetch relations from the API without a reverse relation. I ended up adding the keySource and keyDestination which made everything work out.
Sidenote
Backbone Ralational cannot handle identical foreign keys either, this gave me some problems too. This will make the lastly declared foreign key overwrite all the previous ones. This can be quite impractical since within an API it's not uncommon that model's are related to a column named id. So the idAttribute can be set with idAttribute: '_id' for example, but the foreign key has to be unique across your application.
BookmarkBinding model:
App.Model.BookmarkBinding = Backbone.RelationalModel.extend({
defaults: {
set_id: null,
bookmark_id: null
},
relations: [{
type: Backbone.HasOne,
key: 'bookmark',
keySource: 'id',
keyDestination: 'bookmark',
relatedModel: 'App.Model.Bookmark'
}],
urlRoot: 'http://api.testapi.com/api/v1/bookmark-bindings'
});
Note: I imagine that this will be a super easy question for anyone with Ember experience. Don't be daunted by the length of my question. Skip down to the bottom if you don't want to read all of the overhead.
Overhead
My company has a project coming up which requires the use of front-end technologies to accomplish what I would otherwise prefer to do with PHP.
We looked into a few different JavaScript frameworks, and the solution that we agreed upon was Ember.js.
I followed the TodoMVC tutorial on their website, and learned the very basics.
With this project, we will be using an AJAX request to pull in our data at the start of the application, and then put everything into fixtures.
I'm having difficulty figuring out how to pass multiple fixtures into my template at the same time. I started with adding two fixtures. Here are their data and definitions:
App.Students = DS.Model.extend({
first: DS.attr('string'),
last: DS.attr('string'),
classes: DS.hasMany('ClassGroup')
});
App.ClassGroup = DS.Model.extend({
className: DS.attr('string'),
isActive: DS.attr('number'),
students: DS.hasMany('Students',{ async: true })
});
App.ClassGroup.FIXTURES = [
{
id: 1,
className: 'Class 1',
isActive: 1,
students: [1, 2]
},
{
id: 2,
className: 'Class 2',
isActive: 0,
students: [2]
}
];
App.Students.FIXTURES = [
{
id: 1,
first: 'Student',
last: 'One',
classes: [1]
},
{
id: 2,
first: 'Student',
last: 'Two',
classes: [1, 2]
}
];
This is just a very simple implementation. The actual data will have dozens of relations, but I've simplified things for the purpose of learning this framework.
Here is how I am currently setting up my router:
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.store.find('ClassGroup');
}
});
Doing it this way allows me to access the data via Handlebars by using {{#each model}} and then something like {{className}} to access it's data members. I am also able to jump to the students array by nesting another each statement like {{#each students}}.
However, I cannot figure out how to get more than one entry point into my data. For example, by passing ClassGroup via the router, I am unable to access the Students fixture by itself; I must first loop through ClassGroup, and from there, access a student.
Likewise, if I change my router to pass in the students fixture, I can loop through the students, and then jump to the classes via a nested each statement, but I lose the ability to simply loop through a list of all classes.
Is there a way that I can pass all of my fixtures into the template? Am I going about this the right way?
The Long Story Short
How can I pass ALL of my fixtures into the template at once, in such a way that I can access my students array or my classes array? How can I access said fixture data (i.e., if I want to display the first name of the student with ID 2, represented as students[2]['first'] in a language like PHP, how can this be done with handlebars)?
That's right, the Template only has access to what it's been passed by the Controller. In this case, since you don't explicitly set up the controller, and the model is an array, it'll be an array controller, hence you ability to do {{#each}} to iterate over the ClassGroups (you actually don't even need model). You haven't passed in the students array anywhere explicitly, nor created it in the controller, so you don't have access to it in the template. Fortunately, Ember has a setupController route hook which does exactly this kind of thing. In your example:
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.store.find('ClassGroup');
},
setupController: function(controller, model){
this._super(controller, model);
controller.set('students', this.store.find('Students'));
}
});
Now you'll have a students property available on your controller and therefore your template.