Let's suppose I have two simple fixture files, one for the user(1) and one for the messages(2).
The Backbone Model for the messages is the following (3).
If I load the "Message Fixture", I would like to have also the related info regarding the user as specified in Message Model.
What is the proper way to active this goal in a spec view (4) by using jasmine test suite?
Please see the comments in (4) for more details.
(1)
// User Fixture
beforeEach(function () {
this.fixtures = _.extend(this.fixtures || {}, {
Users: {
valid: {
status: 'OK',
version: '1.0',
response: {
users: [
{
id: 1,
name: 'olivier'
},
{
id: 2,
name: 'pierre',
},
{
id: 3,
name: 'george'
}
]
}
}
}
});
});
(2)
// Message Fixture
beforeEach(function () {
this.fixtures = _.extend(this.fixtures || {}, {
Messages: {
valid: {
status: 'OK',
version: '1.0',
response: {
messages: [
{
sender_id: 1,
recipient_id: 2,
id: 1,
message: "Est inventore aliquam ipsa"
},
{
sender_id: 3,
recipient_id: 2,
id: 2,
message: "Et omnis quo perspiciatis qui"
}
]
}
}
}
});
});
(3)
// Message model
MessageModel = Backbone.RelationalModel.extend({
relations: [
{
type: Backbone.HasOne,
key: 'recipient_user',
keySource: 'recipient_id',
keyDestination: 'recipient_user',
relatedModel: UserModel
},
{
type: Backbone.HasOne,
key: 'sender_user',
keySource: 'sender_id',
keyDestination: 'sender_user',
relatedModel: UserModel
}
]
});
(4)
// Spec View
describe('MyView Spec', function () {
describe('when fetching model from server', function () {
beforeEach(function () {
this.fixture = this.fixtures.Messages.valid;
this.fixtureResponse = this.fixture.response.messages[0];
this.server = sinon.fakeServer.create();
this.server.respondWith(
'GET',
// some url
JSON.stringify(this.fixtureResponse)
);
});
it('should the recipient_user be defined', function () {
this.model.fetch();
this.server.respond();
// this.fixtureResponse.recipient_user is not defined
// as expected by the relation defined in (3)
expect(this.fixtureResponse.recipient_user).toBeDefined();
});
});
});
});
Take a look at this series of tutorials http://tinnedfruit.com/2011/03/03/testing-backbone-apps-with-jasmine-sinon.html
This is the specific part about Model testing.
Don't know if will solve your problem, but may contain precious info.
this.fixtureResponse is the source data for the model, but when the model is actually created it makes a copy of that data to an internal property. So, when Backbone Relational resolves the relation, it shouldn't change the source data object.
Did you tried with expect(this.model.get('recipient_user')).toBeDefined()?
Backbone-Relational provides the ability to either create a related model from nested entities within JSON retrieved via the model's fetch or to lazily load related models using fetchRelated.
You're providing Backbone-Relational with the message model data but no way to retrieve the user model data. You could add another response returning the appropriate related user data and call fetchRelated on the message model.
Alternatively inline the user data into the message response and the user model will be created automatically and added as a relation on the message model.
Related
I'm trying to learn about API's and am creating a simple response when a user hit's an endpoint on my basic express app.
I'm not sure what constitutes a correct API response though, does it need to be an object?
The data I was given is an array of arrays and I export it from a file and return it in the express app as an object using response.json(), it displays ok but I'm not sure if I'm following the right approach for how I'm handling it and displaying it.
I would like to understand how should API data be returned, I had the feeling that it should always be an object but what I'm returning here is an array of arrays, some explanation to understand this better would be helpful please.
Here is my data file:
export const data = [
[
'a9e9c933-eda2-4f45-92c0-33d6c1b495d8',
{
title: 'The Testaments',
price: { currencyCode: 'GBP', amount: '10.00' },
},
],
[
'c1e435ad-f32b-4b6d-a3d4-bb6897eaa9ce',
{
title: 'Half a World Away',
price: { currencyCode: 'GBP', amount: '9.35' },
},
],
[
'48d17256-b109-4129-9274-0bff8b2db2d2',
{ title: 'Echo Burning', price: { currencyCode: 'GBP', amount: '29.84' } },
],
[
'df2555ad-7dc2-4b1f-b422-766184b5c925',
{
title: ' The Institute',
price: { currencyCode: 'GBP', amount: '10.99' },
},
],
];
Here is a snippet from my express app file which imports the data object for this endpoint:
app.get('/books', (request, response) => {
response.json({ books: data });
});
Looks okay, one thing I would do is ensure the root of the object you return always has the same name between api calls, so here your root object name is "books":
app.get('/books', (request, response) => {
response.json({ books: data });
});
I would personally change this to something like:
app.get('/books', (request, response) => {
response.json({ data: data });
});
Now that may seem confusing, but what that means is when your api is queried, the actual resources the api is returning is always in an object called data, it just builds a common pattern for where people should be expecting to see the api response resources in your JSON response.
So if you had another endpoint for authors for example, people will know the authors response will be under an object called data:
app.get('/authors', (request, response) => {
response.json({ data: authors });
});
I am trying to build an app that has a many to many relationship in Meteor. There will be jobs, clients and users collections. Clients can have multiple jobs, and most importantly multiple users can work on the same job.
I have the jobs collection set up as follows in my fixtures file:
Jobs.insert({
jobNum: 'Somejob',
clientId: 'XXXXXXXX',
clientName: 'Some Client',
rate: XX,
userNames: [
{userId: user1._id},
{userId: user2._id}
],
active: true
});
I am publishing according to the readme for publish-composite, but I cannot get the users to publish to the client. Here is the publication code:
Meteor.publishComposite('jobsActive', {
find: function() {
// Find all active jobs any client
return Jobs.find({active: true});
},
children: [
{
find: function (job) {
// Return a client associated with the job
return Clients.find({_id: job.clientId});
}
},
{
find: function (job) {
// Return all users associated with the job
// This is where the problem is
return Meteor.users.find({_id: job.userNames.userId});
}
}
]
});
I can't figure out how to correctly find over an array. I tried a number of things and nothing worked. Is this possible? Or do I need to go about this in another way? I've thought about referencing jobs in the users collection, but there will be far more jobs than users, so it seems to make more sense like this.
BTW, I did subscribe to 'jobsActive' as well. The other two collections are coming over to the client side fine; I just can't get the users collection to publish.
Thanks for any help and ideas.
job.userNames.userId doesn't exist in your collection. job.userNames is an array of objects which have the key userId.
Try something like _.map( job.userNames, function( users ){ return users.userId } ).
Your code will be:
Meteor.publishComposite('jobsActive', {
find: function() {
return Jobs.find({active: true});
},
children: [
{
find: function (job) {
return Clients.find({_id: job.clientId});
}
},
{
find: function (job) {
return Meteor.users.find({ _id: { $in: _.map( job.userNames, function( users ) { return users.userId } ) } });
}
}
]
});
I think you don't need publish-composite at all, try this code snippet. It works for me!
Meteor.publish('jobsActive', function () {
return Events.find(
{
$or: [
// { public: { $eq: true } },
{ active: true },
{ userNames: this.userId}
],
},
{
sort: {createdAt: -1}
}
);
});
I have merged together from this two problem (How to pass model in Nested routes - emberjs and Embedded data from RestApi) a JsBin example: http://jsbin.com/OxIDiVU/544
It works fine if you navigate customers-> info -> contact, but it will break if one calls directly a customer's contact eg.:http://jsbin.com/OxIDiVU/544#/customers/3/contact
Error while loading route: customer.contact Cannot set property 'store' of undefined TypeError: Cannot set property 'store' of undefined
When you do a request for a single record, it uses a different serializer endpoint and expects the data in a different format. The format it expects is:
{
customer: {
id: 1,
currency:1
},
currencies: [
{
id:1,
prop: 'foo'
}
]
}
And the endpoint in the serializer is extractSingle. Feel free to extract out the portions of extractArray that are similar and share those.
Pretending your payload is:
{
customer:{
id:3,
name:"Joue",
currency:{
id:5,
iso_code:"BDT"
}
}
}
Your extractSingle would be
extractSingle: function(store, type, payload, id) {
var customer = payload.customer,
currencies = [];
var currency = customer.currency;
delete customer.currency;
if(currency){
currencies.push(currency);
customer.currency = currency.id;
}
payload = { customer:customer, currencies: currencies };
return this._super(store, type, payload, id);
}
Here's the example, with a response for customer 3
http://jsbin.com/OxIDiVU/545#/customers/3/contact
your property name should match inside the model, and the root name (currencies here) should be the plural version of the type of record it is.
{
customer: {
id: 1,
default_currency:1
},
currencies: [
{
id:1,
prop: 'foo'
}
]
}
I've been trying my hand at Backbone recently and have a very basic question.
I need to search for different kind of records and the search API returns a JSON response like
{ foo:
[
{ name: 'foo1', url: '/foo1' },
{ name: 'foo2', url: '/foo2' }
],
bar:
[ { name: 'bar1', url: '/bar1' } ],
baz:
[ { name: 'baz1', url: '/baz1' } ]
}
I have a backbone model for Foo, Bar and Baz. A collection which on fetch should hit my server and get me the search results. I attempted something like
window.searchEntities = Backbone.Collection.extend({
url: '/get_search_results'
model: function(attrs, options) {
//Typecast the JSON to Foo, Bar and Baz Models
});
});
However, I do not know how to parse the results returned by server so that my collection holds models Foo, Bar and Baz? Or should I tweak the results returned by server so that its easier to handle this with Backbone?
As I'm seeing your JSON is not returning 3 different Models but 3 different Collection due the 3 of them are containing Arrays.
I think you should start from the beginning, If I understood weel you want to return a bunch of Models of different type, let's say:
[
{
type: "Foo",
name: "foo1",
url: "/foo1"
},
{
type: "Foo",
name: "foo2",
url: "/foo2"
},
{
type: "Bar",
name: "bar1",
url: "/bar1"
},
{
type: "Baz",
name: "baz1",
url: "/baz1"
},
]
I see a Collection there, and also different Models of different types.
Now let's see the SearchCollection, I don't think you can play with the model property like you show in your example, so let's say all the Models has a common Model parent Result:
window.SearchEntities = Backbone.Collection.extend({
url: '/get_search_results'
model: Result
});
From here we can do it simple and don't create sub-classes of Result if there is not a real need for it:
window.Result = Backbone.Model.extend({
initialize: function(){
this.url = this.get( "url" );
}
});
And you're done:
var searchEntities = new window.SearchEntities();
searchEntities.fetch();
// ... wait for the fetch ends
searchEntities.at(0).get( "type" ); //=> "Foo"
Still I don't feel confortable by two reasons:
I don't see clear why you want to play with the Result.url.
Where are the ids of your Models? which are very important for Backbone.
I would like to make a relation between two models User and Task using backbone-relational.
The relation between the two models is the following:
taskModel.creator_id = userModel.id
// TaskModel
var TaskModel = Backbone.RelationalModel.extend({
relations: [
{
type: Backbone.HasOne,
key: 'creator',
keySource: 'creator_id',
relatedModel: Users
}
],
// some code
});
// Task collection
var TaskCollection = Backbone.Collection.extend({
model: TaskModel,
// some code
});
// User Model
var User = Backbone.RelationalModel.extend({
// some code
});
Actually the problem is in the collection.models, please see the attached images:
Please check this jsfiddle: http://jsfiddle.net/2bsE9/5/
var user = new User(),
task = new Task(),
tasks = new Tasks();
task.fetch();
user.fetch();
tasks.fetch();
console.log(user.attributes, task.attributes, tasks.models);
P.S.:
Actually I am using requireJs to get the UserModel, so I cannot include quotes in relatedModel value.
define([
'models/user',
'backbone',
'relationalModel'
], function (User) {
"use strict";
var Task = Backbone.RelationalModel.extend({
relations: [
{
type: Backbone.HasOne,
key: 'creator',
keySource: 'creator_id',
relatedModel: User
}
],
});
);
Edit 2:
http://jsfiddle.net/2bsE9/13/
I updated the jsfiddle to reflect the changes I suggested below. As long as you are calling toJSON on your task, what gets to the server is a json object with the creator_id property set to the actual id of the user. The keyDestination here is redundant as the documentation states it is set automatically if you use keySource.
Edit:
https://github.com/PaulUithol/Backbone-relational#keysource
https://github.com/PaulUithol/Backbone-relational#keydestination
https://github.com/PaulUithol/Backbone-relational#includeinjson
The combination of the three above might solve your issue.
var Task = Backbone.RelationalModel.extend({
relations: [
{
type: Backbone.HasOne,
// The User object can be accessed under the property 'creator'
key: 'creator',
// The User object will be fetched using the value supplied under the property 'creator_id'
keySource: 'creator_id',
// The User object will be serialized to the property 'creator_id'
keyDestination: 'creator_id',
// Only the '_id' property of the User object will be serialized
includeInJSON: Backbone.Model.prototype.idAttribute,
relatedModel: User
}
],
});
The documentation also states that the property specified by keySource or keyDestination should not be used by your code. The property cannot be accessed as an attribute.
Please try this and comment if that fixes your issue.
Btw, here is a nice blog post that uses backbone-relational end to end.
http://antoviaque.org/docs/tutorials/backbone-relational-tutorial/
Edit
Updated jsfiddle
The problem is that Backbone-Relational explicitly deletes the keySource to 'prevent leaky abstractions'. It has a hardcoded call to unset on the attribute, in Backbone-Relational:
// Explicitly clear 'keySource', to prevent a leaky abstraction if 'keySource' differs from 'key'.
if ( this.key !== this.keySource ) {
this.instance.unset( this.keySource, { silent: true } );
}
You will need to overwrite the unset method in your Task model:
var Task = Backbone.RelationalModel.extend({
urlRoot: ' ',
relations: [
{
type: Backbone.HasOne,
key: 'creator',
relatedModel: User,
keySource: 'creator_id'
}
],
unset: function(attr, options) {
if (attr == 'creator_id') {
return false;
}
// Original unset from Backbone.Model:
(options || (options = {})).unset = true;
return this.set(attr, null, options);
},
sync: function (method, model, options) {
options.success({
id: 1,
name: 'barTask',
creator_id: 1
});
}
});
Obvious problems with this approach are that you will need to modify your code if either Backbone changes its Backbone.Model.unset method or Backbone-Relational changes its keySource behavior.