Spring Data Rest OneToMany POST JSON With Child Entity - spring

I've been searching for an answer for this since yesterday and have not been successful in finding it.
Can you save an entity with it's child entity at the same time? From what I've seen, the way to do it is to save the entity, follow the link from the response, and add the child entity. Is there a way to post it all at once?
{
"name": "some-name",
"age": "30",
"address": {
"street": "some-street",
"city": "some-city"
}
}
In the above example we have a Person entity for example and it has a OneToMany relationship to Address entity. I know that you save Person, get the link from the response, and save Address, but it would be convenient to do it all in one shot if that's possible. I am assuming that it's not possible out of the box but figured I would ask before writing a custom controller method to handle it in one shot.

That will work for adding a new entity via POST. For editing an existing entity this would also work via a PUT request if you include all the data and expose the IDs in the JSON.
e.g.
public class MvcConfiguration extends RepositoryRestConfigurerAdapter {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
super.configureRepositoryRestConfiguration(config);
config.exposeIdsFor(/*Person.class,*/ Address.class);
}
}
PUT /person/123
{
//"id" : 123, prob not required
"name": "new-name",
"age": "30",
"address": {
"id": 1, //required
"street": "new-street",
"city": "some-city"
}
}
I have used when there have been multiple levels of nesting and with collections but only when child entities do not have their own REST endpoints: I am not sure if that would have any bearing on things.

Related

Is it ok to use an array of objects in Rest Controller

I'm writing a controller that handles an array of object, something like:
#PostMapping("/post")
public void saveEmployeeArray(#RequestBody Emoloyee[] employeeArray)
{
// Method body
}
Keep in mind that, in this case, employees are usually created, update, or deleted in bulk, meaning, I usually need to handle multiple employees at the same time.
A colleague told me that although it works I should only handle one employee in the controller as, according to him, it's rest best practice. But he didn't present an alternative to the issue of having to handle multiple employees most of the time, other than making multiple requests.
My question is, how is the best way to handle multiple objects?
If your list of Employee is inside another data structure (e.g. company), you can offer PATCH operation for outer resource to add or change a list of Employees. JSON PATCH is specified in RFC 6902, see https://datatracker.ietf.org/doc/html/rfc6902/
The JSON body of a PATCH request would look like this:
[
{ "op": "add", "path": "/employees", "value": { "name": "Employee 1", ...} },
{ "op": "add", "path": "/employees", "value": { "name": "Employee 2", ...} },
...
]
The body is a list of PATCH operations to change the addressed data structure at several places within one request, so it fits to your requirement.
The specification in RFC 6902 offers more than just adding elements, you can also remove, replace, move, copy and test. Have a look in the RFC for good and simple examples.

Laravel attribute not working as expected

In the current application, there is a model Part and a model Supplier. Every Part has a Supplier.
public function supplier()
{
return $this->belongsTo(Supplier::class);
}
I added an accessor to get the name of the supplier.
If i return the whole dataset, i get the supplier:
public function getSupplierNameAttribute()
{
return $this->supplier;
}
"supplierName": {
"id": 1,
"uuid": "37e3a715-09d3-4fac-ae88-8f12e63fe79c",
"name": "Laserteam",
"street": "8602 Dessie Tunnel",
"zip": "15869",
"city": "New Clementview",
"email_send_type": null,
"active": 0,
"created_at": "2020-01-09 09:46:02",
"updated_at": "2020-01-09 09:46:02",
"deleted_at": null,
"action": "",
"activeLabel": "<span class='badge badge-secondary'>Inaktiv<\/span>"
},
If i try to get only the name (what I need finally), there is an error:
public function getSupplierNameAttribute()
{
return $this->supplier->name;
}
ErrorException: Trying to get property 'name' of non-object in file /gopanel/sites/7industry_net/public/7time/app/Models/Part/PartAttribute.php on line 48
If i try in this way, it works:
public function getSupplierNameAttribute()
{
return $this->supplier['name'];
}
Why does return $this->supplier->name; not work?
It's hard to say what could be the problem here, but:
Make sure you don't have supplier in casts for your model
Make sure you don't use supplier for database column name or you don't make somewhere something this
$this->supplier = $this->supplier->toArray();
It seems supplier property somewhere becomes array that's why one notation works but the other doesn't.
I’m not really sure what’s going on there; if you have a supplier relation then accessing the property should lazy-load the relationship.
Alternatively, you could try this syntax:
public function getSupplierNameAttribute()
{
$this->loadMissing('supplier');
return $this->getRelation('supplier')->name;
}
However, I’d avoid defining accessors like this. It can lead to N+1 problems if you say, retrieve a collection of parts and then call $part->supplier_name on each one without eager-loading the supplier relationship.
Personally, if I’m accessing attributing on relations that I prefer to do it through the relation (i.e. $part->supplier->name) so any relations I need to eager load are shown to me.
Try this code
public function getSupplierNameAttribute()
{
return $this->supplier->name ?? 'supplier not exists';
}

How do I integrate Spring Data Elasticsearch (Repository Interface) and template queries?

Elasticsearch has a concept called template query, where we can store a templated query on the Elasticsearch server.
This is an example of how we can store such a query with a single parameter firstName:
POST _scripts/findByProfileFirstName
{
"script": {
"lang": "mustache",
"source": {
"query": {
"match": {
"esCandProfile.firstName": "{{firstName}}"
}
}
}
}
}
This can be invoked using the following example:
GET candidates/_search/template
{
"id": "findByProfileFirstName",
"params": {
"firstName": "Shannon"
}
}
How do I extend Spring Data Elasticsearch to achieve the following:
public interface CandidateRepository extends ElasticsearchRepository<Candidate, String> {
#Query("findByProfileFirstName")
Page<Candidate> findByCustomQuery(#Param("firstName") String firstName, Pageable pageable);
}
For simplicity, expect the following:
The template will be generated/maintained outside of the application lifecycle
I am willing to go through the trouble of writing this code once to make it work across all template queries
What is the best approach to neatly implement this, preferably within the framework's extension hooks?
For this, we would need to introduce a new annotation like #TemplateSearch because #Query is used to build a wrapped query that is sent to the _search endpoint. Here we need to address the _search/template endpoint with different arguments.
The SearchOperations interface would need an additional method like searchByTemplate which does the call to Elasticsearch, and the Repository logic would need to be adapted to use this new method when a repository method is annotated with the new annotation.
Please create an issue for this improvement in Jira.

Map HATEOAS links to actual API links

I'm trying to implement a HATEOAS Rest Client using Spring Boot.
Right now, I'm stuck in a point where I need to convert HATEOAS into an actual API URI.
If I post a new object of type Customer like:
{
"name": "Frank",
"address": "http://localhost:8080/address/23"
}
And then I retrieved with a request to http://localhost:8080/api/customer/1`, HATEOAS gives me something like
{
"name": Frank,
"_links": {
"address": {
"href": "http://localhost:8080/api/customer/1/address"
}
}
}
Is it possible to convert a link of the form of http://localhost:8080/api/customer/1/address to an API call like http://localhost:8080/api/address/23 ?
If you see what HATEOS returns after you say,
GET: http://localhost:8080/api/customer/1
is
{
"name": Frank,
"_links": {
"address": {
"href": "http://localhost:8080/api/customer/1/address"
}
}
}
According to Understanding HATEOS,
It's possible to build more complex relationships. With HATEOAS, the output makes it
easy to glean how to interact with the service without looking up a specification or
other external document
which means,
after you have received resource details with
http://localhost:8080/api/customer/1
what other operations are possible with the received resource those will be shown for easier/click thru access to your service/application,
here in this case HATEOS could find a link http://localhost:8080/api/customer/1/address that was accessible once you have customer/1 and from there if you want then without going anywhere else customer/1 's address could be found with /customer/1/address.
Similarly if you have /customer/1's occupation details then there would be another link below address link called http://localhost:8080/api/customer/1/occupation.
So if address is dependent on customer i.e. there can be no address without customer then your API endpoint has to be /api/customer/1/address and not directly /api/address/23.
However, after understanding these standards and logic behind HATEOS's such responses if you still want to go with your own links that may not align with HATEOS's logic you can use,
Link Object provided by LinkBuilder interface of HATEOS.
Example:
With object of type Customer like:
Customer c = new Customer( /*parameters*/ );
Link link= linkTo(AnyController.class).slash("address").slash(addressId);
customer.add(link);
//considering you want to add link `http://localhost:8080/api/address/23` and `23 is your addressID`.
Also you can create a list of Links and keep adding many such links to that list and then add that list to your object.
Hope this helps you !

Laravel 5 Eloquent model dynamically generated at runtime

I am looking to make some sort of "GenericModel" class extending Eloquent's Model class, that can load database configuration (like connection, table name, primary key column) as well as relationships at runtime based on a configuration JSON file.
My reasons for wanting this are as follows: I'm going to have a lot of database tables and thus a lot of models, but most don't really have any complicated logic behind them. I've developed a generic CRUD API and front-end interface to interact with them. Each model has a "blueprint" JSON file associated with it that describes things like its attributes and relationships. This lets me automatically generate, say, a view to create a new model and it knows what attributes I need to fill in, what input elements to use, what to label them, which are mandatory, how to validate, whether to check for uniqueness, etc. without ever needing code specific to that model. Here's an example, project.json:
{
"db_table": "projects",
"primary_key": "projectid",
"display_attr": "title", // Attribute to display when picking row from list, etc
"attributes": {
"projectid": { // Attribute name matches column name
"display": "ID", // Display this to user instead of db column name
"data_type": "integer" // Can be integer, string, numeric, bool...
},
"title": {
"data_type": "string",
"unique": true // Check for uniqueness when validating field
},
"customer": {
"data_type": "integer", // Data type of local key, matches customer PK
"relationship": { // Relationship to a different model
"type": "manytoone",
"foreign_model": "customer"
},
"user": "autocomplete" // User input element/widget to use, queries customer model for matches as user types
},
"description": {
"data_type": "string",
"user": "textarea" // Big string, use <textarea> for user input
"required": false // Can be NULL/empty, default true
}
},
"views": {
"table": [ // Show only these attributes when viewing table
"customer",
"title"
],
"edit_form": [ // Show these when editing
"customer",
"title",
"description"
],
...
}
}
This works extremely well on the front end, I don't need any more information than this to describe how my models behave. Problem is I feel like I just end up writing this all over again in most of my Model classes and it seems much more natural to have them just pull information from the blueprint file as well. This would result in the information being in one place rather than two, and would avoid extra effort and possible mistakes when I change a database table and only need to update one file to reflect it.
I'd really just like to be able to do something like GenericModel::blueprint('project.json')->find($id) and get a functioning "product" instance. Is this possible, or even advisable? Or is there a better way to do this?
Have you looked at Migrations (was Schema Builder)? It allows you to programatically build models (from JSON if necessary).
Then you could leverage Eloquent on your queries...

Resources