How to save Father and Child document with MongooseJS - mongodb

I'm quite new in MongoDB & MongooseJS. Actually learning the MEAN stack, I already love it.
I'm not sure to exactly understand noSQL principles, sorry If I duplicate.
I want to join 2 documents, type father and child, with 1 to N relationship.
Here's two solutions:
• Sub documents: http://mongoosejs.com/docs/subdocs.html
• References: http://mongoosejs.com/docs/populate.html
I've chosen the first one, simpler and IHMO closer to my problem.
Here's the code:
var personSchema = new Schema({
name : String
});
var groupSchema = new Schema({
name : String,
persons : [ personSchema ]
});
var Group = module.exports = mongoose.model('Group', groupSchema);
var Person = module.exports = mongoose.model('Person', personSchema);
Group.findById(groupId, function(err, group) {
var person = new Person();
person.name = "john";
group.persons.push(person);
group.save();
});
When I check for all groups, it work perfectly. The groups are returned, with the people saved.
Group.find(function(err, group) {
// group is full of groups & people
}
But when I check for person, nothing is returned.
Person.find(function(err, person) {
// person is empty
}
It seems that only the Group table document was filled. Is the solution implies to save 1/ the person and 2/ the group, and if so, will Person be saved in two different places (in Group document and in Person document)?
Group.findById(groupId, function(err, group) {
var person = new Person();
person.name = "john";
group.persons.push(person);
person.save(function(...) {
group.save();
});
});
Thanks

Related

How to update a ManyToMany relationship in Objection JS?

I am learning Objection JS. I have a manyToMany relationship on an accounts and roles table that are related through an accounts_roles table. I was wondering if there was a way to update account roles on the account model by doing something like:
AccountOne.addRoles([roleId])
AccountOne.removeRoles([roleId])
AccountOne.updateRoles([roleId])
AccountOne.deleteRoles([roleId])
I searched online and went through the official objection documentation. So far I can do a GraphInsert, on my account model with a role object "x", and that creates a new role "x" with a relationship correctly defined in accounts_roles. But that always creates a new role. I would like to add and remove relationships between existing accounts and roles. Any help would be much appreciated.
You can use relate and unrelate to connect two rows together in a many-to-many relationship. Obviously, for this to work, you have to have your Models' relationMappings set up correctly. I have put a pair of example models too. It's critical that you match your key of your relationMappings to what you put in your $relatedQuery method to call relate/unrelate on.
Example Models
//Person and Movie Models
const { Model } = require('objection');
class Person extends Model {
static tableName = 'persons';
static get relationMappings() {
return {
movies: {
relation: Model.ManyToManyRelation,
modelClass: Movie,
join: {
from: 'persons.id',
through: {
from: 'persons_movies.person_id',
to: 'persons_movies.movie_id'
},
to: 'movies.id'
}
}
};
}
}
class Movie extends Model {
static tableName = 'movies';
static get relationMappings() {
return {
persons: {
relation: Model.ManyToManyRelation,
modelClass: Person,
join: {
from: 'movies.id',
through: {
from: 'persons_movies.movie_id',
to: 'persons_movies.person_id'
},
to: 'persons.id'
}
}
};
}
}
Relate Example
In the case of a many-to-many relation, creates a join row to the join table.
const person = await Person
.query()
.findById(123);
const numRelatedRows = await person
.$relatedQuery('movies')
.relate(50);
// movie with ID 50 is now related to person with ID 123
// this happened by creating a new row in the linking table for Movies <--> Person
Unrelate Example
For ManyToMany relations this deletes the join row from the join table.
const person = await Person
.query()
.findById(123)
const numUnrelatedRows = await person
.$relatedQuery('movies')
.unrelate()
.where('id', 50);
// movie with ID 50 is now unrelated to person with ID 123
// this happened by delete an existing row in the linking table for Movies <--> Person

new Model() that has fields set as select:false are not available

This is my Schema:
var userScheme = mongoose.Schema({
aField:String,
info: {
type:{
local: {
email:String
}
},
select:false
}
});
When I try to create a new user this works fine:
var newUser = new User()
newUser.aField="Something"
newUser.save()
But when I try to access the field that has select:false, I can't access the data. so this doesn't work:
var newUser = new User()
newUser.aField="something"
newUser.info.local.email="email#domain.com"
newUser.save()
The error I get is:
TypeError: Cannot read property 'local' of undefined
My guess is that the new Model is returned without the info field becuase it is set to select:false.
How can I make the new Model() return all the fields including those set to 'select:false'?
Thanks!
Turns out the select:false had nothing to do with it.
The culprit was the fact that info has no values by default and there for was not included in the model at all.
The solution was to create a new schema just for info, and to include it in the user schema like this:
var userScheme = mongoose.Schema({
aField:String,
info: {
type:infoSchema,
default:infoSchema, //Without this, the new document will still not include an 'info' document,
select:false
}
});
Hope this helps anyone. This was just 3 hours of my life.

Users loop in Meteor Js

For example I have 50 users and I have collection like
Rooms = new Mongo.Collection('rooms');
First I want to mix Users like if I have this [id1,id2,id3...] make it this [id52,id91241,id2...]
and after put in every Room 5 users like
for (i=0;i<countofmyusers;i=i+5)
crete new room and put 5 users // ?? how .. Rooms.insert(??)
{
users: [id1,id44,id2451,id921241,id23]
...
}
Any idea how to do it ?
Here's an example function that creates a set of rooms, each with a random sample of users:
var randomRooms = function(roomCount, sampleSize) {
// extract all of the user ids in the datbase
var userIds = _.pluck(Meteor.users.find({}, {fields: {_id: 1}}).fetch(), '_id');
// create roomCount rooms
_.times(roomCount, function() {
// insert a new room with a random sample of users of size sampleSize
Rooms.insert({users: _.sample(userIds, sampleSize)});
});
};
Here's a new version which enforces that user ids not be repeated across groups (i.e. each user will be assigned to one and only one group):
var randomRooms = function(userCountInEachRoom) {
// extract all of the user ids in the datbase
var userIds = _.pluck(Meteor.users.find({}, {fields: {_id: 1}}).fetch(), '_id');
// create a new array of randomly sorted user ids
var shuffledUserIds = _.shuffle(userIds);
// create a list of lists of user ids where each list has at most
// userCountInEachRoom ids - note that users will not be repeated in any lists
var userLists = [];
while (shuffledUserIds.length > 0)
userLists.push(shuffledUserIds.splice(0, userCountInEachRoom));
// insert a new group for each sub-array of user ids
_.each(userLists, function(users) {
Rooms.insert({users: users});
});
};
You'd call it like randomRooms(5) to place at most five users in each group. Note that the last group will have fewer than five users if the total user count is not a multiple of five.

How to implement search with multiple filters using lucene.net

I'm new to lucene.net. I want to implement search functionality on a client database. I have the following scenario:
Users will search for clients based on the currently selected city.
If the user wants to search for clients in another city, then he has to change the city and perform the search again.
To refine the search results we need to provide filters on Areas (multiple), Pincode, etc. In other words, I need the equivalent lucene queries to the following sql queries:
SELECT * FROM CLIENTS
WHERE CITY = N'City1'
AND (Area like N'%area1%' OR Area like N'%area2%')
SELECT * FROM CILENTS
WHERE CITY IN ('MUMBAI', 'DELHI')
AND CLIENTTYPE IN ('GOLD', 'SILVER')
Below is the code I've implemented to provide search with city as a filter:
private static IEnumerable<ClientSearchIndexItemDto> _search(string searchQuery, string city, string searchField = "")
{
// validation
if (string.IsNullOrEmpty(searchQuery.Replace("*", "").Replace("?", "")))
return new List<ClientSearchIndexItemDto>();
// set up Lucene searcher
using (var searcher = new IndexSearcher(_directory, false))
{
var hits_limit = 1000;
var analyzer = new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_30);
// search by single field
if (!string.IsNullOrEmpty(searchField))
{
var parser = new QueryParser(Lucene.Net.Util.Version.LUCENE_30, searchField, analyzer);
var query = parseQuery(searchQuery, parser);
var hits = searcher.Search(query, hits_limit).ScoreDocs;
var results = _mapLuceneToDataList(hits, searcher);
analyzer.Close();
searcher.Dispose();
return results;
}
else // search by multiple fields (ordered by RELEVANCE)
{
var parser = new MultiFieldQueryParser(Lucene.Net.Util.Version.LUCENE_30, new[]
{
"ClientId",
"ClientName",
"ClientTypeNames",
"CountryName",
"StateName",
"DistrictName",
"City",
"Area",
"Street",
"Pincode",
"ContactNumber",
"DateModified"
}, analyzer);
var query = parseQuery(searchQuery, parser);
var f = new FieldCacheTermsFilter("City",new[] { city });
var hits = searcher.Search(query, f, hits_limit, Sort.RELEVANCE).ScoreDocs;
var results = _mapLuceneToDataList(hits, searcher);
analyzer.Close();
searcher.Dispose();
return results;
}
}
}
Now I have to provide more filters on Area, Pincode, etc. in which Area is multiple. I tried BooleanQuery like below:
var cityFilter = new TermQuery(new Term("City", city));
var areasFilter = new FieldCacheTermsFilter("Area",areas); -- where type of areas is string[]
BooleanQuery filterQuery = new BooleanQuery();
filterQuery.Add(cityFilter, Occur.MUST);
filterQuery.Add(areasFilter, Occur.MUST); -- here filterQuery.Add not have an overloaded method which accepts string[]
If we perform the same operation with single area then it's working fine.
I've tried with ChainedFilter like below, which doesn't seems to satisfy the requirement. The below code performs or operation on city and areas. But the requirement is to perform OR operation between the areas provided in the given city.
var f = new ChainedFilter(new Filter[] { cityFilter, areasFilter });
Can anybody suggest to me how to achieve this in lucene.net? Your help will be appreciated.
You're looking for the BooleanFilter. Almost any query object has a matching filter object.
Look into TermsFilter (from Lucene.Net.Contrib.Queries) if your indexing doesn't match the requirements of FieldCacheTermsFilter. From the documentation of the later; "this filter requires that the field contains only a single term for all documents".
var cityFilter = new FieldCacheTermsFilter("CITY", new[] {"MUMBAI", "DELHI"});
var clientTypeFilter = new FieldCacheTermsFilter("CLIENTTYPE", new [] { "GOLD", "SILVER" });
var areaFilter = new TermsFilter();
areaFilter.AddTerm(new Term("Area", "area1"));
areaFilter.AddTerm(new Term("Area", "area2"));
var filter = new BooleanFilter();
filter.Add(new FilterClause(cityFilter, Occur.MUST));
filter.Add(new FilterClause(clientTypeFilter, Occur.MUST));
filter.Add(new FilterClause(areaFilter, Occur.MUST));
IndexSearcher searcher = null; // TODO.
Query query = null; // TODO.
Int32 hits_limit = 0; // TODO.
var hits = searcher.Search(query, filter, hits_limit, Sort.RELEVANCE).ScoreDocs;
What you are looking for is nested boolean queries so that you have an or (on your cities) but that whole group (matching the or) is itself matched as an and
filter1 AND filter2 AND filter3 AND (filtercity1 OR filtercity2 OR filtercity3)
There is already a good description of how to do this here:
How to create nested boolean query with lucene API (a AND (b OR c))?

EF 6 - many-to-many - Join table without duplicates

I'm using EF6 have some confusion on seeding a many to many relationship.
I have the following:
A User has many saved ChartQueries (that they can execute to get a chart).
A ChartQuery typically belongs to only one user, but there are several "shared" ChartQuerys that every User can execute. As a result I set up a many to many relationship using a join table UserChartQuery. The tables are up in the database just fine at 1-to-many on each side of the join table.
However, I'm not quite understanding how to seed or use this relationship. I don't want to end up with several duplicates of the "shared" ChartQuerys (a duplicate for each User). Instead, there should only be a single row for each "shared" ChartQuery that is a part of each User's SavedChartQueries collection (along with other, non-shared ChartQuerys that belong to that User only).
It seems like I'm forced to duplicate for each user:
var sharedChartQuery = new ChartQuery { ... };
var nonSharedChartQuery = new ChartQuery { ... };
var userOneChartQueryOne = new UserChartQuery { User = userOne, ChartQuery = sharedChartQuery };
var userTwoChartQueryOne = new UserChartQuery { User = userTwo, ChartQuery = sharedChartQuery };
var userTwoChartQueryTwo = new UserChartQuery { User = userTwo, ChartQuery = nonSharedChartQuery };
context.UserChartQueries.Add(userOneChartQueryOne);
context.UserChartQueries.Add(userOneChartQueryTwo);
context.UserChartQueries.Add(userTwoChartQueryTwo);
So first of all is this the right way to seed (through UserChartQueries table directly) or should I seed each User's SavedChartQueries navigation property?
And will this result in duplicate sharedChartQuery in the join table for each User? If so is there any way to avoid this?
Ok I understand how this works now. The following works as expected:
var userOne = new User {};
var userTwo = new User {};
var chartQuery = new ChartQuery { };
context.Users.Add(userOne);
context.Users.Add(userTwo);
context.UserChartQueries.Add(new UserChartQuery { User = userOne, ChartQuery = chartQuery });
context.UserChartQueries.Add(new UserChartQuery { User = userTwo, ChartQuery = chartQuery });
context.ChartQueries.Add(chartQuery);
The last line adds it to the table where the record actually resides. Checking the join table in SSMS shows that it only holds the foreign keys and nothing else. There are no duplicates.