Official Grails documentation says that
Version 2.0.x of the scaffolding plugin includes different scaffolding
templates that are aligned with the new REST APIs introcued in Grails
2.3 and above.
(taken from here http://grails.org/doc/latest/guide/scaffolding.html)
But I can't make (or I don't understand the concept) work RESTfulness together with scaffolding.
Let's start from scratch:
grails create-app myapp
cd myapp/
grails create-domain-class Book
grails create-scaffold-controller myapp.Book
Add a field to the domain class
class Book {
String text
static constraints = {
}
}
and run the app with grails run-app.
Surfing on the http://localhost:8080/myapp/ shows that scaffolding works great:
http://localhost:8080/myapp/book/index page shows books list
http://localhost:8080/myapp/book/show/1 page show details for the book with id = 1
http://localhost:8080/myapp/book/create page creates a book
and so force, good old scaffolding.
Let's see what about REST.
Official docs say I should use URLs like http://localhost:8080/myapp/books/... for the REST but any attempt to access the app, like this curl -i -H "Accept: application/json" localhost:8080/myapp/books/1 returns 404 with bunch of HTML.
Ok, let's read docs carefully:
The easiest way to create a RESTful API in Grails is to expose a
domain class as a REST resource. This can be done by adding the
grails.rest.Resource transformation to any domain class
No problem, now the Book class heading is
import grails.rest.*
#Resource(uri='/books') class Book {
Now surfing on the http://localhost:8080/myapp/ shows that scaffolding is broken:
http://localhost:8080/myapp/book/index page shows books list
http://localhost:8080/myapp/book/create page shows xml output <?xml version="1.0" encoding="UTF-8"?><book><text /></book>
and so force, bad new xml output.
I'd played with #Resource and "/books"(resources:"book") in URLMappings.groovy but hadn't found any working solution which makes possible scaffolding and RESTfulness work back-to-back. Indeed, I managed to make them work separately.
Update
I'd found the way how to achieve the desired goal. The way I found is:
Mark the Book class with #Resource(uri = "/books").
Remove scaffold controller BookController.
Create dedicated controller with scaffolding for the Book: class HumanBookController {static scaffold = Book}
Now scaffold GUI pages with URLs like http://localhost:8080/myapp/humanBook/index work pretty well. Either json requests are handled well with URLs like http://localhost:8080/myapp/books/1. But it's not elegant to have 2 controllers doing same things for common web and json.
You can do this:
import grails.rest.RestfulController
class BookController extends RestfulController {
static responseFormats = ['html', 'json']
BookController() {
super(Book)
}
}
And then in the UrlMappings.groovy:
"/books"(resources:"book")
"/$controller/$action?/$id?(.${format})?"{
constraints {
// apply constraints here
}
}
No need to add #Resource in the domain.
You can now have /books/1.json or /books/1.html to point to the right places. You might still need to do grails generate-view Book to have the view generated. But although you need to generate the views for html, you keep only single controller and path.
I had the same problems as yours.
That might be a trivial solution and not for every case, but try updating your Grails version.
As for me: Grails 2.3.4 -> Grails 2.3.6 worked.
Hope that help anyone.
I'm currently using Grails 2.4.0, the solution came by doing this:
Controller: BookController { static scaffold = true }
Domain: Book { .... } // WITHOUT #Resource
The result is that you can:
/book.json to get a list JSONized
/book/index to get an HTML standard scaffolding
/book/create html scaffold for a new item
/book/show/1 html scaffold edit item 1
/book/show/1.json JSON for item id: 1
I'ts wicked, i know. I found this and it get me going.
With Grails 2.4.4 I was able to get the scaffolding working with single controller using the following steps:
Added a URL to Resource mapping in UrlMappings.groovy, e.g. "/books"(resources:"book")
Inserted static scaffold = true into the generated controller
I did not verify if the following made a difference, but I also set grails.mime.disable.accept.header.userAgents = [] and grails.mime.use.accept.header = true in Config.groovy (the latter is presumably the new default value).
Both of the scaffolded REST and UI interfaces are working fine with the following tests:
GET /app//1 (passing Accept header)
GET /app//1.json (no Accept header)
POST /app/ (with payload as json or form encoded)
DELETE /app//1
PUT /app//1 (with json payload. form payload updated the object, but sent back 302 redirects)
EDIT
Removed the Resource annotation step and clarified the URL mapping setup
The URI assigned in the URL mapping is not the same as the default URI for the controller. For example, "books" instead of "book". After adding this mapping, the URI for the controller will default to the URI in UrlMapping, but the original URI will continue to work.
The generated controller is a Restful controller because it implements actions aware of requests like:
curl -i -X GET yourDomain:8080/yourApp/books.json
It returns a list of books in json format. (10 books, assuming that you created test data, did you?)
You can append a parameter like:
curl -i -X GET yourDomain:8080/yourApp/books.xml?40
By default you will get the html format. You need to append .json or .xml to get the correct data.
You can to use the Accept header too
curl -i -X GET -H "Accept: application/xml" yourDomain/books/1
returns details of book with id=1 in xml format. Finally
curl -i -X POST -H "Content-Type: application/json" -d "{name: 'Book'}" yourDomain/books
creates a new book and
curl -i -X PUT -H "Content-Type: application/json" -d "{name: 'Book'}" yourDomain/books/1
updates name of book with id=1
All resources need to be exposes through and url. The url is not generated for you, you should write it on UrlMappings file:
"/v1/books"(resources: "book")
Where the first string "/v1/books" is the uri and the second string "book" is the controller name following the grails convention. (The preceding v1 string is because I always put version number to my API URIs)
| GET | /v1/books | Action: index |
| GET | /v1/books/create | Action: create |
| POST | /v1/books | Action: save |
| GET | /v1/books/${id} | Action: show |
| GET | /v1/books/${id}/edit | Action: edit |
| PUT | /v1/books/${id} | Action: update |
| DELETE | /v1/books/${id} | Action: delete |
All that should be required is #Resource annotation with the uri on the Domain class. If you want specific formats (default format is first), also include the formats:
#Resource(uri='/books', formats=['json', 'xml'])
That should be it. If ypu are still having trouble finding your dynamic #Resource endpoint, try running:
grails url-mappings-report
That will give you a nice summary of all urls, including those backed by scaffolded controllers for #Resource domains. I have found that I tend to make silly mistakes when trying to "guess" the URL - using the report output ensures you and grails are in agreement.
Related
When I call http://localhost:8080/nuxeo/api/v1/id/bad6cbc5-b75f-4373-981f-6908cec66779?enrichers.document=children endpoint it returns all child elements include deleted elements. But I need to get only active elements and I think I should add isTrashed=false query to endpoint. But http://localhost:8080/nuxeo/api/v1/id/bad6cbc5-b75f-4373-981f-6908cec66779?enrichers.document=children&isTrashed=false does not any effect. How can I get only active child elemets from nuxeo server using rest api?
/nuxeo/api/v1/id endpoint with children enricher does not support this kind of filtering.
I see two options:
Implement own enricher which will support filtering of trashed documents. children enricher is implemented by org.nuxeo.ecm.core.io.marshallers.json.enrichers.ChildrenJsonEnricher class so you can inspire there how to do that.
Use different end point with page provider which supports filtering of trashed documents:
/nuxeo/api/v1/search/pp/advanced_document_content/execute?&ecm_parentId=bad6cbc5-b75f-4373-981f-6908cec66779&ecm_trashed=false
Benefits of the second option:
paging - simply add currentPageIndex=0&offset=0&pageSize=20 to the query
properties - you can define what properties do you need by adding of header: properties:dublincore,common,uid,file
enrichers - it means that you can use enrichers for each child and receive for example permission or thumbnail URL for each child. To do that add this header: enrichers-document: thumbnail, permissions
Example curl call:
curl -X GET -u Administrator:Administrator \
-H "properties:dublincore,common,uid,file" \
-H "enrichers-document: thumbnail, permissions" \
"http://localhost:8080/nuxeo/api/v1/search/pp/advanced_document_content/execute?&ecm_parentId=bad6cbc5-b75f-4373-981f-6908cec66779&ecm_trashed=false" | jq
I am trying to test an app with RESTful services via Postman (awaiting the front-end to be implemented).
The GET/POST and DELETE requests work as expected, but when it comes to PUT and PATCH I'm completely stuck.
I have a simple form with several inputs and files (pdf & image). With the POST request I simply use the "form-data" body to add all needed parameters. But when I try to test the PUT one, "form-data" detects nothing - with or without a "Content-Type" = "multipart/form-data" set in the header.
The PUT only works with the "x-www-form-urlencoded" option for the body, but then I cannot or don't know how to add both files, as I don't have the ability to choose from "Text/File" anymore via Postman's dropdown. Again, adding Content-Type doesn't help a bit.
I tried simulating POST with a "_method"="PUT" both as a URL parameter and as a form input, but it just creates new item instead of updating existing one (with correct route applied).
# Updated with the routes (standard Laravel routes for RESTful services)
GET | api/v1/Collection | api.v1.collection.index
POST | api/v1/collection | api.v1.collection.store
GET | api/v1/collection/create | api.v1.collection.create
GET | api/v1/collection/{item} | api.v1.collection.show
PUT | api/v1/collection/{item} | api.v1.collection.update
DELETE | api/v1/collection/{item} | api.v1.collection.destroy
GET | api/v1/collection/{item}/edit | api.v1.collection.edit
I realize I have to use a hidden input with _method/PUT as key-value, but I have no idea where to add it in Postman. I can only see two options: text input or file.
What am I missing?
I can provide screenshots upon request. Thanks.
How do I change an initArgs value for Synonyms using the Managed Resources REST API?
In particular, I need to change the following:
"initArgs":{"ignoreCase":false}
... to true.
https://cwiki.apache.org/confluence/display/solr/Managed+Resources#ManagedResources-Synonyms
I don't see any mention in the documentation about changing initArgs.
You can edit the file directly after it has been created, but the docs explicitly say this is not the correct way to change data in this file. (it does work however).
found it. try&error style ;-)
curl -X POST -H 'Content-type:application/json' --data-binary '{"initArgs":{"ignoreCase":true}}' "http://<solr-host>/solr/<core>/schema/analysis/synonyms/german"
UPDATE 2022-12-01
This question is now obsolete. Smartsheet API now supports the requested feature. The accepted answer contains more details on the update.
CONTEXT
A developer wishes to populate an existing smartsheet worksheet using the smartsheet API. The data to populate consists of both data and formulas.
However, according to the API documentation (http://www.smartsheet.com/developers/api-documentation) formulas cannot be added using the API.
Cells containing formulas, links to other cells, system values, or
Gantt values cannot be inserted or updated through the API.
PROBLEM
Testing verifies this. Attempting to add a simple formula using the smartsheet API causes the formula to be transformed to opaque text. Inspection reveals that the formula is modified with the single quote character, which renders it as opaque text instead of a formula.
QUESTION
Is there any way (other than through manual entry) to force smartsheet to re-evaluate the opaque text inserted, so as to transform the opaque text back into a formula?
If it is not possible (other than through manual entry) is it possible to copy an existing sheet that has formulas in place, and then populate the non-formula data into the sheet, all using the smartsheet API?
GOAL
The basic goal is to find a way to populate formula data into the smartsheet application without having to require manual entry of the formula data.
Update: Smartsheet does now support adding or updating formulas via the API which can be found in the documentation for adding a row and updating a row.
The main difference is to set the formula in the row object instead of setting the value.
Original Answer
You are correct, formulas are not currently supported via the API. Although, we do plan to add this functionality in the future.
Currently, if you try to send a formula to the API it will be handled as a string and a single quote will be added to the beginning of the formula. Then the only way to convert the string back to a formula is to manually remove the single quote when inside the Smartsheet UI.
Available Option
Your suggestion to use a template will definitely work if you will always be using the same formulas. The process will look like the following:
Setup a template with the formulas that you want to use.
Create a new sheet from the template.
Add extra data to the new sheet that the formulas will use.
Note: rows that have never been used cannot be updated since they do not exist. So, in the template you can initialize the rows by putting a word in the locations that you want to update. For example, you could put the word "PLACEHOLDER" in all of the locations that you intend to update.
I have added two examples below one using curl and the other using our Java SDK.
Curl Example
Create a new sheet from the Template. Make sure to replace YOUR_TOKEN and YOUR_TEMPLATE_OR_SHEET_ID in the below command.
curl https://api.smartsheet.com/1.1/sheets?include=data,attachments,discussions -H "Authorization: Bearer YOUR_TOKEN" -H "Content-Type: application/json" -X POST -d '{"name":"newSheetFromTemplate","fromId":"YOUR_TEMPLATE_OR_SHEET_ID"}'
Then take the sheet id from the response and issue a command to get the row id's.
curl https://api.smartsheet.com/1.1/sheet/YOUR_SHEET_ID -H "Authorization: Bearer YOUR_TOKEN"
Last, grab the row id and column id from the response and issue a command to update the appropriate cells. I'm updating two cells with the values 1 and 2 with the below command.
curl https://api.smartsheet.com/1.1/row/YOUR_ROW_ID/cells -H "Authorization: Bearer YOUR_TOKEN" -H "Content-Type: application/json" -X PUT -d '[ {"columnId": YOUR_COLUMN_ID, "value": 1}]'
curl https://api.smartsheet.com/1.1/row/YOUR_ROW_ID/cells -H "Authorization: Bearer YOUR_TOKEN" -H "Content-Type: application/json" -X PUT -d '[ {"columnId": YOUR_COLUMN_ID, "value": 2}]'
SDK Example
This example requires installing our Java SDK. There is also a C# SDK that works in a very similar fashion.
import java.util.EnumSet;
import java.util.List;
import com.smartsheet.api.Smartsheet;
import com.smartsheet.api.SmartsheetBuilder;
import com.smartsheet.api.models.Cell;
import com.smartsheet.api.models.Column;
import com.smartsheet.api.models.ObjectInclusion;
import com.smartsheet.api.models.Row;
import com.smartsheet.api.models.Sheet;
import com.smartsheet.api.SmartsheetException;
public class Test {
public static void main(String[] args) throws SmartsheetException {
// Setup a Smartsheet object
Smartsheet smartsheet = new SmartsheetBuilder().setAccessToken("YOUR_TOKEN").build();
// Create a copy of a sheet from the template
Sheet newSheet = new Sheet.CreateFromTemplateOrSheetBuilder().setFromId(YOUR_TEMPLATE_OR_SHEET_ID).setName("newSheetName").build();
newSheet = smartsheet.sheets().createSheetFromExisting(newSheet, EnumSet.allOf(ObjectInclusion.class));
// Get the columns/rows/data for the sheet we just created
newSheet = smartsheet.sheets().getSheet(newSheet.getId(), EnumSet.allOf(ObjectInclusion.class));
// Grab the column and rows that will be updated in the new sheet
Column column1 = newSheet.getColumnByIndex(0);
Row row1 = newSheet.getRowByRowNumber(1);
Row row2 = newSheet.getRowByRowNumber(2);
// Setup two cells for the the specified columns
List<Cell> newCell1 = new Cell.UpdateRowCellsBuilder().addCell(column1.getId(), 1).build();
List<Cell> newCell2 = new Cell.UpdateRowCellsBuilder().addCell(column1.getId(), 2).build();
// Update the cell for the specified row
smartsheet.rows().updateCells(row1.getId(), newCell1);
smartsheet.rows().updateCells(row2.getId(), newCell2);
}
}
I created a rest server. Using POST method, I send the following data:
{"class":"abc.User","email":"me#gmail.com"}
To make sure it work, I use curl:
curl --request POST --data '{"class":"abc.User","email":"me#gmail.com"}' --header "Content-Type: application/json" http://localhost:8081/abc/api/add
The problem arise when I create rest client to send data. I use rest-client-builder plugin.
My code for client is:
def resp = rest.post("http://localhost:8082/abc/api/add"){
contentType "application/json"
json {
class = "dsi.ewallet.prepaidserver.Kartu"
email = "me#gmail.com"
}
}
I got error: "unexpected token"
If I remove the 'class' , it fix the problem. But my rest server need to pass class name.
Is there anyway to fix it? Or how to remove the need to pass class name for rest server? Thanks.
I think the problem is class is reserved word in java as you can use Class.class or this.getClass(), so it is making problem while parsing, so I think you have to change the name of this attribute.
it is working with curl because it is not using java
I decide to use JSONBuilder()
def builder = new JSONBuilder()
String result = builder.build() {
setProperty("abc", abcValue)
setProperty("def", defValue)
}
return result
When I need to construct more complex structure, I create groovy class that contains only the attributes. Then populate the data to that class and use it with JSONBuilder().