MongoDB Query many-to-many equivalent with Grails MongoDB plugin - mongodb

I have two classes mapped with MongoDB Grails PLUGIN like that:
Class Person:
class Person {
static mapWith="mongo"
String name
String email
static hasMany = [profiles: Profile]
static belongsTo = Profile
String toString(){
return name +" - " + (profiles)
} }
And Class Profile:
class Profile{
static mapWith="mongo"
String abbreviation
String description
static hasMany = [people: Person]
String toString(){
return abbreviation + " - " + description
}}
How can I make a query to return people per profile with finders provided by mongo?
A mongo query may be useful too!
This finder doesn't return anything
def people = Profile.findAllByAbbreviation("example").people
Sorry the english...

class Person {
static mapWith="mongo"
String name
String email
List<Profile> profile= new ArrayList<Profile>();
static embedded = ['profile']
}

Related

How Can I Conditionally Combine Predicates For A JPA Query?

Lets say I have a Book Entity like this:
#Entity
#Table(uniqueConstraints = {
#UniqueConstraint(columnNames = {"title"})
})
class Book {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long id;
String title;
String author;
String description;
}
and a repository like this:
#Repository
public interface BookRepository extends JpaRepository<Book, Long>, JpaSpecificationExecutor<Book> {
List<Book> findByAuthor(String author);
Book findByTitle(String title);
List<Book> findByDescriptionContaining(String description);
static Specification<Book> hasTitle(String title) {
return (book, cq, cb) -> cb.equal(book.get("title"), title);
}
static Specification<Book> hasAuthor(String author) {
return (book, cq, cb) -> cb.equal(book.get("author"), author);
}
static Specification<Book> hasDescription(String description) {
return (book, cq, cb) -> cb.like(book.get("description"), "%" + description + "%");
}
}
I can then do a query like this:
repository.findAll(where(hasAuthor("Robert Ludlum")).and(hasTitle("The Bourne Identity")).and(hasDescription("also a film"))).
If I have this in a method with parameters, an empty or null value might be passed.
e.g. a REST API search endpoint that has optional parameters.
In that case I would only want to query by author repository.findAll(where(hasAuthor("Robert Ludlum"))) since adding the other predicates would return no results.
I want to start with a base query that includes everything, then if a parameter is not null add that predicate.
If the author was empty in the above example we wouldn't have a hasAuthor to start the Specification.
How can I conditionally combine the predicates in this way?
You can build your Specification this way.
Specification<Book> spec = Specification.where(null);
if (byAuthor) {
spec = spec.and(hasAuthor("Robert Ludlum"));
}
if (byTitle) {
spec = spec.and(hasTitle("The Bourne Identity"));
}
...
repository.findAll(where(spec));

Morphia (MongoDB) Datastore "get" returns null

So I started working with Morphia and I'm encountering a weird problem.
Here's my entity class
#Entity("movies")
#Indexes(#Index(value = "Name", fields = #Field("Name")))
#Converters(LocalDateConverter.class)
public class MovieDetails implements Serializable
{
#Id
public String Id;
public String Name;
public String Description;
public String ImageName;
public LocalDate ReleaseDate;
public String Director;
public int Duration;
public String Genres;
public String Actors;
public MovieDetails()
{
}
public MovieDetails(String id, String name, String description, String imageName, String director, String actors, LocalDate releaseDate, String genres, int duration)
{
this (name, description, imageName, director, actors, releaseDate, genres, duration);
Id = id;
}
public MovieDetails(String name, String description, String imageName, String director, String actors, LocalDate releaseDate, String genres, int duration)
{
Name = name;
Description = description;
ImageName = imageName;
Director = director;
Actors = actors;
ReleaseDate = releaseDate;
Genres = genres;
Duration = duration;
}
}
Here's my little test:
final Morphia morphia = new Morphia();
// tell Morphia where to find your classes
// can be called multiple times with different packages or classes
morphia.mapPackage("nimrodpasha.cinema.objects");
// create the Datastore connecting to the default port on the local host
final Datastore datastore =
morphia.createDatastore(SingleMongoClient.getInstance().getClient(),
Constants.DB.TICKET_DATABASE);
datastore.ensureIndexes();
//region new movie
MovieDetails movie = new MovieDetails("The Mask", "Stanley Ipkiss (Jim Carrey) is a bank clerk that is an incredibly nice man. Unfortunately," +
" he is too nice for his own good and is a pushover when it comes to confrontations. After one of the worst days of his life, he finds a mask that depicts Loki, " +
"the Norse night god of mischief. Now, when he puts it on, he becomes his inner, self: a cartoon romantic wild man. However, a small time crime boss, Dorian Tyrel (Peter Greene), " +
"comes across this character dubbed The Mask by the media. After Ipkiss's alter ego indirectly kills his friend in crime," +
" Tyrel now wants this green-faced goon destroyed.",
"MASK.jpg", "Chuck Russell", "Jim Carrey as Stanley Ipkiss/The Mask,Cameron Diaz as Tina Carlyle,Amy Yasbeck as Peggy Brandt,Joely Fisher as Maggie", new LocalDate(1994, 2, 1), "Action,Comedy,CrimeAction,Family,Fantasy", 88);
//endregion
// Clearing the db first
datastore.delete(datastore.createQuery(MovieDetails.class));
// Saving a new entity and getting the result saved id
String id = (String) datastore.save(movie).getId();
// This returns as null
MovieDetails movieRetrieved = datastore.get(MovieDetails.class, id);
// This returns with one item
List<MovieDetails> allMovies = datastore.createQuery(MovieDetails.class).asList();
When I use
datastore.get(MovieDetails.class, id)
I get null
When I use:
datastore.createQuery(MovieDetails.class).asList();
I do see my movie in the DB, with the Id used in the get function.
Tried the id in many variations... toString(), ObjectId(id), Key (The value returned from the save result).
The Id in the DB (viewed with Mongo Explorer) does show as something which isn't string (blue colored), suspicious:
Mongo Explorer item picture
Any ideas?
Edit:
* the Id is indeed a string, the cast works and it was verified using watch + instanceof
Edit 2:
* Somehow the cast from ObjectId to String passed and the Id wasnt really a String.
I would change your I'd field from String to a BSON ObjectId field which MongoDB will automagically assign on save. If you then do your get call with the ObjectId as the parameter, it should work. Using ObjectId as your ID field is highly recommended with Mongo.
I am guessing that Morphia is trying to marshall an ObjectId into your String Id field and there is a small bug somewhere. I would try calling datastore.get(Example.class, new ObjectId(id)).
After Nic Cottrell (Thanks!) answer I've had a new perspective of the problem.
As my #Id field was not assigned by me it was automaticaly assigned in the DB as ObjectId.
As I still want to use String I've simply assigned the #Id field on object creation.
Id = ObjectId.get().toString();
found the solution on:
MongoDB / Morphia saves technical id as ObjectId although it's a String in Java

Strange behaviour- mongoDB GORM

I have a Grails application using Grails 2.3.8 and Mongo GORM plugin 3.0.1 . I have a service which constructs an object during its first invocations and saves it in mongoDB and returns it. In subsequent invocations, it would just retrieve the constructed object from the mongoDB and return it.
def loadWeekData(String startDate,String storeId){
def weekJson = WeekJson.findByStoreIdAndStartDate(storeId,startDate)
if(weekJson==null){
//construct weekJson here
weekJson.save(flush:true)
weekJson=WeekJson.findByStoreIdAndStartDate(storeId,startDate)
}
weekJson
}
WeekJson domain class has other nested objects with hasMany relation. WeekJson hasMany Employee which hasMany Day which hasMany Planned which hasMany Activity
WeekJson domain class
public class WeekJson{
static hasMany = [employees:Employee]
static mapWith = "mongo"
static mapping = {
employees fetch: 'join'
}
String toString()
{
"$employees"
}
}
Employees domain class
public class Employee {
static mapWith = "mongo"
static hasMany = [days:Day]
static mapping = {
days fetch: 'join'
}
String toString()
{
"$days"
}
}
Day domain class
public class Day {
Planned planned;
static mapWith = "mongo"
static constraints = {
planned nullable:true
}
String toString()
{
" plan: $planned "
}
static mapping = { planned lazy:false}
}
Planned domain class
public class Planned {
List<Activity> activities
static hasMany = [activities:Activity]
static mapWith = "mongo"
static mapping = {
activities lazy:false
}
String toString()
{ activities }
}
Activity Domain class
public class Activity {
String inTime;
String outTime;
double duration;
String type;
String desc;
static mapWith = "mongo"
static constraints = {
duration nullable:true
type nullable:true
desc nullable:true
}
String toString()
{
"$inTime to $outTime"
}
}
I have changed fetching behavior to eager in all the classes with hasMany relations.
The first time, all the nested objects are constrcuted properly, saved in mongoDB, and the returned object is correct.
However, for the next call, Activity objects are null. I've verified that the nested objects are still present in mongoDB during this call. Records in the Planned collection have ids to Activity collection records .
When I do,
println weekJson.employees.days.planned.activities
the list of `Activity is printed. However,
println weekJson
gives Activity list null and so does rendering as Json.
Why is GORM not retrieving the nested objects the second time around ?
Is it possible that this a problem of GORM being unable to handle relationships with this level of nesting ?
Maybe you should switch to sub-documents in your domain model.
Btw, if you want to help us help you, post more data on your case: which version of mongo, grails etc. you are using? what your domain classes look like? what do you see in the mongo collections upon saving?

Structuring REST URI's with Jersey

New to Jersey(REST Framework for Java) and I'm trying to setup two resources, in two separate classes that share a root path, and I'm having a problem.
So, I have something like:
#Path("/users")
public class User extends RestSupport {
#GET
#Path("/{user_uuid}")
public String get(#PathParam("user_uuid") String uuid) {
return "Hello User " + uuid;
}
}
The above class works. However, I want to create a child resource in a separate class. But when I do this, it seems to create a URI naming conflict. So, here, I want to get all the pets for a particular users
#Path("/users")
public class Pets extends RestSupport {
#GET
#Path("/{user_uuid}/pets")
public String get(#PathParam("user_uuid") String uuid) {
return "Hello Pets " + uuid;
}
}
These top-level resources have lots of child resources, so I'm looking for the best way to organize them. Any help would be appreciated.
Change the path of Pets class from #Path("/users")to#Path("/users/{user_uuid}/pets")
Don't add the HTTP annotation #GET on your Users root resource method if you want Jersey to delegate calls to a child resource. Consider a User class:
public class User {
String uuid;
User(String id) { this.uuid = id; }
#GET
public String get() { return "Hello user " + uuid; }
#GET
#Path("/pets")
public String getPets() { return "Hello pets " + uuid; }
}
and then adjust your Users resource:
#Path("/users")
public class Users {
#Path("/{user_uuid}")
public User get(#PathParam("user_uuid") String uuid) {
// Get the user from the DAO here...
return new User(uuid);
}
}

grails: dynamically adding associations

Mormally setting up one-to-many associations is easy. Take for example:
class Author {
String firstName
String lastName
static hasMany = [books: Book]
static constraints = {
books(nullable: true)
}
}
class Book {
String title
Author author
Publisher publisher
static constraints = {
author(nullable: true)
publisher(nullable: true)
}
}
However, if I've already setup the Author domain without knowing Book at all, there is no static hasMany = [books: Book] specified initially. Later, I want to add a Book domain and want to add static hasMany = [books: Book] to Author. Could I do this with a plugin? If so, how?
Thanks.
If you don't want to update the Author class, you could create your own association class.
class AuthorsToBooks {
Author author
static belongsTo = [Book: book]
}