Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Allow select on a subfield of an embedded list.

This is accomplished with a subselect() method on
BsonRecordListQueryField.

I tried having one subfield method on BsonRecordListQueryField that
could work in both query and select contexts, but that required that the
method return a DummyField[List[V]], which in turn meant that all
queries against a subfield of an embedded list had to use list
predicates. That was broken conceptually since it didn't match up with
the actual mongo queries, so you could've both formulated incorrect
queries and not formulated correct ones. A simple example is that if you
had a list field of embedded objects, each with an int, before you
would've been able to do:

    (_.listField.subfield(_.intField) > 5)

... but with one combined subfield method, that wouldn't have been
possible anymore.

Instead, I made a subselect method for selection only. It's a little
weird, but it's not possible to pass the output of
BsonRecordListQueryField.subfield to select() anyway, so hopefully this
won't cause many problems.

Unfortunately, this means there's this inconsistency between
BsonRecordQueryField and BsonRecordListQueryField where you use subfield
for selection on one and subselect for selection on the other.
  • Loading branch information...
commit c95d1f398e1da6b6525ac36cbaf507945bd5410b 1 parent 5403c45
Neil Sanchala authored
17 src/main/scala/com/foursquare/rogue/Query.scala
View
@@ -10,6 +10,7 @@ import net.liftweb.common.{Box, Full}
import net.liftweb.mongodb.MongoDB
import net.liftweb.mongodb.record._
import net.liftweb.record.Field
+import org.bson.types.BasicBSONList
import scala.collection.mutable.ListBuffer
import scala.collection.immutable.ListMap
@@ -509,10 +510,18 @@ case class BaseQuery[M <: MongoRecord[M], R,
inst.fieldByName(field.name) match {
case Full(fld) => fld.setFromAny(dbo.get(field.name))
case _ => {
- // Subfield select
- Box !! field.name.split('.').toList.foldLeft(dbo: Object){
- case (obj, f) => obj.asInstanceOf[DBObject].get(f)
- }
+ val splitName = field.name.split('.').toList
+ Box.!!(splitName.foldLeft(dbo: Object)((obj: Object, fieldName: String) => {
+ obj match {
+ case dbl: BasicBSONList =>
+ (for {
+ index <- 0 to dbl.size - 1
+ val item: DBObject = dbl.get(index).asInstanceOf[DBObject]
+ } yield item.get(fieldName)).toList
+ case dbo: DBObject =>
+ dbo.get(fieldName)
+ }
+ }))
}
}
}
6 src/main/scala/com/foursquare/rogue/QueryField.scala
View
@@ -283,6 +283,11 @@ class BsonRecordListQueryField[M <: MongoRecord[M], B <: BsonRecord[B]](field: B
val rec = field.setFromJValue(JArray(JInt(0) :: Nil)).open_!.head // a gross hack to get at the embedded record
new DummyField[V, M](field.owner, field.name + "." + subfield(rec).name)
}
+
+ def subselect[V](subfield: B => Field[V, B]): SelectableDummyField[List[V], M] = {
+ val rec = field.setFromJValue(JArray(JInt(0) :: Nil)).open_!.head // a gross hack to get at the embedded record
+ new SelectableDummyField[List[V], M](field.owner, field.name + "." + subfield(rec).name)
+ }
}
class MapQueryField[V, M <: MongoRecord[M]](val field: Field[Map[String, V], M]) {
@@ -453,7 +458,6 @@ class OptionalSelectField[V, M <: MongoRecord[M]](override val field: Field[V, M
override def apply(v: Any): Any = v.asInstanceOf[Box[V]]
}
-
// ********************************************************************************
// *** Dummy field
// ********************************************************************************
13 src/test/scala/com/foursquare/rogue/EndToEndTest.scala
View
@@ -142,6 +142,9 @@ class EndToEndTest extends SpecsMatchers {
Tip.where(_._id eqs t.id).select(_.counts at "foo").fetch() must_== List(Full(1L))
Venue.where(_._id eqs v.id).select(_.geolatlng.unsafeField[Double]("lat")).fetch() must_== List(Full(40.73))
+
+ val subuserids: List[Box[List[Long]]] = Venue.where(_._id eqs v.id).select(_.claims.subselect(_.userid)).fetch()
+ subuserids must_== List(Full(List(1234, 5678)))
}
@Ignore("These tests are broken because DummyField doesn't know how to convert a String to an Enum")
@@ -158,5 +161,15 @@ class EndToEndTest extends SpecsMatchers {
statuses must_== List(Full("Approved"))
// This assertion is what we want, and it fails.
// statuses must_== List(Full(ClaimStatus.approved))
+
+ val subuseridsAndStatuses: List[(Box[List[Long]], Box[List[VenueClaimBson.status.MyType]])] =
+ Venue.where(_._id eqs v.id)
+ .select(_.claims.subselect(_.userid), _.claims.subselect(_.status))
+ .fetch()
+ // This assertion works.
+ subuseridsAndStatuses must_== List((Full(List(1234, 5678)), Full(List("Pending approval", "Approved"))))
+
+ // This assertion is what we want, and it fails.
+ // subuseridsAndStatuses must_== List((Full(List(1234, 5678)), Full(List(ClaimStatus.pending, ClaimStatus.approved))))
}
}
3  src/test/scala/com/foursquare/rogue/QueryTest.scala
View
@@ -114,7 +114,7 @@ class QueryTest extends SpecsMatchers {
// BsonRecordField subfield queries
Venue where (_.claims.subfield(_.status) eqs ClaimStatus.approved) toString() must_== """db.venues.find({ "claims.status" : "Approved"})"""
- Venue where (_.lastClaim.subfield(_.userid) eqs 123) toString() must_== """db.venues.find({ "lastClaim.uid" : 123})"""
+ Venue where (_.lastClaim.subfield(_.userid) eqs 123) toString() must_== """db.venues.find({ "lastClaim.uid" : 123})"""
// Enumeration list
OAuthConsumer where (_.privileges contains ConsumerPrivilege.awardBadges) toString() must_== """db.oauthconsumers.find({ "privileges" : "Award badges"})"""
@@ -160,6 +160,7 @@ class QueryTest extends SpecsMatchers {
Tip where (_.legacyid eqs 1) select (_.counts at "foo") toString() must_== """db.tips.find({ "legid" : 1}, { "counts.foo" : 1})"""
Venue where (_.legacyid eqs 1) select (_.geolatlng.unsafeField[Double]("lat")) toString() must_== """db.venues.find({ "legid" : 1}, { "latlng.lat" : 1})"""
Venue where (_.legacyid eqs 1) select (_.lastClaim.subfield(_.status)) toString() must_== """db.venues.find({ "legid" : 1}, { "lastClaim.status" : 1})"""
+ Venue where (_.legacyid eqs 1) select(_.claims.subselect(_.userid)) toString() must_== """db.venues.find({ "legid" : 1}, { "claims.uid" : 1})"""
// TODO: case class list fields
// Comment select(_.comments.unsafeField[Long]("userid")) toString() must_== """db.venues.find({ }, { "comments.userid" : 1})"""
Please sign in to comment.
Something went wrong with that request. Please try again.