Вопрос

I'm new to scala and can't get my head around how the Lift guys implemented the Record API. However, the question is less about this API but more about Scala in general. I'm interested in how the object in class pattern works, used in Lift.

class MainDoc private() extends MongoRecord[MainDoc] with ObjectIdPk[MainDoc] {
  def meta = MainDoc

  object name extends StringField(this, 12)
  object cnt extends IntField(this)
}

object MainDoc extends MainDoc with MongoMetaRecord[MainDoc]

In the upper snippet you can see how a record is defined in Lift. The interesting part is that the fields are defined as objects. The API allows you to create Instances like this:

val md1 = MainDoc.createRecord
  .name("md1")
  .cnt(5)
  .save

This is probably done by using the apply method? But at the same time you are able to get the values by doing something like this:

val name = md1.name

How does this all work? Are the objects not that static when in scope of an class. Or are they just constructor classes for some internal representation? How is it possible to iterate over all fields, do you use Reflection?

Thanks, Otto

Это было полезно?

Решение

You're right about it being the apply method. Record's Field base class defines a few apply methods.

def apply(in: Box[MyType]): OwnerType
def apply(in: MyType): OwnerType

By returning the OwnerType, you can chain invocations together.

Regarding the use of object to define fields, that confused me at first, too. The object identifier defines an object within a particular scope. Even though it's convenient to think of object as a shortcut for the singleton pattern, it's more flexible than that. According to the Scala Language Spec (section 5.4):

It is roughly equivalent to the following definition of a lazy value:
lazy val m = new sc with mt1 with ... with mtn { this: m.type => stats }

<snip/>

The expansion given above is not accurate for top-level objects. It cannot be because variable and method definition cannot appear on the top-level outside of a package object (§9.3). Instead, top-level objects are translated to static fields.

Regarding iterating over all the fields, Record objects define a allFields method which returns a List[net.liftweb.record.Field[_, MyType]].

Другие советы

Otto,

You are more of less on the right track. You actually don't need to define your fields as objects, you could have written your example as

class MainDoc private() extends MongoRecord[MainDoc] with ObjectIdPk[MainDoc] {
  def meta = MainDoc

  val name = new StringField(this, 12)
  val cnt= new IntField(this)
}

object MainDoc extends MainDoc with MongoMetaRecord[MainDoc]

The net.liftweb.record.Field trait does contain an apply method that is the equivalent to set. That's why you can assign the fields by name after instantiating the object.

The field reference you mentioned:

val name = md1.name

Would type name as a StringField. If what you were thinking was

val name: String = md1.name

that would fail to compile (unless there was an implicit in scope to convert Field[T] => T). The proper way retrieve the String value of the field would be

val name = md1.name.get

Record does use reflection to gather the fields. When you define an object within a class, the compiler will create a field to hold the object instance. From the standpoint of reflection, the object appears very similar to the alternate way to define a field that I mentioned before. Each of the definitions probably creates a subclass of the field type, but that's no different than

val name = new StringField(this, 12) {
  override def label: NodeSeq = <span>My String Field</span>
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top