Really confused about snippets - scala

I have a problem with my lift view. The thing is, I am making an expensive remote rest-api call twice - where I should really need to do it only once.
But I can't figure out how to solve this.
Basically I have an HTML template like this, that needs to display the list of users and their count:
//UserSearchResults.html
Num users: <span class="lift:UserSearchResults.userCount"></span>
User list:
<ul>
<lift:UserSearchResults.userList>
<li><user:userName/></li>
</lift:UserSearchResults.userList>
</ul>
And then I have an actual snippet that goes and retrieves the list of users from the rest-api server. However, note that it actually does this TWICE - once to count the number of users, and once to render the list.
//UserSearchResults.scala
/** Get list of users from api */
def users: List[User] = {
val url = "http://server/rest-api/user-search";
val result = io.Source.fromURL(url).mkString
//... parse users into List[User] and return it
return entries
}
/** Render user count */
def userCount =
"* *" #> users.length //<-- ONE call
def userList(in: NodeSeq): NodeSeq = {
users.flatMap(user => Helpers.bind("user", in, //<--SECOND call
"userName" -> user.user_name))
}
Is there a better place to put the api call? Is there like a "constructor" for the snippet, that I can use cache the user list, and to share it across all the functions in the class?
Any help is appreciated.

If UserSearchResults is a class (as opposed to an object), then there will be a per-request instance of that class. As such, all you have to do is change your def users to a lazy val users and you should be good to go.

If your snippet extends StatefulSnippet, you can just save the list in an instance variable. Another option would be to put the list into a RequestVar. Then it could also be accessed from other snippets.

Related

Scala Play Framework Editing a value and storing the new value

I have a need in my web application where I need to allow a user to update an existing item. However I want to know how I can store the original value so that it can be deleted and the new one used once the user has filled in the form.
Let me expand a bit:
Basically, I initially provide the user with a form to fill. After the form is filled in, I create an object from the properties of the form - things like age, name, height, etc are stored in an object, let's call it Person:
case class Person(age: Int, name: String, height: Int)
Now this information can be edited when the user clicks an edit button beside the item. So on the website, there is an option to edit the person.
My question is, how do I store the previous person, and once the user presses Update on the site, I am able to delete the original person object and replace it with the new one?
My current solution is to store the original object in the Session (as JSON), then when the form is updated, I read the JSON from the session to do more work.
I feel like this is not safe because (correct me if I'm wrong) it is possible to change the session data json from outside the app, thus allowing someone to change the person's name or something and I will not know this and then delete the wrong person from the list rather than the one I was going to update.
def editPerson(name: String) = Action { implicit request =>
Person.findByName(name).map { person =>
val form = personForm.fill(person)
Ok(views.html.persons.editPerson(form))
.addingToSession(("Edit", Json.prettyPrint(Json.toJson(person))))
} getOrElse NotFound
}
The above method is mapped to a route which allows one to specify a person to edit. When the Update button is pressed, the following controller method is called:
def save = Action { implicit request =>
val newPersonForm = personForm.bindFromRequest()
newPersonForm.fold(
hasErrors = { form =>
request.session.get("Edit").map { person =>
Redirect(routes.Persons.editPerson(person.name))
.flashing(Flash(form.data) + ("error" -> Messages("validation.errors")))
} getOrElse(BadRequest.removingFromSession("Edit"))
},
success = { newPerson =>
request.session.get("Edit").foreach { prevP =>
Person.remove(prevP) // delete the person that was edited
}
Person.add(newPerson) // add the new person
val message = Messages("persons.new.success", newPerson.name)
Redirect(routes.Persons.show(newPerson.name)).flashing("success" -> message).removingFromSession("Edit")
}
)
}
What the above save method does is that if the form was completed with errors, the person information is read once again from the session and we are redirected to the edit page once more.
If the form has no errors, we once again read the person information from the session and this time, it is deleted and the new person added.
What other methods can I use to ensure that the object is not exposed to the outside and remains within the controller until the user enters valid values in the form, at which point the object to edit is then deleted and the new one added?

Play Framework Search Bar

I'm trying to make a search bar with only one variable - the search input. I'm sure there's a fairly simple way to do this, but everything that I've found about getting input from the DOM (the views file) has been about using a Form and getting multiple variables. Is there a simpler way to do this if it's just a single variable?
I have a function in my Applications
def singleElement = Action { implicit request =>
val databaseSupport = new InteractWithDatabase(comm, db)
val put = Future {
while (true) {
val data = databaseSupport.getFromDatabase()
if (data.nonEmpty) {
comm.communicator ! data.head
}
}
}
Ok(views.html.singleElement)
}
I want to take some input from the user on the page singleElement and pass it into getFromDatabase which calls a MySQL query. How do I do this?
You can use restful and do something like this
routs file
GET /content/search/:search controllers.ContentController.search(search:String)
and in controller:
public Result search(String saerch) {}

Have a List in Play framework web service parameters

I have written this web service in play framework.
controller
def getByGenre(genre: String) = Action {
val result = Await.result(Movies.getByGenre(genre), 5 seconds)
Ok(toJson(result))
}
routes
GET /movies/genre/:genre controllers.MoviesController.getByGenre(genre: String)
However a user may select multiple Genre. Therefore I need to convert the genre parameter to a List[String]
I also need to know how to pass that Array parameter to the web service using CURL.
If you can pass the genres parameter as part of the query string, just repeat the parameter with different values and then retrieve it like this:
def getByGenre() = Action.async { implicit request =>
val genres = request.queryString.get("genres")
Movies.getByGenre(genres).map { movies =>
Ok(toJson(movies))
}
}
Your route will be:
GET /movies/genre controllers.MoviesController.getByGenre()
Also, notice that you will need to change the Movies.getByGenre signature to:
def getByGenre(genres: Option[Seq[String]]): Seq[Movies]
An final url will be something like #mfirry showed:
myhost.com/movies/genre?genre=action&genre=drama
Finally, as you may have noticed, I've removed the blocking code from you action. Using Await at your controller means that you action would be blocking for at least 5 seconds at the worst case scenario. I suggest you to take a look at the following page of Play docs:
https://www.playframework.com/documentation/2.5.x/ScalaAsync

How to execute tests on the argument that a controller passes to the view in Play Framework

In our play application every controller function fetches data from the database (or some other way) and passes these values to the result
def index = Action { implicit request =>
val newsItems: List[String] = fetchNewsFromDB()
Ok(views.html.home.index(newsItems))
}
def fetchNewsFromDB() = List("Headline1", "Headline2")
I am writing tests using specifiactions (based on the documentation http://www.playframework.com/documentation/2.2.x/ScalaTest)
According to this documentation by controller as follows. In the next test I want to make sure that the index page contains a headline. I do this by checking if there exists a div with the class "headline"
"Example Page#index" should {
"should contain a headline" in {
val controller = new TestController()
val result: Future[SimpleResult] = controller.index().apply(FakeRequest())
val bodyText: String = contentAsString(result)
bodyText.toLowerCase must contain("<div class=\"headline\"")
}
}
However I would rather check whether the list newsItems which the controller passes to the view is nonempty.
What is the best way to do this?
Is it possible to this in a generic way for which little modification of the controllers is required?
I too was frustrated that I couldn't intercept the parameters on their way to the template - and in fact it can become extremely difficult to even get the template to render at all in tests if you have a lot of "state" in your pages (for example, implicits that provide the user object, navigation helpers etc).
What I ended up doing was putting in an extra "seam" for testability in my controllers; in my tests, I extend the controller under test, replacing the HTML rendering function with a mocked one, which I can then use to verify the parameters.
Here's a simple example based on your "news" Action; first, the controller, which is no longer an object so we can extend it:
object Application extends ApplicationController
trait ApplicationController extends Controller {
def newsAction = Action {
Ok(renderNews("this is the news"))
}
def renderNews(s:List[String]):Html = html.sandbox(s)
}
The renderNews method gives us the all-important "test seam". I think it also actually improves the readability of controller methods too, which is nice :-)
Now, the unit test:
class ApplicationSpec extends Specification with Mockito {
val mockedNewsRenderer = mock[List[String] => Html]
val controller = new ApplicationController {
override def renderNews(s:List[String]) = mockedNewsRenderer(s)
}
"Application News Controller" should {
"Pass a non-empty list of news items to the template" in {
val result = controller.newsAction(FakeRequest())
status(result) must beEqualTo(200)
val captor = ArgumentCaptor.forClass(classOf[List[String]])
there was one(mockedNewsRenderer).apply(captor.capture())
val theArgument = captor.getValue
theArgument.isEmpty must beFalse
}
}
}
We create a mock to stand-in for the renderNews function, extend the controller so that we can substitute it in (note that we don't change anything else about it of course), and then call the action as normal. Note that we still get a standard Play Result so we can still check status codes etc, but then, we can use the Mockito verify functionality that's built into Specs2, together with Mockito's ArgumentCaptor facility to assert that our template was indeed called, and that it was supplied with a non-empty list of strings.
This approach has worked well for me - it makes it possible to get really good code coverage of your controllers with fast-running and easy-to-write unit tests.
You have a very good question and a very valid point on testing controllers, but I'm afraid it can't be done easily. The problem is that the views compile to Scala functions meaning when you call views.html.home.index(newsItems) it will return an object of Html, which already has the Html put together and compiled. If you would like to test what get's passed in you need to intercept it before the view is called.
To solve this you would have to rewrite your controllers, by moving all your business logic out of the controller and only have the necessary request handling code there. That would almost be easier to test.

Cannot access the parameter of a Menu.param from a Lift Snippet

I'm trying to extract the parameter from a Lift Menu.param within a snippet so that I can use it to create a named Comet. However, I get a NullPointerException when I try to pass the parameter to the snippet using SnippetDisptach in my Boot.scala, as suggested here:
http://comments.gmane.org/gmane.comp.web.lift/44299
I've created the Menu item as follows:
object AnItemPage {
// create a parameterized page
def menu = Menu.param[Item]("Item", "Item",
s => fetchItem(s), item => item._id.toString) / "item"
private def fetchItem(s:String) : Box[Item] = synchronized {
ItemDAO.findById(ObjectId.massageToObjectId(s))
}
}
I've added the menu to SiteMap. I've also created a Snippet which I would like to pick up the Item parameter. (I'm using fmpwizard's InsertNamedComet library here):
class AddCometItemPage(boxedItem: Box[Item]) extends InsertNamedComet with DispatchSnippet{
val item : Item = boxedItem.openOr(null)
override lazy val name= "comet_item_" + item._id.toString
override lazy val cometClass= "UserItemCometActor"
def dispatch = null
}
My next step is to crate an instance of this class as demonstrated by David Pollak here:
http://comments.gmane.org/gmane.comp.web.lift/44299
This is what I have added to my Boot.scala:
LiftRules.snippetDispatch.append {
case "item_page" => new AddCometItemPage(AnItemPage.menu.currentValue)
}
My item.html references this snippet:
<div class="lift:item_page">
I get the following null pointer exception when I compile and run this:
Exception occurred while processing /item/5114eb4044ae953cf863b786
Message: java.lang.NullPointerException
net.liftweb.sitemap.Loc$class.siteMap(Loc.scala:147)
net.liftweb.sitemap.Menu$ParamMenuable$$anon$9.siteMap(Menu.scala:170)
net.liftweb.sitemap.Loc$class.allParams(Loc.scala:123)
net.liftweb.sitemap.Menu$ParamMenuable$$anon$9.allParams(Menu.scala:170)
net.liftweb.sitemap.Loc$class.net$liftweb$sitemap$Loc$$staticValue(Loc.scala:87)
net.liftweb.sitemap.Menu$ParamMenuable$$anon$9.net$liftweb$sitemap$Loc$$staticValue(Menu.scala:170)
net.liftweb.sitemap.Loc$$anonfun$paramValue$2.apply(Loc.scala:85)
net.liftweb.sitemap.Loc$$anonfun$paramValue$2.apply(Loc.scala:85)
net.liftweb.common.EmptyBox.or(Box.scala:646)
net.liftweb.sitemap.Loc$class.paramValue(Loc.scala:85)
net.liftweb.sitemap.Menu$ParamMenuable$$anon$9.paramValue(Menu.scala:170)
net.liftweb.sitemap.Loc$$anonfun$currentValue$3.apply(Loc.scala:114)
net.liftweb.sitemap.Loc$$anonfun$currentValue$3.apply(Loc.scala:114)
net.liftweb.common.EmptyBox.or(Box.scala:646)
net.liftweb.sitemap.Loc$class.currentValue(Loc.scala:114)
net.liftweb.sitemap.Menu$ParamMenuable$$anon$9.currentValue(Menu.scala:170)
bootstrap.liftweb.Boot$$anonfun$lift$8.apply(Boot.scala:107)
bootstrap.liftweb.Boot$$anonfun$lift$8.apply(Boot.scala:106)
net.liftweb.util.NamedPF$$anonfun$applyBox$1.apply(NamedPartialFunction.scala:97)
net.liftweb.util.NamedPF$$anonfun$applyBox$1.apply(NamedPartialFunction.scala:97)
net.liftweb.common.Full.map(Box.scala:553)
net.liftweb.util.NamedPF$.applyBox(NamedPartialFunction.scala:97)
net.liftweb.http.LiftRules.snippet(LiftRules.scala:711)
net.liftweb.http.LiftSession$$anonfun$net$liftweb$http$LiftSession$$findSnippetInstance$1.apply(LiftSession.scala:1506)
net.liftweb.http.LiftSession$$anonfun$net$liftweb$http$LiftSession$$findSnippetInstance$1.apply(LiftSession.scala:1506)
net.liftweb.common.EmptyBox.or(Box.scala:646)
net.liftweb.http.LiftSession.net$liftweb$http$LiftSession$$findSnippetInstance(LiftSession.scala:1505)
net.liftweb.http.LiftSession$$anonfun$locateAndCacheSnippet$1$1$$anonfun$apply$88.apply(LiftSession.scala:1670)
net.liftweb.http.LiftSession$$anonfun$locateAndCacheSnippet$1$1$$anonfun$apply$88.apply(LiftSession.scala:1669)
Has anybody any idea where I'm going wrong? I've not been able to find a lot of information on Menu.param.
Thank you very much for your help.
f
I have never tried what you are doing, so I am not sure the best way to accomplish it. The way you are using the Loc Param, you are extracting a variable from a URL pattern. In your case, http://server/item/ITEMID where ITEMID is the string representation of an Item, and which is the value that gets passed to the fetchItem function. The function call will not have a value if you just arbitrarily call it, and from what I can see you are requesting a value that is not initialized.
I would think there are two possible solutions. The first would be to use S.location instead of AnItemPage.menu.currentValue. It will return a Box[Loc[Any]] representing the Loc that is currently being accessed (with the parameters set). You can use that Loc to retrive currentValue and set your parameter.
The other option would be to instantiate the actor in your snippet. Something like this:
item.html
<div data-lift="AnItemPage">
<div id="mycomet"></div>
</div>
And then in your AnItemPage snippet, something like this:
class AnItemPage(item: Item) {
def render = "#mycomet" #> new AddCometItemPage(item).render
}
I haven't tested either of those, so they'll probably need some tweaking. Hopefully it will give you a general idea.