I have a very simple snippet to add a new row to the books table in the database:
def add = Book.toForm(Full("Add"), { _.save })
Calling this snippet in my template generates a form just fine, and submitting the form gives me a post request, but nothing happens, it never tries to talk to the database, no errors or exceptions occur:
09:03:53.631 [865021464#qtp-2111575312-18] INFO net.liftweb.util.TimeHelpers - Service request (POST) /books/ returned 200, took 531 Milliseconds
I am not sure if my model's save method is just not being called, or if the save method is not working. Based on examples in the book "Lift in Action", I am under the impression that the default Mapper save method should just work, and that is what I am using right now. My model class is simply:
class Book extends LongKeyedMapper[Book] with IdPK {
def getSingleton = Book
object name extends MappedString(this, 100)
}
object Book extends Book with LongKeyedMetaMapper[Book] {
override def dbTableName = "books"
}
Am I missing something in my model, or does this appear to be correct? If this should work, how do I debug it not working?
Forms don't work if you don't have a session (so you need cookies enabled). The session maps the form name to a function on the server. Unfortunately, lift doesn't log an error when the form's handler function isn't found.
Related
I am working on a simple Finatra API example, but having trouble using a case class for the POST request when using more than one field in the request body.
Here is an example of my case class and controller:
class SaleResource extends Controller {
post("/sales") { sale: Sale =>
s"$sale"
}
}
case class Sale(
user: Option[String],
streetAddress: Option[String]
)
I send a post request to that route with the following request body JSON:
{
"user": "Foo Barrington",
"streetAddress":"Baz Street"
}
The response:
Sale(Some(Foo Barrington),None)
The response shows that the user is being properly deserialized, but for some reason I cannot get the streetAddress field to populate.
Also, I noticed when I set either of these fields to String instead of Option[String] I only get unsuccessful 500 responses.
Things I have tried:
case statements matching Some(streetAddress) to that fields string value or "none found" when it is None. In these cases it still is saying streetAddress is None when it is not.
Making the request with both curl and Postman.
I can always access the user field from the Sales object, but never the streetAddress (or any other field from the request body for that matter if I add test elements to the case class.
I would expect both fields to be recognized since they are both provided in the request. I am newer to Scala/Finatra in general, so it is possible I am just using the Finatra library or Case classes incorrectly.
EDIT:
It seems as if changing the field names to not be mixed/camelcase fixes all issues, but this seems like odd behavior.
Finatra uses Jackson library behind the scenes. The default configuration uses PropertyNamingStrategy.SNAKE_CASE which seems like:
{
"user": "Foo Barrington",
"street_address":"Baz Street"
}
You need to change it to PropertyNamingStrategy.LOWER_CAMEL_CASE to parse that JSON.
In order to do that, you need to define a custom FinatraJacksonModule and tell the app to
use it.
object CustomFinatraJacksonModule extends FinatraJacksonModule {
override val propertyNamingStrategy = PropertyNamingStrategy.LOWER_CAMEL_CASE
}
class MyFinatraHttpServer extends HttpServer {
override protected def jacksonModule: Module = CustomFinatraJacksonModule
}
Jackson Integration provides more information about the topic.
I have a REST API which I have configured as follows
#Api(value="rest", description="Sweet blah!!!")
#Controller
public class abc{...}
A method in abc is annotated as follows
#ApiOperation(value="Create Account",
notes="Sweet Blah",
response=Account.class,
nickname="AccountCreation2",
produces= "application/json,application/xml",
consumes="application/json, application/xml")
#ApiImplicitParams(value=
{ #ApiImplicitParam(name="body",value="Sweet Blah.",
required=true, paramType="body", dataType="com.trrr.Account"),
#ApiImplicitParam(name="accountId", value="provides account Id for the new
account",required=true, paramType="path", dataType="Integer")
})
#RequestMapping(value = "/accounts/{accountId}", method = RequestMethod.PUT)
public ResponseEntity<?> createAccount(#PathVariable("accountId") Integer accountId,
#RequestBody Account acct){ ... }
My generated documentation using Swagger UI shows everything find however is unable to generated Model json for Account which is my model class.
Account is composed of few variables, in addition to an array of User Defined class 'Sharing'.
It is composed of another User defined class User.
Account class is annotated as follows:
#XStreamAlias("Account")
#XmlRootElement(name = "Account")
public class Account {... }
The generated documentation displays for Model Response and Request
**{
"unknownFields": {}
}**
Kindly guide as to what may be going wrong here. How to have a json version of Account object displayed. Thank you.
Well, the same thing worked on a next day. Not sure why it was not working on day one. Its appears idiotic but only purpose of me writing this is to ensure that nobody else doing the right way and gets confused with my post.
I have data writing to a mongoDB database with issues using integration tests and the Grails scaffolding. When trying to select a domain instance from the 'list' type page, I get the error "[domain name] not found with id null".
I am sure it is because of the Grails url [controller]/[action]/[id]. This id is a string and needs to be converted to an ObjectId for Grails queries.
Is there a way to do this so that it affects a specified domain or even better yet, all of the domains at once?
I guess as I'm writing my app, I can convert it to an ObjectId from within the action method, but I'd like to have the scaffolding work or provide a global solution.
I believe this is happening because the show() method (that the Grails scaffolding functionality generates as an action) accepts an id parameter of type Long ie.
def show(Long id) {
def suiteInstance = Suite.get(id)
if (!suiteInstance) {
flash.message = message(code: 'default.not.found.message', args: [message(code: 'suite.label', default: 'MyDomainClass'), id])
redirect(action: "list")
return
}
[suiteInstance: suiteInstance]
}
which binds the id parameter to the argument. Because the ObjectId can't be converted to a Long, it ends up being null, hence the call to MyDomainClass.get(id) fails with the error message.
You can get around this by overriding the show() action in your scaffolded controller so that it expects an ObjectId or String, but I would say the proper fix for this is to update the Grails scaffolding plugin so it is a little more liberal in the types of IDs it accepts.
I had this problem as well. You can keep the domain object id as an ObjectId and update the controller as follows:
domain Object:
import org.bson.types.ObjectId;
class DomainObject {
ObjectId id
// Add other member variables...
}
Controller:
def show(String id) {
def domainObjectInstance = domainObject.get(new ObjectId(id))
if (!domainObjectInstance) {
flash.message = message(code: 'default.not.found.message', args: [message(code: 'domainObject.label', default: 'DomainObject'), id])
redirect(action: "list")
return
}
[domainObjectInstance: domainObjectInstance]
}
You would also need to update your other controller methods that use id as well such as edit, update etc.
Additionally, if you want the grails default controller generation to work like this for all your domain objects you can update the template as coderLMN suggests.
The get(params.id) call in show() method will NOT convert params.id String to an ObjectId object, so the domain instance will be null, then the following code takes you to list action with an error message:
if (!exampleInstance) {
flash.message = message(code: 'default.not.found.message', args: [message(code: 'example.label', default: 'Example'), params.id])
redirect(action: "list")
return
}
Possible solutions:
you can run "grails install-template" command, so that the scaffolding templates in src/templates/scaffolding/ directory can be modified. Then you have new scaffold ready to generate customized controllers, views, tests for all your Domain classes.
A simpler solution is to define the id property as String instead of ObjectId. A String id will be equal to objectId.toString(), in this case your scaffold will work.
In domain classes keep you id type as ObjectId and keep scaffold = true for all respective controllers.
In Domain class :
ObjectId id
In respective controller :
static scaffold = true
Clear all existing collections from Mongo
I guess that's sufficient to have Grails-Mongo app up & running, considering you have correctly configured mongo-plugin
When attempting to edit a new (proxy) entity using RequestFactoryEditorDriver.edit() I am getting the following error: "Exception caught: Attempting to edit an EntityProxy previously edited by another RequestContext". I am fairly sure that this is a result of my misunderstanding of the request factory/editor framework architecture. Here is the editor code that I think pertains to this problem:
public class OrgMaintenanceWidget extends Composite implements Editor<IOrgProxy> {
... other fields ...
private IOrgEditorDriver _orgEditorDriver;
interface IOrgEditorDriver extends RequestFactoryEditorDriver<IOrgProxy, OrgMaintenanceWidget> {}
public OrgMaintenanceWidget(final IClientFactory clientFactory) {
... widget initialization ...
_orgEditorDriver = GWT.create(IOrgEditorDriver.class);
_orgEditorDriver.initialize(_clientFactory.getRequestFactory().getEventBus(),
_clientFactory.getRequestFactory(), this);
}
#UiHandler("newButton")
public void onNewButtonClick(final ClickEvent clickEvent) {
_org = _clientFactory.getCache().getOrgCache().newOrg();
_orgEditorDriver.edit(_org, _clientFactory.getRequestFactory().orgRequestContext());
}
...
}
It's the "_orgEditorDriver.edit()" line that causes the exception. The "newOrg()" method is:
public IOrgProxy newOrg() {
return _clientFactory.getRequestFactory().orgRequestContext().create(IOrgProxy.class);
}
The RequestFactory is simply:
public interface IRequestFactory extends RequestFactory {
IOrgRequestContext orgRequestContext();
}
I am sure that I'm missing something fundamental about editing a new entity. When I edit an existing entity everything is fine ... the UI components are populated automatically, and flushing the editor back to the entity works very nicely. Here's the code that initiates editing for an existing entity:
#UiHandler("newButton")
public void onNewButtonClick(final ClickEvent clickEvent) {
_org = _clientFactory.getCache().getOrgCache().newOrg();
_orgEditorDriver.edit(_org, _clientFactory.getRequestFactory().orgRequestContext());
}
Any help would be greatly appreciated, and I'll try to publish any lessons learned.
This code:
_clientFactory.getRequestFactory().orgRequestContext().create(IOrgProxy.class);
Means:
Create new orgRequestContext()
Create new IOrgProxy using this context
Edit new IOrgProxy using this context, because as docs say: "Returns a new mutable proxy that this request can carry to the server, perhaps to be persisted.", it means that the proxy is edited by this request.
This code:
_orgEditorDriver.edit(_org, _clientFactory.getRequestFactory().orgRequestContext());
Means:
Again, create new orgRequestContext() (because each invocation of getRequestFactory().orgRequestContext() provides new instance of orgRequestContext()
"Start driving the Editor and its sub-editors with data." as docs say. But as a part of it, use passed orgRequestContext() to edit passed IOrgProxy instance, so that the proxy is editable.
Because the proxy was already edited while created by other RequestContext, you get the exception, because there is fundamental rule in RequestFactory, that proxy can be edited only by one RequestContext.
See also this thread.
I think you can't create an object with one RequestContext and then edit it with another one.
So you can solve this in two ways:
Persist the created object with the RequestContext you used when you created the object. The save method should return the persisted object and this persisted object can be passed to the editor with a fresh new RequestContext
Somewhere save the RequestContext you used for creating the object and pass it to the edit function of your Driver
Solution two could look something like this:
#UiHandler("newButton")
public void onNewButtonClick(final ClickEvent clickEvent) {
IOrgRequestContext ctx = _clientFactory.getRequestFactory().orgRequestContext();
_org = ctx.create(IOrgProxy.class);
_orgEditorDriver.edit(_org,ctx );
}
What’s the preferred way to handle 404 errors with Play 2.0 and show a nice templated view?
You can override the onHandlerNotFound method on your Global object, e.g.:
object Global extends GlobalSettings {
override def onHandlerNotFound(request: RequestHeader): Result = {
NotFound(views.html.notFound(request))
}
}
Please note that there are really two different problems to solve:
Showing a custom 404 page when there is "no handler found", e.g. when the user goes to an invalid URL, and
Showing a custom 404 (NotFound) page as a valid outcome of an existing handler.
I think the OP was referring to #2 but answers referred to #1.
"No Handler Found" Scenario
In the first scenario, for "no handler found" (i.e. invalid URL), the other answers have it right but to be more detailed, per the Play 2.1 documentation as:
Step 1: add a custom Global object:
import play.api._
import play.api.mvc._
import play.api.mvc.Results._
object Global extends GlobalSettings {
override def onHandlerNotFound(request: RequestHeader): Result = {
NotFound(
views.html.notFoundPage(request.path)
)
}
}
Step 2: add the template. Here's mine:
#(path: String)
<html>
<body>
<h1>Uh-oh. That wasn't found.</h1>
<p>#path</p>
</body>
</html>
Step 3: tweak your conf/application.conf to refer to your new "Global". I put it in the controllers package but it doesn't have to be:
...
application.global=controllers.Global
Step 4: restart and go to an invalid URL.
"Real Handler can't find object" Scenario
In the second scenario an existing handler wants to show a custom 404. For example, the user asked for object "1234" but no such object exists. The good news is that doing this is deceptively easy:
Instead of Ok(), surround your response with NotFound()
For example:
object FruitController extends Controller {
def showFruit(uuidString: String) = Action {
Fruits.find(uuidString) match {
case Some(fruit) => Ok(views.html.showFruit(fruit))
// NOTE THE USE OF "NotFound" BELOW!
case None => NotFound(views.html.noSuchFruit(s"No such fruit: $uuidString"))
}
}
}
What I like about this is the clean separation of the status code (200 vs 404) from the HTML returned (showFruit vs noSuchFruit).
HTH
Andrew
If you want to do the same using Java instead of Scala you can do it in this way (this works for play framework 2.0.3):
Global.java:
import play.GlobalSettings;
import play.mvc.Result;
import play.mvc.Results;
import play.mvc.Http.RequestHeader;
public class Global extends GlobalSettings {
#Override
public Result onHandlerNotFound(RequestHeader request) {
return Results.notFound(views.html.error404.render());
}
}
Asumming that your 404 error template is views.html.error404 (i.e. views/error404.scala.html).
Please note that Play development team are making lots of efforts to move away from global state in Play, and hence GlobalSettings and the application Global object have been deprecated since version 2.4.
HttpErrorHandler.onClientError should be used instead of
GlobalSettings.onHandlerNotFound. Basically create a class that inherits from HttpErrorHandler, and provide an implementation for onClientError method.
In order to find out type of error (404 in your case) you need to read status code, which is passed as a one of the method arguments e.g.
if(statusCode == play.mvc.Http.Status.NOT_FOUND) {
// your code to handle 'page not found' situation
// e.g. return custom implementation of 404 page
}
In order to let Play know what handler to use, you can place your error handler in the root package or configure it in application.conf using play.http.errorHandler configuration key e.g.
play.http.errorHandler = "my.library.MyErrorHandler"
You can find more details on handling errors here: for Scala or Java.
This works in 2.2.1. In Global.java:
public Promise<SimpleResult> onHandlerNotFound(RequestHeader request) {
return Promise.<SimpleResult>pure(notFound(
views.html.throw404.render()
));
}
Ensure that you have a view called /views/throw404.scala.html
This works in 2.2.3 Play - Java
public Promise<SimpleResult> onHandlerNotFound(RequestHeader request) {
return Promise<SimpleResult>pure(Results.notFound(views.html.notFound404.render()));
}
html should be within /views/notFound404.scala.html
Dont forget to add Results.notFounf() and import play.mvc.Results;
For Java, if you want to just redirect to main page, I solved it by this.
#Override
public Promise<Result> onHandlerNotFound(RequestHeader request) {
return Promise.pure(redirect("/"));
}