Grails 3 & relational domain mapping with Mongo - mongodb

Trying to figure out if there is a way that I can relate these two domain objects in a similar way that I would if I were connected to an Oracle db.
gradle.properties
grailsVersion=3.2.9
gradleWrapperVersion=2.9
gormVersion=6.1.3.RELEASE
build.gradle
compile "org.grails.plugins:mongodb:6.1.3"
compile "org.mongodb:mongodb-driver:3.4.2"
Domain objects:
class Store {
Long id
// other properties
Long sellerId
}
class Seller {
Long id
// other properties
}
I thought to do something like this:
class Store {
Long id
// other properties
Long sellerId
Seller seller
Seller getSeller {
Seller.findById(this.sellerId)
}
}
In the case above, only sellerId is persisted to Mongo since it is not marked as embedded. This works great if I reference it in grails code - giving me valid values for all of the properties in store.seller. However, if I return a store from a controller, store.seller does not come through fully. The response JSON for store looks like this (notice how seller ONLY has the id property):
{
id: 1,
// other properties
seller: {
id: 22
}
}
I have also tried something like this but afterLoad never gets hit:
class Store {
Long id
// other properties
Long sellerId
Seller seller
def afterLoad() {
seller = Seller.findById(this.sellerId)
}
}
Is there a better way to go about doing this?

Related

GORM - get raw DB value for domain class properties

I'm using GORM for MongoDB in my Grails 3 web-app to manage read/writes from DB.
I have the following 2 domain classes:
class Company {
String id
}
class Team {
String id
Company company
}
For teams, their company is saved on DB as String, and with GORM I can simply use team.company to get an instance of Company domain class.
However, I need to override the getter for company, and I need the raw value for company id (as stored on DB), without GORM getting in the way and performing its magic.
Is there a way to get the raw String value?
Any help is welcome! Thanks in advance
Update (May 27)
Investigating #TaiwaneseDavidCheng suggestion, I updated my code to
class Company {
String id
}
class Team {
String id
Company company
String companyId
static mapping = {
company attr: "company" // optional
companyId attr: "company", insertable: false, updateable: false
}
}
Please note that I'm using GORM for MongoDB, which (citing the manual) tries to be as compatible as possible with GORM for Hibernate, but requires a slightly different implementation.
However I found out (by trial&error) that GORM for MongoDB doesn't support a similar solution, as it seems only one property at a time can be mapped to a MongoDB document property.
In particular the last property in alphabetical order wins, e.g. companyId in my example.
I figured out a way to make the whole thing work, I'm posting my own answer below.
given a non-insertable non-updateable column "companyId" in domain class
class Company {
String id
}
class Team {
String id
Company company
Long companyId
static mapping = {
company column:"companyId"
companyId column:"companyId",insertable: false,updateable: false
}
}
(Follows the edit to my question above)
I defined a custom mapping, and made use of Grails transients by also defining custom getter and setter for team's company.
class Company {
String id
}
class Team {
String id
Company company
String companyId
static mapping = {
companyId attr: "company" // match against MongoDB property
}
static transients = [ 'company' ] // non-persistent property
Company getCompany() {
return Company.get(companyId)
}
void setCompany(Company company) {
companyId = company.id
}
}

Saving Extra Data With GoLang

So I am using Go Lang 1.10.3 with the Echo framework with Gorm and Postgres as my database.
I have the three tables / structs , Profile, Addresses and Profile_addresses
The structs are as follows,
Profile
type Profile struct {
gorm.Model
Company string `gorm:"size:255" json:"company"`
AddedDate time.Time `gorm:"type:date" json:"date_added"`
ProfileAddresses []ProfileAddress `gorm:"foreignkey:profile_fk" json:"address_id"`
}
Address
type Address struct {
gorm.Model
AddressLine1 string `gorm:"size:255" json:"line1"`
AddressLine2 string `gorm:"size:255" json:"line2"`
AddressLine3 string `gorm:"size:255" json:"line3"`
City string `gorm:"size:200" json:"city"`
Country string `gorm:"size:255" json:"country"`
Postcode string `gorm:"size:12" json:"postcode"`
ProfileAddresses []ProfileAddress `gorm:"foreignkey:address_fk"`
}
And Profile Address
type ProfileAddress struct {
gorm.Model
Archive bool `json:"archive"`
Profile Profile
Address Address
AddressFK int`gorm:"ForeignKey:id"`
ProfileFK int`gorm:"ForeignKey:id"`
}
These tables all get made fine but I am now trying to save an address ID to the Profile Address table when making a new Profile. Posting data to /profile/add (with Postman) including the following data
{
"company": "testing-000001",
"date_added": "3051-11-09T00:00:00+00:00",
"address_id": 3
}
Now I can save a new profile and a new address but not save this data. I have only just added the json:"address_id"` option to the end of the Profile struct but that did not work.
I have set this up like so, as one profile might have many address so there needs to be a linked table with all the address ids. I sure that I could do this in a two stage step, e.g. save the profile, then save the addresses I want to add to that profile but I would like to get this to work. I also dont need to create a new address, these would have already been added into the system.
So what am I doing wrong?
Any help most welcome.
Thanks,
This is how I got it to work, please let me know if this is not correct or if there is a better way.
So I added the Profile struct to a wrapper/nested struct, like so:
type ProfileWrapper struct {
gorm.Model
ProfileData Profile
AddressID int `json:"address_id"`
}
So I then changed/updated the json data I was sending into this struct to this:
{
"ProfileData": {
"company": "testing-with-add-0001",
"date_added": "9067-11-09T00:00:00+00:00"
},
"address_id": 3
}
So to saved the profile I did this,
db.Create( &p.ProfileData )
I then built a new struct for the Profile Address information to be created/added, like so:
pd := structs.ProfileAddress{AddressFK: p.AddressID, ProfileFK: profileID}
db.Create( &pd )
Not sure if this is the best way of doing this, but it seems to work. If this is wrong please let me know.
Thanks.

Grails MongoDB Update object with Association

I can't seem to understand where I am going wrong currently when I attempt to update my User domain model that has a hasOne association to a Profile object.
My domain models are as follows:
class User {
static hasOne = [profile: Profile]
static fetchMode = [profile: 'eager']
ObjectId id
String username
}
class Profile {
static belongsTo = [user: User]
ObjectId id
String familyName
String givenName
}
I am able to persist a User with a profile originally but when attempting to update the User object I get validation errors.
Validation error occurred during call to save():
- Field error in object 'co.suitable.User' on field 'profile.familyName': rejected value [null]
- Field error in object 'co.suitable.User' on field 'profile.givenName': rejected value [null]
I am able to print out the user.profile ID and also the user.profile.familyName before saving the object. Like the following:
println(user.profile.familyName)
println(user.profile.id.toString())
user.save(flush: true, failOnError: true)
But I still get the validation errors before saving, i'd imagine that the println(user.profile.familyName) call is fetching the profile object if it already hasn't been loaded which I thought setting the fetchMode would have handled.
The object is able to successfully persist and save when I do:
user.profile = Profile.findById(user.profile.id)
println(user.profile.id.toString())
user.save(flush: true, failOnError: true)
I could wrap that in a service but I was hoping for a solution that would be handled by Grails if possible. Any advice or thoughts is much appreciated.
You should not apply the logic for the SQL DB to Mongo 1 to 1. Mongo and other document-oriented DBs are not originally intended to store the joins between collections. There are some workarounds, like db-refs, but they are to be used with caution.
For your case - with hasOne - I would suggest using mongo's subdocuments (mirrored as GORM's embedded objects) instead of referencing:
class User {
ObjectId id
String username
Profile profile
static embedded = [ 'profile' ]
}
class Profile {
String familyName
String givenName
}
thus you use the mongo in accordance to it's original puprose. Also querying is simpler and faster.

EJB inheritance strategy

BIG UPDATE:
Ok, I see my problem is much more complicated than I thought. I have tables like this:
Patient:
id
bloodtype
level (FK to level table)
doctor (FK to doctor table)
person (FK to person table)
Doctor:
id
person (FK)
speciality
level (FK to level)
Paramedic:
id
person (FK)
Person:
id
name
surname
Account:
id
login
pass
Level:
id
account (FK)
name (one of: 'patient' , 'paramedic' , 'doctor')
In entity class I'm using now #Inheritance(strategy= InheritanceType.JOINED)
#DiscriminatorColumn(discriminatorType=DiscriminatorType.STRING,name="name") in class Level. To check if someone is for ex. patient I have function:
public boolean ifPatient(String login) {
account = accountfacade.getByLogin(login);
for (Level l : account.getLevelCollection()) {
if (l instanceof Patient) {
return true;
}
}
return false;
}
Now I have situation: I'm logged in as a doctor. I want to add a patient. I have something like this:
public Doctor findDoctor(String login) {
account = accountfacade.getByLogin(login);
for (Doctor d : doctorfacade.findAll()) {
if (d.getLevel().getAccount().getLogin().equals(login)) {
doctor = d;
return doctor;
}
}
}
#Override
public void addPatient(Account acc, Patient pat, Person per, String login) {
Doctor d = findDoctor(login);
accountfacade.create(acc);
personfacade.create(per);
Patient p = new Patient();
p.setAccount(acc);
p.setBlood(pat.getBlood());
// etc
p.setPerson(per);
d.getPatientCollection().add(p);
acc.getLevelCollection().add(p);
}
But it doesn't work. Always totally weird errors like duplicate value of primary key in table Account (but I use TableGenerator...) or NULL value in field Account but for INSERT INTO Doctor (how?! I'm creating new Patient, NOT Doctor...). I'm totally lost now, so I think most important for me now is to know if actually I can use InheritanceType.JOINED in this case.
UPDATE:
You are using the field nazwa as discriminator
#DiscriminatorColumn(discriminatorType=DiscriminatorType.STRING,name="nazwa")
The framework stores there the name of the class that it has to use to deserialize the object (it is the name of the PoziomDostepu class).
As far as I can see you are using a different table for each class, so the Strategy.JOINED would make little sense.
More update:
Where I said class it meant entity. You can check the effect by changing the entity name (say to "CacaCuloPedoPis") of PoziomDostepu and seeing which is the new value being inserted.
Looks like all that is missing is a #DiscriminatorValue annotation on the PoziomDostepu class to use one of the values in your constraint, otherwise it defaults to the class name. The insert of PoziomDostepu is coming from the this.poziom - if you don't want PoziomDostepu instances inserted you might make the class abstract and make sure this instance is a subclass instead. You shouldn't need to change the inheritance type, as doing so will require changing the database tables to each contain the same fields.
Ok, I've done what I wanted. It's not elegant, but efficient. Tables and inheritance strategy are the same. In endpoint I create account and person entities, then I create Patient entity but using EntityManager persist() method. I also need to set id of level manually ((Integer)poziomfacade.getEntityManager().createQuery("SELECT max(p.idpoziom) FROM PoziomDostepu p").getSingleResult())+1 because Generator in Level entity class doesn't work. Nevertheless the problem is solved, thanks for every constructive comment or answer.

ASP.NET MVC 3: ViewModel that deals with a list of lists

I'm trying to put together a ViewModel that will have a list of users and each user will have a list of locations.
The User table and Location table are joined together through another table that holds each respective ID and some other information. This table is essentially a many to many join table.
I've tried a few different viewModel approaches and they we're severely lacking... What would be the best approach for displaying this type of information?
I assume that the issue is that you want to be able to access the collection by either User or Location. One approach could be to use ILookup<> classes. You'd start with the many-to-many collection and produce the lookups like this:
var lookupByUser = userLocations.ToLookup(ul => ul.User);
var lookupByLocation = userLocations.ToLookup(ul => ul.Location);
Update:
Per your description, it seems like you don't really need to have a full many-to-many relationship in your ViewModel. Rather, your ViewModel could have a structure like this:
public class YourViewModel
{
public IEnumerable<UserViewModel> Users { get; set; }
}
public class UserViewModel
{
// User-related stuff
public IEnumerable<LocationViewModel> Locations { get; set; }
}
If you wanted to avoid redundant LocationViewModel objects, you could pre-build a mapping between your Model and ViewModel objects:
var locationViewModels = myLocations.ToDictionary(
loc => loc, loc => CreateLocationViewModel(loc));
And then reuse these objects when populating your page's ViewModel.