I'm designing an API and also consuming it with Backbone.js. Part of the API will include search operations. For example when searching for cars, I might have something like:
http://api.mysite.com/search/cars?q=volvo
With backbone, I can see two options for consuming the results.
Option 1: A search is a Collection
var CarSearch = Backbone.Collection.extend({
model: Car,
initialize : function(models, options){
this.query = options.query;
},
url: function(){
return "http://api.mysite.com/search/cars?q="+this.query;
}
});
var volvos = new CarSearch([], {query:'volvo'});
volvos.fetch();
Option 2: A search is a Model, and the results are a Collection
var CarSearchResults = Backbone.Collection.extend({
model: Car
});
var CarSearch = Backbone.Model.extend({
defaults: {
"query":"",
"carSearchResults":null
},
url: function(){
return "http://api.mysite.com/search/cars?q="+this.get('query');
},
parse: function(resp,xhr){
resp.carSearchResults = new CarSearchResults(resp.carSearchResults);
return resp;
}
});
var volvoSearch = new CarSearch();
volvoSearch.set({query:'volvo'});
volvoSearch.save();
What are the advantages / disadvantages of these options? Is there a backbone-y way of designing this?
I'm leaning towards option 2 because it seems easier to add things to the response like pagination details, or a next url. But option 2 seems messier in a couple of ways. For example, would I generate an ID on the server for the search model when it is saved? Don't think I need to get that model by ID, deleting or updating it doesn't really make sense either cause I'm not persisting it.
i dont know if its a good practice,
but i use for my search the "data" option in the "fetch" method.
https://stackoverflow.com/a/6659501/1067061
Maybe it helps.
Good Luck!
EDIT
This is the right way to pass query parameters in your collections url,
The reference to the Docs shows how to pass the data attribute in fetch options, the data attribute is actually an object with key value pairs referring to query params and their values
I would go with option one. At least imo a model should correspond to a single search result and the collection to the entire set of search results. so if you search for volvo and there are 6 items returned, each item should be a model contained within your collection.
Now this will largely depend on how you are representing a result on your server. If say for instance you have car instances then you just do the search server side using the query and return the resulting objects as json. Then you can have the returned list be the collection of car models that match the criteria. but if you are planning on returning the query results some other way then you will have to think about how the model should represent the data
I would recommend using a collection, like in option 1, but without the need to define a new collection just for the search.
Take a look at my blog post about this here: http://willdemaine.ghost.io/restful-search-with-backbone/
var SearchableCollection = Backbone.Collection.extend({},{
search: function(query, options){
var search = $.Deferred();
options = options || {};
var collection = new this([], options);
collection.url = _.result(collection, 'url') + 'search?q=' + query;
var fetch = collection.fetch();
fetch.done(_.bind(function(){
Backbone.Events.trigger('search:done');
search.resolveWith(this, [collection]);
}, this));
fetch.fail(function(){
Backbone.Events.trigger('search:fail');
search.reject();
});
return search.promise();
}
});
Then you can do:
var Cars = SearchableCollection.extend({});
var findCars = Cars.search('volvo');
findCars.done(function(cars){
var carsView = new CarsView({
collection: cars
});
carsView.render();
});
Related
How do you go about formatting the data response from mongoose? For a simple Post Schema
const postSchema = new mongoose.Schema({
title: {
type: String,
required: true,
}
},{
timestamps: true
});
Whenever I do a GET request to find all the post, It returns me all its fields including _id and __v in which I wouldn't want to return those fields in an API.
Is there a way I would select only certain fields that I would want to return?
As far as I've found was that I could set a second parameter of title onto my query and it would return only the _id and title.
const post = await Post.find({},'title');
I find the method above isn't the proper way to filter fields in cases in the future where the values are deeply nested object and we would like to pick out certain values.
Is there perhaps a way to create a Model/Class and pick the fields based on the Model/Class and return the respond?
You can use select from mongoose.
You can either select only the fields you want.
var find = await model.find({}).select("my_field")
Or not show the fields you don't want
var find = await model.find({}).select("-my_field")
Check the documentation
Given the data structure below in firebase, i want to run a query to retrieve the blog 'efg'. I don't know the user id at this point.
{Users :
"1234567": {
name: 'Bob',
blogs: {
'abc':{..},
'zyx':{..}
}
},
"7654321": {
name: 'Frank',
blogs: {
'efg':{..},
'hij':{..}
}
}
}
The Firebase API only allows you to filter children one level deep (or with a known path) with its orderByChild and equalTo methods.
So without modifying/expanding your current data structure that just leaves the option to retrieve all data and filter it client-side:
var ref = firebase.database().ref('Users');
ref.once('value', function(snapshot) {
snapshot.forEach(function(userSnapshot) {
var blogs = userSnapshot.val().blogs;
var daBlog = blogs['efg'];
});
});
This is of course highly inefficient and won't scale when you have a non-trivial number of users/blogs.
So the common solution to that is to a so-called index to your tree that maps the key that you are looking for to the path where it resides:
{Blogs:
"abc": "1234567",
"zyx": "1234567",
"efg": "7654321",
"hij": "7654321"
}
Then you can quickly access the blog using:
var ref = firebase.database().ref();
ref.child('Blogs/efg').once('value', function(snapshot) {
var user = snapshot.val();
ref.child('Blogs/'+user+'/blogs').once('value', function(blogSnapshot) {
var daBlog = blogSnapshot.val();
});
});
You might also want to reconsider if you can restructure your data to better fit your use-case and Firebase's limitations. They have some good documentation on structuring your data, but the most important one for people new to NoSQL/hierarchical databases seems to be "avoid building nests".
Also see my answer on Firebase query if child of child contains a value for a good example. I'd also recommend reading about many-to-many relationships in Firebase, and this article on general NoSQL data modeling.
Given your current data structure you can retrieve the User that contains the blog post you are looking for.
const db = firebase.database()
const usersRef = db.ref('users')
const query = usersRef.orderByChild('blogs/efg').limitToLast(1)
query.once('value').then((ss) => {
console.log(ss.val()) //=> { '7654321': { blogs: {...}}}
})
You need to use limitToLast since Objects are sorted last when using orderByChild docs.
It's actually super easy - just use foreslash:
db.ref('Users').child("userid/name")
db.ref('Users').child("userid/blogs")
db.ref('Users').child("userid/blogs/abc")
No need of loops or anything more.
I'm using the request library to make calls from one sails app to another one which exposes the default blueprint endpoints. It works fine when I query by non-id fields, but I need to run some queries by passing id arrays. The problem is that the moment you provide an id, only the first id is considered, effectively not allowing this kind of query.
Is there a way to get around this? I could switch over to another attribute if all else fails but I need to know if there is a proper way around this.
Here's how I'm querying:
var idArr = [];//array of ids
var queryParams = { id: idArr };
var options: {
//headers, method and url here
json: queryParams
};
request(options, function(err, response, body){
if (err) return next(err);
return next(null, body);
});
Thanks in advance.
Sails blueprint APIs allow you to use the same waterline query langauge that you would otherwise use in code.
You can directly pass the array of id's in the get call to receive the objects as follows
GET /city?where={"id":[1, 2]}
Refer here for more.
Have fun!
Alright, I switched to a hacky solution to get moving.
For all models that needed querying by id arrays, I added a secondary attribute to the model. Let's call it code. Then, in afterCreate(), I updated code and set it equal to the id. This incurs an additional database call, but it's fine since it's called just once - when the object is created.
Here's the code.
module.exports = {
attributes: {
code: {
type: 'string'//the secondary attribute
},
// other attributes
},
afterCreate: function (newObj, next) {
Model.update({ id: newObj.id }, { code: newObj.id }, next);
}
}
Note that newObj isn't a Model object as even I was led to believe. So we cannot simply update its code and call newObj.save().
After this, in the queries having id arrays, substituting id with code makes them work as expected!
// in server.js
Meteor.publish("directory", function () {
return Meteor.users.find({}, {fields: {emails: 1, profile: 1}});
});
// in client.js
Meteor.subscribe("directory");
I want to now get the directory listings queried from the client like directory.findOne() from the browser's console. //Testing purposes
Doing directory=Meteor.subscribe('directory')/directory=Meteor.Collection('directory') and performing directory.findOne() doesn't work but when I do directory=new Meteor.Collection('directory') it works and returns undefined and I bet it CREATES a mongo collection on the server which I don't like because USER collection already exists and it points to a new Collection rather than the USER collection.
NOTE: I don't wanna mess with how Meteor.users collection handles its function... I just want to retrieve some specific data from it using a different handle that will only return the specified fields and not to override its default function...
Ex:
Meteor.users.findOne() // will return the currentLoggedIn users data
directory.findOne() // will return different fields taken from Meteor.users collection.
If you want this setup to work, you need to do the following:
Meteor.publish('thisNameDoesNotMatter', function () {
var self = this;
var handle = Meteor.users.find({}, {
fields: {emails: 1, profile: 1}
}).observeChanges({
added: function (id, fields) {
self.added('thisNameMatters', id, fields);
},
changed: function (id, fields) {
self.changed('thisNameMatters', id, fields);
},
removed: function (id) {
self.removed('thisNameMatters', id);
}
});
self.ready();
self.onStop(function () {
handle.stop();
});
});
No on the client side you need to define a client-side-only collection:
directories = new Meteor.Collection('thisNameMatters');
and subscribe to the corresponding data set:
Meteor.subscribe('thisNameDoesNotMatter');
This should work now. Let me know if you think this explanation is not clear enough.
EDIT
Here, the self.added/changed/removed methods act more or less as an event dispatcher. Briefly speaking they give instructions to every client who called
Meteor.subscribe('thisNameDoesNotMatter');
about the updates that should be applied on the client's collection named thisNameMatters assuming that this collection exists. The name - passed as the first parameter - can be chosen almost arbitrarily, but if there's no corresponding collection on the client side all the updates will be ignored. Note that this collection can be client-side-only, so it does not necessarily have to correspond to a "real" collection in your database.
Returning a cursor from your publish method it's only a shortcut for the above code, with the only difference that the name of an actual collection is used instead of our theNameMatters. This mechanism actually allows you to create as many "mirrors" of your datasets as you wish. In some situations this might be quite useful. The only problem is that these "collections" will be read-only (which totally make sense BTW) because if they're not defined on the server the corresponding `insert/update/remove' methods do not exist.
The collection is called Meteor.users and there is no need to declare a new one on neither the server nor the client.
Your publish/subscribe code is correct:
// in server.js
Meteor.publish("directory", function () {
return Meteor.users.find({}, {fields: {emails: 1, profile: 1}});
});
// in client.js
Meteor.subscribe("directory");
To access documents in the users collection that have been published by the server you need to do something like this:
var usersArray = Meteor.users.find().fetch();
or
var oneUser = Meteor.users.findOne();
I'm trying to use as much of the OOTB sync and RESTful functionality in Backbone. I have a Web API set up for basic CRUD for my models. I have:
var SearchModel = Backbone.Model.extend({});
var SearchMappingModel = Backbone.Model.extend({});
var SearchComponentModel = Backbone.Model.extend({});
var SearchCollection = Backbone.Collection.extend({});
var SearchMappingCollection = Backbone.Collection.extend({});
var SearchComponentCollection = Backbone.Collection.extend({});
For every Search there is 1-to-many SearchMappings, and for every SearchMapping, there are 1-to-many SearchComponents. My URLs for sync would be something like, "/search" for the Search collection, "'/searchmapping/' + searchId" for the SearchMapping collection, and "'/searchcomponent/' + mappingId" for the SearchComponent collection.
My question is, since each collection is dependent on the previous one, is there a way I can make a cascading relationship in backbone to minimize my code and use as much of the basic sync functionality that's already there?
My initial thought is to create a collection within a collection and write my own .fetch() to first fetch the parent collection and on its success then fetch the child, which will then also get its child after its own success, like this:
var SearchCollection = Backbone.Collection.extend({
model: SearchModel,
initialize: function (data) {
this.url = baseURL + "/search";
this.data = data;
this.SearchMappingCollection = new SearchMappingCollection();
},
fetchData: function () {
this.fetch({
success: _.bind(function (results) {
this.fetchListSuccess(results);
}, this)
});
},
fetchListSuccess: function (results) {
this.SearchMappingCollection.fetchData(results);
}
The same would be done on a .save(). This may be a good way of doing it, but wanted to get feedback from anyone else that's done something similar.
I ended up not using a cascading format. It seems that it was adding more complexity and giving nothing in return. All 3 collections now sit on the controller level, and I just load the next collection after each collection is loaded on each "reset" event.