How to pass reference to an object as JSON in a POST in grails - rest

I have 2 classes Person and and Designation. I am using #Resource annotation to make them restful
import grails.rest.Resource
#Resource(formats=['json', 'xml'])
class Person {
String name
static belongsTo = [designation:Designation]
static constraints = { }
}
and Designation class is
import grails.rest.Resource
#Resource(formats=['json', 'xml'])
class Designation {
Long designationId
String name
static constraints = { }
}
Since I'm using grails default resource url mapping like
"/app/person"(resources: "person", includes: ['index', 'show', 'save', 'update', 'delete', 'create', 'patch'])
I want to prform insert to Person object using POST request to /app/person with json data.
Since designation is belongs to another object , I need to pass only a reference to an existing Designation object id
how do i pass this via json? what is the json format to do the same?
or can I access the params in beforeInsert() method of Person class , so that i can manually get designatioId , then find Designation object with that id , then assign it new Person object?

I think if you add:
designation: { id: 123 }
to your person JSON, that should do the trick.

Related

How not to allow an IRI when denormalizing embedded relations?

I have a Customer entity that is linked to a Contact entity, in a nullable OneToOne relationship.
When I create a new Customer, the creation of the linked Contact is optional, but it must not be possible to fill in the IRI of an existing Contact. In other words, it must be a new Contact or nothing.
class Customer
{
#[ORM\OneToOne(targetEntity: Contact::class, cascade: ["persist"])]
#[Groups([
'write:Customer:collection', '...'
])]
private $contact;
}
The 'write:Customer:collection' denormalization group is also present on the Contact properties.
With a good request as follow, I can create my Customer and my Contact, no problem with it.
{
"name": "test company",
"contact": [
"firstname" => 'hello',
"lastname" => 'world'
]
}
Problem:
But, and I don't want it, I also can create the new Customer with an existing Contact, like this:
{
"name": "test company",
"contact": "/api/contacts/{id}"
}
As stated in the serialization documentation:
The following rules apply when denormalizing embedded relations:
If an #id key is present in the embedded resource, then the object corresponding to the given URI will be retrieved through the data provider. Any changes in the embedded relation will also be applied to that object.
If no #id key exists, a new object will be created containing data provided in the embedded JSON document.
However, I would like to disable the rule if an #id key is present, for specific validation group.
I thought of creating a custom constraint that would check that the resource does not exist in the database, but I am surprised that no constraint allows to check this.
Am I missing something? Do you have a solution for me? Thanks in advance.
I finally created a custom constraint that checks if the embed resource sent in request is already managed by Doctrine.
The constraint itself:
namespace App\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
* #Target({"PROPERTY", "METHOD", "ANNOTATION"})
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class AcceptPersisted extends Constraint
{
public bool $expected = false;
public string $mustBePersistMessage = 'Set a new {{ entity }} is invalid. Must be an existing one.';
public string $mustBeNotPersistMessage = 'Set an existing {{ entity }} is invalid. Must be a new one.';
public function __construct(bool $expected = false, $options = null, array $groups = null, $payload = null)
{
parent::__construct($options, $groups, $payload);
$this->expected = $expected;
}
}
And it validator:
namespace App\Validator\Constraints;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
class AcceptPersistedValidator extends ConstraintValidator
{
public function __construct(private EntityManagerInterface $entityManager) {}
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof AcceptPersisted) {
throw new UnexpectedTypeException($constraint, AcceptPersisted::class);
}
if ($value === null) {
return;
}
//if current value is/is not manage by doctrine
if ($this->entityManager->contains($value) !== $constraint->expected) {
$entity = (new \ReflectionClass($value))->getShortName();
$message = $constraint->expected ? $constraint->mustBePersistMessage : $constraint->mustBeNotPersistMessage;
$this->context->buildViolation($message)->setParameter("{{ entity }}", $entity)->addViolation();
}
}
}
So, I just had to add the custom constraint on my property:
use App\Validator\Constraints as CustomAssert;
class Customer
{
#[ORM\OneToOne(targetEntity: Contact::class, cascade: ["persist"])]
#[CustomAssert\AcceptPersisted(expected: false)]
//...
private $contact;
}

Flutter: Issue while converting json to model using json_serializable

Suppose there are two models User and City
#JsonSerializable()
class User {
int id;
String name;
City? city;
}
#JsonSerializable()
class City {
int id;
String name;
}
Now suppose during API call, we've got a user model but in the city object model, we only get id not name. Something like this
{
"id": 5,
"name": "Matthew",
"city": {
"id": 12
}
}
But due to the default nature of json_serializable and json_annotation.
This JSON is not mapped to the User model, during mapping, it throws the exception.
type Null is not a subtype of type String. (because here name key is missing in city object)
But as we already declared in the User object that City is optional, I wanted that it should parse the User JSON with city as null.
Any help or solution would be really appreciated, Thank you
There is currently no support for ignoring a certain field only while serializing or only while deserializing. You can either ignore both or none. However, there is a workaround that I use.
Make a global method in your model file that just returns null like this:
T? toNull<T>(_) => null;
Inside your User model add a custom JsonKey for City:
#JsonKey(fromJson: toNull, includeIfNull: false)
City? City;
What this does is when converting from Json it uses your specificed function for converting city and replaces your value with null. Then due to includeIfNull property it just skips parsing.

make ID field required with MongoEngineObjectType

I use graphene with graphene-mongo. My graphql schema has a type similar to this:
type Report {
id:ID!
name:String!
}
My graphene class for this type is
class Product(MongoengineObjectType):
class Meta:
model = MongoProduct
and the mongoengine class is
class MongoProduct(mng.DynamicDocument):
name = mng.fields.StringField(required=True)
How can I make the field id required? GraphiQL shows an exclamation mark next to name, but not next to id.
class MongoProduct(mng.DynamicDocument):
id = ObjectIdField(primary_key=True, required=True) # Optional: Add default=bson.ObjectId
name = mng.fields.StringField(required=True)
id can also be a IntField or a StringField but I'd recommend to stick to an ObjectId

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
}
}

Getting from ToManyField to Model values

In django, I have two models - User and UserProfile. There may exist zero or one profiles for a particular user. I'm trying to include the information from the UserProfile model directly on the UserResource.
I would like to use the profile ToManyField, if it exists, to access the contents of the associated UserProfile model. I've tried a variety of things in dehydrate, including self.profile.get_related_resource(self) and UserProfile.objects.get(id=...), but I can't seem to find my way from the profile field to the model object. Can anybody help me out?
I'm still new to Python, Django, and Tastypie, so hopefully if I'm doing anything awful somebody will be kind enough to point it out.
The goal is to have JSON that looks like this:
{
resourceUri: /v1/users/1
date_of_birth: Jan 1, 1980
... etc
}
where date_of_birth is a property of the UserProfileResource. I don't want all of the fields from UserProfileResource, and I don't want the UserProfile to be a nested object in the response - I want some fields from UserProfileResource to be top-level fields in the response, so that they look like part of the User resource.
class UserResource(ModelResource):
profile = fields.ToOneField('foo.api.UserProfileResource', 'user', null=True)
class Meta:
queryset = User.objects.all()
resource_name = 'users'
allowed_methods = ['get']
#etc...
class UserProfileResource(ModelResource):
date_of_birth = ...
#etc
I assume you're using Django 1.4 and AUTH_PROFILE_MODULE?
Since the User:UserProfile relationship is 1:1 I would opt for the ToOneField instead. This will serialize as a URI pointer to your UserProfileResource (if one exists.) If you'd like the UserProfileResource field data inline with your UserResource, you can specify full=True in the ToOneField definition. With this method you should not need to override dehydrate.
Also, ensure that the second argument in the ToOneField definition is the User attribute which points to your UserProfile Django model. For example if you have OneToOneField(User, related_name='profile') in your Django model then this attribute should be profile.
class UserResource(ModelResource):
profile = fields.ToOneField('foo.api.UserProfileResource', 'profile',
full=True, null=True)
class Meta:
queryset = User.objects.all()
resource_name = 'users'
allowed_methods = ['get']
If what you're after are specific fields from the UserProfile instance mixed in with your User, you should be able to do something like this:
class UserResource(ModelResource):
date_of_birth = fields.DateField('profile__date_of_birth', null=True)
class Meta:
queryset = User.objects.all()
resource_name = 'users'
allowed_methods = ['get']
fields = ['userfields', 'gohere', 'date_of_birth']