How to pass a Casbah result to view in Play Framework? - mongodb

Basically what I want to achieve is simply to execute a MongoDB query using Casbah in a Play Framework 2.2 controller, pass the result(s) to a view and present the result(s) in HTML.
I think my main problem is, I don't know how to define the parameters of the view, because the view doesn't know any of the result types I tried and a #import in the line below doesn't seem to help either.
Controller action:
def read = Action {
val mongoClient = MongoClient("localhost", 27017)
val db = mongoClient("sampleapp")
val coll = db("testcoll1")
// Query: get all documents
val docs = coll.find() // Type: coll.CursorType
val list = docs.toList
Ok(views.html.casbahsamples.read(list))
}
As you can see, I try to convert the result to a List, because docs seems to be of type coll.CursorType where coll is the name of my value and when it's passed to the view I don't have a clue how to use this in the parameter line because the compiler asks for type coll.CursorType but doesn't even know about coll.
Anyway, if I declare the type List[Any] for my list parameter in the view, this will partly work, but I won't be able to access any properties of the result documents, because none of the methods work on objects of type Any.
I think, the best way would be to declare List[BasicDBObject] in the view's parameter line, but BasicDBObject is not a known type. So I tried to import it in the line below (as far as I know that's the only place in a view where imports are allowed). But this doesn't change anything.
View:
#(list: List[BasicDBObject])
#import com.mongodb.BasicDBObject
#* also tried:
#import com.mongodb.casbah.Imports._
*#
<h2>Query results</h2>
<h3>Number of query results:</h3>
<p>#list.length</p>
<h3>Results:</h3>
<code>#list</code><!-- this will be a JSON string representation, but that's not what I want -->
<h4>List:</h4>
#import java.math.BigInteger; var i = 0;
#for(doc <- list) {
#{i += 1; i}:<br>
#*
doc is of type BasicDBObject as I found out doing this:
#doc.asInstanceOf[AnyRef].getClass.getSimpleName
*#
#doc
#* The next line results in a compilation error: value filter is not a member of Any *#
#defining(doc.filter(_.isInstanceOf[BasicDBObject]).map(_.asInstanceOf[BasicDBObject])) { docX =>
#docX.asInstanceOf[AnyRef].getClass.getSimpleName
}
<br><br>
<dl style="background: #ccc">
<dt>_id</dt>
#* doc will be of type BasicDBObject *#
#* <dd>#doc.getString("pie")</dd> *#
#* <dd>#docX.getString("pie")</dd> *#
</dl>
<br><br>
}
My attempt to convert Any to BasicDBObject does not work, because the compiler says filter and map are not members of Any.
What do I have to do to use/access/display the results of my query in a view?
(BTW: The full code is on GitHub)

The answer to this is so simple, I can't believe I didn't have this idea earlier...
I just had to add the full package name in the first line of the view...
#(list: List[com.mongodb.casbah.Imports.DBObject])
..and then I don't even have to import the package. I can now display each attribute value of a document separately with e.g. #doc.get("_id").
Should anybody be interested, here's the full view file.

Related

Hibernate Search 6 Scrollable returning results in a strange way

Since we upgraded from Hibernate Search 5.11 to Hibernate Search 6 we are having problems with Scrollable results
When we get a chunk of hits from the SearchScroll object each hit is stored in an Arrays.ArrayList
What we expected is that each chunk hits would be an ArrayList of say for example of type long
What we get is an ArrayList where where each hit is an Arrays.ArrayList with the Long value
Current code
SearchScroll scroll = searchSession
.search(scope)
.select(projectionArray)
.where(searchPredicate)
.sort(getSort(resultType))
.scroll(20);
Old code with Hibernate Search 5
FullTextQuery fullTextQuery = fullTextSession
.createFullTextQuery(query, resultType)
.setSort(getSort(resultType));
fullTextQuery.setProjection(fields);
ScrollableResults scrollableResults = fullTextQuery.scroll();
Any suggestions welcome
At worst we can loop through the results and convert the Arrays.ArrayList item to a long but cannot find a way to make that work either
The acual search results are correct just coming back in a different format that what we expect
Changing the code to
SearchScroll<Long> scroll = searchSession
.search(scope)
.select(projectionArray)
.where(searchPredicate)
.sort(getSort(resultType))
.scroll(20);
Makes no difference which seems to match the example in the docs
try ( SearchScroll<Book> scroll = searchSession.search(
Book.class )
.where( f -> f.matchAll() )
.scroll( 20 ) ) {
for ( SearchScrollResult<Book> chunk = scroll.next();
chunk.hasHits(); chunk = scroll.next() ) {
for ( Book hit : chunk.hits() ) {
// ... do something with the hits ...
}
totalHitCount = chunk.total().hitCount();
entityManager.flush();
entityManager.clear();
}
}
Not sure if the projection is what is causing the problem
Tested further if I remove the projection I get the results as an ArrayList of the object as expected so obviously I am doing something wrong with the use of projections in Hibernate Search 6
Without projection everything is good
With projection the results are Arrays.ArrayList
If I understand correctly, you are surprised that you get a List for each hit instead of just a Long.
First, I would recommend that you don't ignore raw type warnings.
I'll wager that your projectionArray is defined this way:
SearchProjection[] projectionArray = new SearchProjection[1];
That's wrong because you're using a "raw" type for SearchProjection, which basically disables all kinds of type-checking for all the code afterwards.
The correct way of defining that array is as follows:
SearchProjection<?>[] projectionArray = new SearchProjection<?>[1];
If you do that, then you'll get a compile-time error with the following code, telling you something like "cannot convert SearchScroll<List<?>> to SearchScroll<Long>":
SearchScroll<Long> scroll = searchSession
.search(scope)
.select(projectionArray)
.where(searchPredicate)
.sort(getSort(resultType))
.scroll(20);
Now, the reason you're getting a SearchScroll<List<?>> is you're passing an array of projections to .select(), so you're calling this method from SearchQuerySelectStep:
SearchQueryWhereStep<?, List<?>, LOS, ?> select(SearchProjection<?>... projections);
This method takes an array of projections as an argument, and (ultimately) returns a query whose hits are lists, with the results of requested projections in the same order as your array of projections.
You want to call that method instead:
<P> SearchQueryWhereStep<?, P, LOS, ?> select(SearchProjection<P> projection);
That method takes a single projection as an argument, and (ultimately) returns a query whose hits are directly the result of the requested projection.
To call that method, pass a single projection instead of an array of projections; then you will get the Long values you expect instead of Lists:
SearchProjection<Long> projection = ...;
SearchScroll<Long> scroll = searchSession
.search(scope)
.select(projection)
.where(searchPredicate)
.sort(getSort(resultType))
.scroll(20);

Wrong xpath for nav html tag

I m trying to find element using xpath for tag
<nav id="nav">... </nav>
this works:
WebElement navigationPane = firefox.findElement(By.className("nav"));
but this does not:
WebElement navigationPane =
firefox.findElement(By.xpath("//nav[#id='nav')]"));
How do I make it valid expression in xpath?
In the xpath expression, you've meant to use #class instead of #id. And, there is an extra parenthesis inside. Here is the fixed version:
//nav[#class = 'nav']
After finding an HTML block in your question (made it visible with an edit), I've realized that there is an id attribute set on the element, not class. In this case, you should use the following expression:
//nav[#id = 'nav']
Note that by.id would be an easier and faster way to find the element:
WebElement navigationPane = firefox.findElement(By.id("nav"));

Lift 2.6: How to append a NodeSeq rather than using SetHtml()

I use SetHtml() all the time to swap chunks of HTML as part of the return trip of an ajax call, ie. when my "Edit Item" ajax call returns, I swap the contents of with form elements representing the "Item" to be edited.
Now I'm trying to add a new item to a list (so that "Item 2" appears as the next li under "Item 1"). My html looks like (this is a greatly simplified version) this:
<div data-lift="form.ajax">
<div data-lift="JsCmdTest">
<test:submitbutton></test:submitbutton>
<ul id="targetdiv">
<li>Item 1</li>
</ul>
</div>
</div>
and my Lift code looks like this
class JsCmdTest extends StatefulSnippet
{
def dispatch = { case "render" => render }
def bindStuff(ns: NodeSeq): NodeSeq =
{
bind("test", ns,
"submitbutton" -> SHtml.ajaxButton("Go", ()=> {
val newLi = Jx(<div>Item 2</div>)
(ElemById("targetdiv") ~> JsFunc("appendChild", newLi.toJs)).cmd
})
)
}
def render: (NodeSeq)=>NodeSeq = (ns: NodeSeq) => {
bindStuff(ns)
}
}
When I run this, the ajax call is fired, and the response looks fine, but I get the following error in the browser console:
The server call succeeded, but the returned Javascript contains an error: NotFoundError: Failed to execute 'appendChild' on 'Node': The new child element is null.
WANTED: a way to append replacement HTML to an existing < ul >. I feel like the way I'm going about appending seems way more esoteric than probably needed, but i haven't found any other way to do it.
Thanks in advance!
you're using a very old syntax for binding. If I am not mistaken, a new way for bindings was introduced somewhere in lift-2, and it is the recommended one since somewhere in lift-2.2. (I'll write the syntax below.)
JsFunc is an anonymous function, like def local(a: A): B. You don't need to send anonymous function, you can send the code directly. (See below.)
So, I recommend something like this:
import net.liftweb.http.js.{JsExp, JsCmd, JsCmds}
import net.liftweb.http.js.jquery.JqJE.{JqAppend, JqId}
def render = {
val appendJs: JsExp = JqId("targetdiv") ~> JqAppend(newLi)
"#mySubmitButton" #> SHtml.ajaxButton("Go", () => appendJs.cmd)
}
You'll also have to adapt the HTML a little, like using a normal <button> with id="mySubmitButton".

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.

Django-Nonrel with Mongodb listfield

I am trying to implement manytomany field relation in django-nonrel on mongodb. It was suggessted at to:
Django-nonrel form field for ListField
Following the accepted answer
models.py
class MyClass(models.Model):
field = ListField(models.ForeignKey(AnotherClass))
i am not sure where the following goes, it has been tested in fields.py, widgets,py, models.py
class ModelListField(ListField):
def formfield(self, **kwargs):
return FormListField(**kwargs)
class ListFieldWidget(SelectMultiple):
pass
class FormListField(MultipleChoiceField):
"""
This is a custom form field that can display a ModelListField as a Multiple Select GUI element.
"""
widget = ListFieldWidget
def clean(self, value):
#TODO: clean your data in whatever way is correct in your case and return cleaned data instead of just the value
return value
admin.py
class MyClassAdmin(admin.ModelAdmin):
form = MyClassForm
def __init__(self, model, admin_site):
super(MyClassAdmin,self).__init__(model, admin_site)
admin.site.register(MyClass, MyClassAdmin)
The following Errors keep popping up:
If the middle custom class code is used in models.py
name 'SelectMultiple' is not defined
If custom class code is taken off models.py:
No form field implemented for <class 'djangotoolbox.fields.ListField'>
You just need to import SelectMultiple by the sound of it. You can put the code in any of those three files, fields.py would make sense.
Since it's pretty usual to have:
from django import forms
at the top of your file already, you probably just want to edit the code below to:
# you'll have to work out how to import the Mongo ListField for yourself :)
class ModelListField(ListField):
def formfield(self, **kwargs):
return FormListField(**kwargs)
class ListFieldWidget(forms.SelectMultiple):
pass
class FormListField(forms.MultipleChoiceField):
"""
This is a custom form field that can display a ModelListField as a Multiple Select GUI element.
"""
widget = ListFieldWidget
def clean(self, value):
#TODO: clean your data in whatever way is correct in your case and return cleaned data instead of just the value
return value
You probably also want to try and learn a bit more about how python works, how to import modules etc.