Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
branch: master
Fetching contributors…

Cannot retrieve contributors at this time

8972 lines (6431 sloc) 158.495 kB
#LyX 2.0 created this file. For more info see http://www.lyx.org/
\lyxformat 413
\begin_document
\begin_header
\textclass book
\begin_preamble
% "define" Scala
\lstdefinelanguage{scala}{morekeywords={class,object,trait,extends,with,new,if,while,for,def,val,var},sensitive=true,morecomment=[l]{//},
morecomment=[s]{/*}{*/},
morestring=[b]"}
% Default settings for code listings
\lstset{frame=single,frameround=tttt,language=scala}
\end_preamble
\use_default_options false
\maintain_unincluded_children false
\language english
\language_package default
\inputencoding auto
\fontencoding global
\font_roman default
\font_sans default
\font_typewriter default
\font_default_family default
\use_non_tex_fonts false
\font_sc false
\font_osf false
\font_sf_scale 100
\font_tt_scale 100
\graphics default
\default_output_format default
\output_sync 0
\bibtex_command default
\index_command default
\paperfontsize default
\spacing single
\use_hyperref false
\papersize default
\use_geometry false
\use_amsmath 1
\use_esint 1
\use_mhchem 1
\use_mathdots 1
\cite_engine basic
\use_bibtopic false
\use_indices false
\paperorientation portrait
\suppress_date false
\use_refstyle 0
\index Index
\shortcut idx
\color #008000
\end_index
\secnumdepth 3
\tocdepth 3
\paragraph_separation indent
\paragraph_indentation default
\quotes_language english
\papercolumns 1
\papersides 1
\paperpagestyle default
\tracking_changes false
\output_changes false
\html_math_output 0
\html_css_as_file 0
\html_be_strict false
\end_header
\begin_body
\begin_layout Chapter
The Mapper and Record Frameworks
\begin_inset CommandInset label
LatexCommand label
name "cha:mapper_and_record"
\end_inset
\end_layout
\begin_layout Standard
In our experience, most webapps end up needing to store user data somewhere.
Once you start working with user data, though, you start dealing with issues
like coding up input forms, validation
\begin_inset Index idx
status collapsed
\begin_layout Plain Layout
validation
\end_layout
\end_inset
, persistence, etc.
to handle the data.
That's where the Mapper and Record frameworks come in.
These frameworks provides a scaffolding for all of your data manipulation
needs.
Mapper is the original Lift persistence framework, and it is closely tied
to JDBC for its storage.
Record is a new refactorization of Mapper that is backing-store agnostic
at its core, so it doesn't matter whether you want to save your data to
JDBC, JPA, or even something such as XML.
With Record, selecting the proper driver will be as simple as hooking the
proper traits into your class.
\end_layout
\begin_layout Standard
\begin_inset Box Framed
position "t"
hor_pos "c"
has_inner_box 0
inner_pos "t"
use_parbox 0
use_makebox 0
width "100col%"
special "none"
height "1in"
height_special "totalheight"
status open
\begin_layout Plain Layout
The Record framework is relatively new to Lift.
\strikeout on
The plan is to move to Record as the primary ORM framework for Lift sometime
post-1.0.
\strikeout default
Because Record is still under active design and development, and because
of its current
\begin_inset Quotes eld
\end_inset
moving target
\begin_inset Quotes erd
\end_inset
status, this chapter is mostly going to focus on Mapper.
We will, however, provide a few comparitive examples of Record functionality
to give you a general feel for the flavor of the changes.
In any case, Mapper will not go away even when record comes out, so you
can feel secure that any code using Mapper will be viable for quite a while.
\end_layout
\end_inset
\end_layout
\begin_layout Section
Introduction to Mapper and MetaMapper
\end_layout
\begin_layout Standard
Let's start by discussing the relationship between the Mapper
\begin_inset Index idx
status collapsed
\begin_layout Plain Layout
Mapper
\end_layout
\end_inset
and MetaMapper
\begin_inset Index idx
status collapsed
\begin_layout Plain Layout
MetaMapper
\end_layout
\end_inset
traits (and the corresponding Record
\begin_inset Index idx
status collapsed
\begin_layout Plain Layout
Record
\end_layout
\end_inset
and MetaRecord
\begin_inset Index idx
status collapsed
\begin_layout Plain Layout
MetaRecord
\end_layout
\end_inset
).
Mapper provides the
\emph on
per-instance
\emph default
functionality for your class, while MetaMapper handles the
\emph on
global
\emph default
operations for your class and provides a common location to define per-class
static specializations of things like field order, form generation, and
HTML representation.
In fact, many of the Mapper methods actually delegate to methods on MetaMapper.
In addition to Mapper and MetaMapper, there is a third trait, MappedField,
that provides the per-field functionality for your class.
In Record, the trait is simply called
\begin_inset Quotes eld
\end_inset
Field
\begin_inset Quotes erd
\end_inset
.
The MappedField trait lets you define the individual validators as well
as filters to transform the data and the field name.
Under Record, Field adds some functionality such as tab order and default
error messages for form input handling.
\end_layout
\begin_layout Subsection
Adding Mapper to Your Project
\end_layout
\begin_layout Standard
Since Mapper is a separate module, you need to add the following dependency
to your pom.xml to access it:
\end_layout
\begin_layout Standard
\begin_inset listings
lstparams "language=XML"
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Mapper POM Dependency
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
<project ...>
\end_layout
\begin_layout Plain Layout
...
\end_layout
\begin_layout Plain Layout
<dependencies>
\end_layout
\begin_layout Plain Layout
...
\end_layout
\begin_layout Plain Layout
<dependency>
\end_layout
\begin_layout Plain Layout
<groupId>net.liftweb</groupId>
\end_layout
\begin_layout Plain Layout
<artifactId>lift-mapper</artifactId>
\end_layout
\begin_layout Plain Layout
<version>1.0</version> <!-- or 1.1-SNAPSHOT, etc -->
\end_layout
\begin_layout Plain Layout
</dependency>
\end_layout
\begin_layout Plain Layout
</dependencies>
\end_layout
\begin_layout Plain Layout
...
\end_layout
\begin_layout Plain Layout
</project>
\end_layout
\end_inset
\end_layout
\begin_layout Standard
You'll also need the following import in any Scala code that uses Mapper:
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Mapper Imports
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
import _root_.net.liftweb.mapper._
\end_layout
\end_inset
\end_layout
\begin_layout Subsection
Setting Up the Database Connection
\end_layout
\begin_layout Standard
The first thing you need to do is to define the database connection.
We do this by defining an object called
\family typewriter
DBVendor
\family default
(but you can call it whatever you want).
This object extends the net.liftweb.mapper.ConnectionManager trait and must
implement two methods: newConnection and releaseConnection.
You can make this as sophisticated as you want, with pooling, caching,
etc., but for now, Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Setting-up-the-database"
\end_inset
shows a basic implementation to set up a PostgreSQL driver.
\end_layout
\begin_layout Standard
\begin_inset listings
lstparams "breaklines=true"
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Setting Up the Database
\begin_inset CommandInset label
LatexCommand label
name "lst:Setting-up-the-database"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
..
standard Lift imports ...
\end_layout
\begin_layout Plain Layout
import _root_.net.liftweb.mapper._
\end_layout
\begin_layout Plain Layout
import _root_.java.sql._
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
object DBVendor extends ConnectionManager {
\end_layout
\begin_layout Plain Layout
// Force load the driver
\end_layout
\begin_layout Plain Layout
Class.forName("org.postgresql.Driver")
\end_layout
\begin_layout Plain Layout
// define methods
\end_layout
\begin_layout Plain Layout
def newConnection(name : ConnectionIdentifier) = {
\end_layout
\begin_layout Plain Layout
try {
\end_layout
\begin_layout Plain Layout
Full(DriverManager.getConnection(
\end_layout
\begin_layout Plain Layout
"jdbc:postgresql://localhost/mydatabase",
\end_layout
\begin_layout Plain Layout
"root", "secret"))
\end_layout
\begin_layout Plain Layout
} catch {
\end_layout
\begin_layout Plain Layout
case e : Exception => e.printStackTrace; Empty
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
def releaseConnection (conn : Connection) { conn.close }
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
class Boot {
\end_layout
\begin_layout Plain Layout
def boot {
\end_layout
\begin_layout Plain Layout
...
\end_layout
\begin_layout Plain Layout
DB.defineConnectionManager(DefaultConnectionIdentifier, DBVendor)
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
A few items to note:
\end_layout
\begin_layout Enumerate
The
\family typewriter
name
\family default
parameter for newConnection can be used if you need to have connections
to multiple distinct databases.
One specialized case of this is when you're doing DB sharding (horizontal
scaling).
Multiple database usage is covered in more depth in Section
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:Multiple-Databases"
\end_inset
\end_layout
\begin_layout Enumerate
The newConnection method needs to return a
\family typewriter
Box[java.sql.Connection]
\family default
.
Returning
\family typewriter
Empty
\family default
indicates failure
\end_layout
\begin_layout Enumerate
The releaseConnection method exists so that you have complete control over
the lifecycle of the connection.
For instance, if you were doing connection pooling yourself you would return
the connection to the available pool rather than closing it
\end_layout
\begin_layout Enumerate
The
\family typewriter
DB.defineConnectionManager
\family default
call is what binds our manager into Mapper.
Without it your manager will never get called
\end_layout
\begin_layout Subsection
Constructing a Mapper-enabled Class
\begin_inset CommandInset label
LatexCommand label
name "sub:Constructing-a-Mapper-enabled-class"
\end_inset
\end_layout
\begin_layout Standard
Now that we've covered some basic background, we can start constructing
some Mapper classes to get more familiar with the framework.
We'll start with a simple example of a class for an expense transaction
from our PocketChange application with the following fields:
\end_layout
\begin_layout Itemize
Date
\end_layout
\begin_layout Itemize
Description: a string with a max length of 100 chars
\end_layout
\begin_layout Itemize
Amount: a decimal value with a precision of 16 digits and two decimal places
\end_layout
\begin_layout Itemize
A reference to the Account that owns the transaction
\end_layout
\begin_layout Standard
Given these requirements we can declare our
\family typewriter
Expense
\family default
class as shown in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Entry-class-mapper"
\end_inset
.
\end_layout
\begin_layout Standard
\begin_inset listings
lstparams "showstringspaces=false"
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Expense Class in Mapper
\begin_inset CommandInset label
LatexCommand label
name "lst:Entry-class-mapper"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
import _root_.java.math.MathContext
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
class Expense extends LongKeyedMapper[Expense] with IdPK {
\end_layout
\begin_layout Plain Layout
def getSingleton = Expense
\end_layout
\begin_layout Plain Layout
object dateOf extends MappedDateTime(this)
\end_layout
\begin_layout Plain Layout
object description extends MappedString(this,100)
\end_layout
\begin_layout Plain Layout
object amount extends MappedDecimal(this, MathContext.DECIMAL64, 2)
\end_layout
\begin_layout Plain Layout
object account extends MappedLongForeignKey(this, Account)
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
For comparison, the Record version is shown in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Entry-class-record"
\end_inset
.
This example already shows some functionality that hasn't been ported over
to Record from Mapper; among other things, the
\family typewriter
IdPK
\family default
trait, and foreign key fields (many to one mappings) are missing.
The other minor differences are that the
\family typewriter
getSingleton
\family default
method has been renamed to
\family typewriter
meta
\family default
, and the Field traits use different names under the Record framework (i.e.
\family typewriter
DateTimeField
\family default
vs
\family typewriter
MappedDateTime
\family default
).
\end_layout
\begin_layout Standard
\begin_inset listings
lstparams "showstringspaces=false"
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Entry Class in Record
\begin_inset CommandInset label
LatexCommand label
name "lst:Entry-class-record"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
import _root_.java.math.MathContext
\end_layout
\begin_layout Plain Layout
import _root_.net.liftweb.record._
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
class Expense extends KeyedRecord[Expense,Long] {
\end_layout
\begin_layout Plain Layout
def meta = Expense
\end_layout
\begin_layout Plain Layout
def primaryKey = id
\end_layout
\begin_layout Plain Layout
object id extends LongField(this) with KeyField[Long,Expense]
\end_layout
\begin_layout Plain Layout
object dateOf extends DateTimeField(this)
\end_layout
\begin_layout Plain Layout
object description extends StringField(this, 100)
\end_layout
\begin_layout Plain Layout
object amount extends DecimalField(this, MathContext.DECIMAL64, 2)
\end_layout
\begin_layout Plain Layout
object account extends LongField(this)
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
As you can see, we've set
\family typewriter
Expense
\family default
to extend the
\family typewriter
LongKeyedMapper
\family default
and
\family typewriter
IdPK
\family default
traits and we've added the fields required by our class.
We would like to provide a primary key for our entity; while not strictly
necessary, having a synthetic primary key often helps with CRUD operations.
The
\family typewriter
LongKeyedMapper
\family default
trait accomplishes two objectives: it tells Lift that we want a primary
key defined and that the key should be a long.
This is basically a shortcut for using the
\family typewriter
KeyedMapper[Long,Expense]
\family default
\begin_inset Index idx
status collapsed
\begin_layout Plain Layout
KeyedMapper
\end_layout
\end_inset
trait.
When you use the
\family typewriter
KeyedMapper
\family default
trait you need to provide an implementation for the
\family typewriter
primaryKeyField
\family default
def, which must match the type of the
\family typewriter
KeyedMapper
\family default
trait and be a subtype of
\family typewriter
IndexedField
\family default
\begin_inset Index idx
status collapsed
\begin_layout Plain Layout
IndexedField
\end_layout
\end_inset
.
The
\family typewriter
IdPK
\family default
trait handles the implementation, but note that
\family typewriter
IdPK
\family default
currently only supports
\family typewriter
Long
\family default
keys.
Mapper supports both indexed
\family typewriter
Longs
\family default
and
\family typewriter
Strings
\family default
, so if you want
\family typewriter
Strings
\family default
you'll need to explicitly use
\family typewriter
KeyedMapper[String,...]
\family default
and provide the field definition yourself.
It's possible to use some other type for your primary key, but you'll need
to roll your own (Section
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:Defining-Custom-Field-types-mapper"
\end_inset
).
Technically
\family typewriter
Int
\family default
indexes are supported as well, but there is no corresponding trait for
an
\family typewriter
Int
\family default
foreign key.
That means that if you use an
\family typewriter
Int
\family default
for the primary key, you may not be able to add a relationship to another
object (Section
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:Object-Relationships"
\end_inset
), unless you write your own.
Record is a little more flexible in primary key selection because it uses,
in effect, a marker trait (
\family typewriter
KeyField
\family default
) to indicate that a particular field is a key field.
One thing to note is that in the Mapper framework, the table name
\begin_inset Index idx
status collapsed
\begin_layout Plain Layout
table name
\end_layout
\end_inset
for your entity defaults to the name of the class (Expense, in our case).
If you want to change this, then you just need to override the
\family typewriter
dbTableName
\family default
\begin_inset Index idx
status collapsed
\begin_layout Plain Layout
dbTableName
\end_layout
\end_inset
def in your MetaMapper object.
\end_layout
\begin_layout Standard
Looking at these examples, you've probably noticed that the fields are defined
as objects rather than instance members (vars).
The basic reason for this is that the MetaMapper needs access to fields
for its validation and form functionality; it is more difficult to cleanly
define these properties in the MetaMapper if it had to access member vars
on each instance since a MetaMapper instance is itself an object.
Also note that
\family typewriter
MappedDecimal
\family default
is a custom field type
\begin_inset Foot
status open
\begin_layout Plain Layout
The authors are working on adding this to the core library soon after Lift
1.0
\end_layout
\end_inset
, which we'll cover in Section
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:Defining-Custom-Field-types-mapper"
\end_inset
.
\end_layout
\begin_layout Standard
In order to tie all of this together, we need to define a matching
\family typewriter
LongKeyedMetaMapper
\family default
\begin_inset Index idx
status collapsed
\begin_layout Plain Layout
KeyedMetaMapper
\end_layout
\end_inset
object as the singleton for our entity, as shown in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:EntryMeta-object"
\end_inset
.
The Meta object (whether MetaMapper or MetaRecord) is where you define
most behavior that is common across all of your instances.
In our examples, we've decided to name the meta object and instance class
the same.
We don't feel that this is unclear because the two together are what really
define the ORM behavior for a
\begin_inset Quotes eld
\end_inset
type.
\begin_inset Quotes erd
\end_inset
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
EntryMeta object
\begin_inset CommandInset label
LatexCommand label
name "lst:EntryMeta-object"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
object Expense extends Expense with LongKeyedMetaMapper[Expense] {
\end_layout
\begin_layout Plain Layout
override def fieldOrder = List(dateOf, description, amount)
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
In this instance, we're simply defining the order of fields as they'll be
displayed in XHTML
\begin_inset Index idx
status collapsed
\begin_layout Plain Layout
XHTML
\end_layout
\end_inset
and forms
\begin_inset Index idx
status collapsed
\begin_layout Plain Layout
form
\end_layout
\end_inset
by overriding the
\family typewriter
fieldOrder
\family default
method.
The default behavior is an empty list, which means no fields are involved
in display or form generation.
Generally, you will want to override
\family typewriter
fieldOrder
\family default
because this is not very useful.
If you don't want a particular field to show up in forms or XHTML output,
simply omit it from the fieldOrder
\begin_inset Index idx
status collapsed
\begin_layout Plain Layout
fieldOrder
\end_layout
\end_inset
list.
\end_layout
\begin_layout Standard
Because fields aren't actually instance members, operations on them are
slightly different than with a regular var.
The biggest difference is how we set fields: we use the apply method.
In addition, field access can be chained so that you can set multiple field
values in one statement, as shown in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Setting-field-values"
\end_inset
:
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Setting Field Values
\begin_inset CommandInset label
LatexCommand label
name "lst:Setting-field-values"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
myEntry.dateOf(new Date).description("A sample entry")
\end_layout
\begin_layout Plain Layout
myEntry.amount(BigDecimal("127.20"))
\end_layout
\end_inset
\end_layout
\begin_layout Standard
The underlying value of a given field can be retrieved with the
\family typewriter
is
\family default
method (the
\family typewriter
value
\family default
method in Record) as shown in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Accessing-field-values"
\end_inset
.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Accessing Field Values in Record
\begin_inset CommandInset label
LatexCommand label
name "lst:Accessing-field-values"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
// mapper
\end_layout
\begin_layout Plain Layout
val tenthOfAmount = myEntry.amount.is / 10
\end_layout
\begin_layout Plain Layout
val formatted = String.format("%s : %s",
\end_layout
\begin_layout Plain Layout
myEntry.description.is,
\end_layout
\begin_layout Plain Layout
myEntry.amount.is.toString)
\end_layout
\begin_layout Plain Layout
// record
\end_layout
\begin_layout Plain Layout
if (myEntry.description.value == "Doughnuts") {
\end_layout
\begin_layout Plain Layout
println("Diet ruined!")
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Subsection
Object Relationships
\begin_inset CommandInset label
LatexCommand label
name "sub:Object-Relationships"
\end_inset
\end_layout
\begin_layout Standard
Often it's appropriate to have relationships between different entities.
The archetypical example of this is the parent-child relationship.
In SQL, a relationship can be defined with a foreign key that associates
one table to another based on the primary key of the associated table.
As we showed in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Entry-class-mapper"
\end_inset
, there is a corresponding
\family typewriter
MappedForeignKey
\family default
trait, with concrete implementations for Long and String foreign keys.
Once we have this defined, accessing the object via the relationship is
achieved by using the
\family typewriter
obj
\family default
method on the foreign key field.
Note that the
\family typewriter
obj
\family default
method returns a
\family typewriter
Box
\family default
, so you need to do some further processing with it before you can use it.
With the foreign key functionality you can easily do one-to-many and many-to-on
e
\begin_inset Index idx
status open
\begin_layout Plain Layout
many-to-one
\end_layout
\end_inset
relationships (depending on where you put the foreign key).
One-to-many
\begin_inset Index idx
status open
\begin_layout Plain Layout
one-to-many
\end_layout
\end_inset
relationships can be achieved using helper methods on the
\begin_inset Quotes eld
\end_inset
one
\begin_inset Quotes erd
\end_inset
side that delegate to queries.
We'll cover queries in a moment, but Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Accessing-Foreign-Objects"
\end_inset
shows examples of two sides of the same relationship.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Accessing Foreign Objects
\begin_inset CommandInset label
LatexCommand label
name "lst:Accessing-Foreign-Objects"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
class Expense extends LongKeyedMapper[Expense] with IdPK {
\end_layout
\begin_layout Plain Layout
...
\end_layout
\begin_layout Plain Layout
object account extends MappedLongForeignKey(this, Account)
\end_layout
\begin_layout Plain Layout
def accountName =
\end_layout
\begin_layout Plain Layout
Text("My account is " + (account.obj.map(_.name.is) openOr "Unknown"))
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
class Account ...
{
\end_layout
\begin_layout Plain Layout
...
\end_layout
\begin_layout Plain Layout
def entries = Expense.findAll(By(Expense.account, this.id))
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
If you want to do many-to-many
\begin_inset Index idx
status open
\begin_layout Plain Layout
many-to-many
\end_layout
\end_inset
mappings you'll need to provide your own
\begin_inset Quotes eld
\end_inset
join
\begin_inset Quotes erd
\end_inset
class with foreign keys to both of your mapped entities.
An example would be if we wanted to have tags (categories) for our ledger
entries and wanted to be able to have a given entry have multiple tags
(e.g., you purchase a book for your mother's birthday, so it has the tags
Gift, Mom, and Books).
First we define the
\family typewriter
Tag
\family default
entity, as shown in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Tag-Entity"
\end_inset
.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Tag Entity
\begin_inset CommandInset label
LatexCommand label
name "lst:Tag-Entity"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
class Tag extends LongKeyedMapper[Tag] with IdPK {
\end_layout
\begin_layout Plain Layout
def getSingleton = Tag
\end_layout
\begin_layout Plain Layout
object name extends MappedString(this,100)
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
object Tag extends Tag with LongKeyedMetaMapper[Tag] {
\end_layout
\begin_layout Plain Layout
override def fieldOrder = List(name)
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Next, we define our join entity, as shown in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Join-Entity"
\end_inset
.
It's a
\family typewriter
LongKeyedMapper
\family default
just like the rest of the entities, but it only contains foreign key fields
to the other entities.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Join Entity
\begin_inset CommandInset label
LatexCommand label
name "lst:Join-Entity"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
class ExpenseTag extends LongKeyedMapper[ExpenseTag] with IdPK {
\end_layout
\begin_layout Plain Layout
def getSingleton = ExpenseTag
\end_layout
\begin_layout Plain Layout
object tag extends MappedLongForeignKey(this,Tag)
\end_layout
\begin_layout Plain Layout
object expense extends MappedLongForeignKey(this,Expense)
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
object ExpenseTag extends ExpenseTag with LongKeyedMetaMapper[ExpenseTag]
{
\end_layout
\begin_layout Plain Layout
def join (tag : Tag, tx : Expense) =
\end_layout
\begin_layout Plain Layout
this.create.tag(tag).expense(tx).save
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
To use the join entity, you'll need to create a new instance and set the
appropriate foreign keys to point to the associated instances.
As you can see, we've defined a convenience method on our
\family typewriter
Expense
\family default
meta object to do just that.
To make the many-to-many
\begin_inset Index idx
status open
\begin_layout Plain Layout
many-to-many
\end_layout
\end_inset
accessible as a field on our entities, we can use the HasManyThrough trait,
as shown in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:HasManyThrough-for-Many-to-Many"
\end_inset
.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
HasManyThrough for Many-to-Many Relationships
\begin_inset CommandInset label
LatexCommand label
name "lst:HasManyThrough-for-Many-to-Many"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
class Expense ...
{
\end_layout
\begin_layout Plain Layout
object tags extends HasManyThrough(this, Tag,
\end_layout
\begin_layout Plain Layout
ExpenseTag, ExpenseTag.tag, ExpenseTag.expense)
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
A similar field could be set up on the
\family typewriter
Tag
\family default
entity to point to entries.
It's important to note a few items:
\end_layout
\begin_layout Itemize
The only way to add new entries is to directly construct the ExpenseTag
instances and save them (either directly or via a helper method).
You can't make any modifications via the HasManyThrough trait
\end_layout
\begin_layout Itemize
Although the field is defined as a query, the field is actually lazy and
only runs once.
That means if you query it and then add some new ExpenseTag instances,
they won't show up in the field contents
\end_layout
\begin_layout Standard
If you want a way to retrieve the joined results such that it pulls fresh
from the database each time, you can instead define a helper join method
as shown in Section
\begin_inset CommandInset ref
LatexCommand vref
reference "sub:helper-joins"
\end_inset
.
\end_layout
\begin_layout Subsection
Indexing
\end_layout
\begin_layout Standard
It's often helpful to add indexes to a database to improve performance.
Mapper makes it easy to do most simple indexing simply by overriding the
\family typewriter
dbIndexed_?
\family default
\begin_inset Index idx
status open
\begin_layout Plain Layout
dbIndexed_?
\end_layout
\end_inset
def on the field.
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Indexing-a-field"
\end_inset
shows how we would add an index to our
\family typewriter
Expense.account
\family default
field.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Indexing a Field
\begin_inset CommandInset label
LatexCommand label
name "lst:Indexing-a-field"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
class Expense ...
{
\end_layout
\begin_layout Plain Layout
object account extends ...
{
\end_layout
\begin_layout Plain Layout
override def dbIndexed_? = true
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Mapper provides for more complex indexing via the
\family typewriter
MetaMapper.dbIndexes
\family default
def combined with the
\family typewriter
Index
\family default
,
\family typewriter
IndexField
\family default
and
\family typewriter
BoundedIndexField
\family default
case classes.
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:More-complex-indices"
\end_inset
shows some examples of how we might create more complex indices.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
More Complex Indices
\begin_inset CommandInset label
LatexCommand label
name "lst:More-complex-indices"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
object Expense extends ...
{
\end_layout
\begin_layout Plain Layout
// equivalent to the previous listing
\end_layout
\begin_layout Plain Layout
override dbIndexes = Index(IndexField(account)) :: Nil
\end_layout
\begin_layout Plain Layout
// equivalent to "create index ...
on transaction_t (account, description(10))"
\end_layout
\begin_layout Plain Layout
override dbIndexes = Index(IndexField(account),
\end_layout
\begin_layout Plain Layout
BoundedIndexField(description,10))
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Subsection
Schema Mapping
\begin_inset CommandInset label
LatexCommand label
name "sub:Schema-Mapping"
\end_inset
\end_layout
\begin_layout Standard
The Mapper framework makes it easy not only to define domain objects, but
also to create the database schema to go along with those objects.
The
\family typewriter
Schemifier
\family default
\begin_inset Index idx
status open
\begin_layout Plain Layout
Schemifier
\end_layout
\end_inset
object is what does all of the work for you: you simply pass in the
\family typewriter
MetaMapper
\family default
objects that you want the schema created for and it does the rest.
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Using-Schemifier"
\end_inset
shows how we could use
\family typewriter
Schemifier
\family default
to set up the database for our example objects.
The first argument controls whether an actual write will be performed on
the database.
If false,
\family typewriter
Schemifier
\family default
will log all of the DDL statements that it would like to apply, but no
changes will be made to the database.
The second argument is a logging function (logging is covered in Appendix
\begin_inset CommandInset ref
LatexCommand ref
reference "cha:Logging-in-Lift"
\end_inset
).
The remaining arguments are the
\family typewriter
MetaMapper
\family default
objects that you would like to have schemified.
You need to be careful to remember to include all of the objects, otherwise
the tables won't be created.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Using Schemifier
\begin_inset CommandInset label
LatexCommand label
name "lst:Using-Schemifier"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
Schemifier.schemify(true, Log.infoF _, User, Expense, Account, Tag, ExpenseTag)
\end_layout
\end_inset
\end_layout
\begin_layout Standard
As we mentioned in Section
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:Constructing-a-Mapper-enabled-class"
\end_inset
, you can override the default table name for a given Mapper class via the
\family typewriter
dbTableName
\family default
def in the corresponding MetaMapper.
The default table name is the name of the Mapper class, except when the
class name is also an SQL reserved word; in this case, a
\begin_inset Quotes eld
\end_inset
_t
\begin_inset Quotes erd
\end_inset
is appended to the table name.
You can also override individual column names on a per-field basis by overridin
g the
\family typewriter
dbColumnName
\family default
\begin_inset Index idx
status open
\begin_layout Plain Layout
dbColumnName
\end_layout
\end_inset
def in the field itself.
Like tables, the default column name for a field will be the same as the
field name as long as it's not an SQL reserved word; in this case a
\begin_inset Quotes eld
\end_inset
_c
\begin_inset Quotes erd
\end_inset
is appended to the column name.
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Custom-column-name"
\end_inset
shows how we could make our
\family typewriter
ExpenseTag.expense
\family default
field map to
\begin_inset Quotes eld
\end_inset
expense_id
\begin_inset Quotes erd
\end_inset
.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Setting a Custom Column Name
\begin_inset CommandInset label
LatexCommand label
name "lst:Custom-column-name"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
class ExpenseTag ...
{
\end_layout
\begin_layout Plain Layout
object expense extends ...
{
\end_layout
\begin_layout Plain Layout
override def dbColumnName = "expense_id"
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Subsection
Persistence Operations on an Entity
\begin_inset CommandInset label
LatexCommand label
name "sub:Persistence-Operations-on-entity"
\end_inset
\end_layout
\begin_layout Standard
Now that we've defined our entity we probably want to use it in the real
world to load and store data.
There are several operations on
\family typewriter
MetaMapper
\family default
that we can use :
\end_layout
\begin_layout Description
create Creates a new instance of the entity
\end_layout
\begin_layout Description
save Saves an instance to the database.
\end_layout
\begin_layout Description
delete Deletes the given entity instance
\end_layout
\begin_layout Description
count Returns the number of instances of the given entity.
An optional query criteria list can be used to narrow the entities being
counted
\end_layout
\begin_layout Description
countByInsecureSQL Similar to count, except a raw SQL string can be used
to perform the count.
The count value is expected to be in the first column and row of the returned
result set.
An example would be
\end_layout
\begin_deeper
\begin_layout LyX-Code
Expense.countByInsecureSQL(
\begin_inset Quotes eld
\end_inset
select count(amount)
\begin_inset Quotes eld
\end_inset
+
\end_layout
\begin_layout LyX-Code
\begin_inset Quotes eld
\end_inset
from Expense where amount > 20
\begin_inset Quotes erd
\end_inset
, ...)
\end_layout
\begin_layout Standard
We'll cover the
\family typewriter
IHaveValidatedThisSQL
\family default
parameter in a moment.
\end_layout
\end_deeper
\begin_layout Standard
There are also quite a few methods available for retrieving instances from
the database.
Each of these methods comes in two varieties: one that uses the default
database connection, and one that allows you to specify the connection
\begin_inset Index idx
status open
\begin_layout Plain Layout
Mapper ! multiple databases
\end_layout
\end_inset
to use (Section
\begin_inset CommandInset ref
LatexCommand vref
reference "lst:Multi-database-Connection-Manager"
\end_inset
).
The latter typically has
\begin_inset Quotes eld
\end_inset
DB
\begin_inset Quotes erd
\end_inset
appended to the method name.
The query methods on
\family typewriter
MetaMapper
\family default
are:
\end_layout
\begin_layout Description
findAll Retrieves a list of instances from the database.
The method is overloaded to take an optional set of query criteria parameters;
these will be covered in detail in their own section,
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:Querying-for-Entities"
\end_inset
.
\end_layout
\begin_layout Description
findAllByInsecureSQL Retrieves a list of instances based on a raw SQL query.
The query needs to return columns for all mapped fields.
Usually you can use the
\family typewriter
BySQL
\family default
QueryParameter to cover most of the same functionality.
\end_layout
\begin_layout Description
findAllByPreparedStatement Similar to
\family typewriter
findAllByInsecureSQL
\family default
except that prepared statements are used, which usually means that the
driver will handle properly escaping arguments in the query string.
\end_layout
\begin_layout Description
findAllFields This allows you to do a normal query returning only certain
fields from your Mapper instance.
For example, if you only wanted the amount from the transaction table you
would use this method.
Note that any fields that aren't specified in the query will return their
default value.
Generally, this method is only useful for read access to data because saving
any retrieved instances could overwrite real data.
\end_layout
\begin_layout Description
findMap* These methods provide the same functionality as the non-Map methods,
but take an extra function argument that transforms an entity into a
\family typewriter
Box[T]
\family default
, where
\family typewriter
T
\family default
is an arbitrary type.
An example would be getting a list of descriptions of our transactions:
\end_layout
\begin_deeper
\begin_layout LyX-Code
Expense.findMap(entry => Full(entry.description.is))
\end_layout
\end_deeper
\begin_layout Standard
The
\family typewriter
KeyedMapperClass
\family default
adds the
\family typewriter
find
\family default
method, which can be used to locate a single entity based on its primary
key.
In general these operations will be supported in both Record and Mapper.
However, because Record isn't coupled tightly to a JDBC backend some of
the find methods may not be supported directly and there may be additional
methods not available in Mapper for persistence.
For this reason, this section will deal specifically with Mapper's persistence
operations.
\end_layout
\begin_layout Subsubsection*
Creating an Instance
\end_layout
\begin_layout Standard
Once we have a
\family typewriter
MetaMapper
\family default
object defined we can use it to create objects using the
\family typewriter
create
\begin_inset Index idx
status collapsed
\begin_layout Plain Layout
create
\end_layout
\end_inset
\family default
method.
You generally don't want to use the
\begin_inset Quotes eld
\end_inset
new
\begin_inset Quotes erd
\end_inset
operator because the framework has to set up internal data for the instance
such as field owner, etc.
This is important to remember, since nothing will prevent you from creating
an instance manually: you may just get errors when you go to use the instance.
The
\family typewriter
join
\family default
method in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Join-Entity"
\end_inset
shows an example of create usage.
\end_layout
\begin_layout Subsubsection*
Saving an Instance
\end_layout
\begin_layout Standard
Saving an instance is as easy as calling the
\family typewriter
save
\family default
method on the instance you want to save.
Optionally, you can call the
\family typewriter
save
\family default
method on the Meta object, passing in the instance you want to save.
The
\family typewriter
save
\family default
method uses the the
\family typewriter
saved_?
\family default
and
\family typewriter
clean_?
\family default
flags to determine whether an insert or update is required to persist the
current state to the database, and returns a boolean to indicate whether
the save was successful or not.
The
\family typewriter
join
\family default
method in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Join-Entity"
\end_inset
shows an example of
\family typewriter
save
\family default
usage.
\end_layout
\begin_layout Subsubsection*
Deleting an Instance
\begin_inset CommandInset label
LatexCommand label
name "sub:Deleting-an-Instance"
\end_inset
\end_layout
\begin_layout Standard
There are several ways to delete instances.
The simplest way is to call the
\family typewriter
delete_!
\family default
method on the instance you'd like to remove.
An alternative is to call the
\family typewriter
delete_!
\family default
method on the Meta object, passing in the instance to delete.
In either case, the
\family typewriter
delete_!
\family default
method returns a boolean indicating whether the delete was successful or
not.
Listing
\begin_inset CommandInset ref
LatexCommand vref
reference "lst:Example-deletion"
\end_inset
shows an example of deleting instances.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Example Deletion
\begin_inset CommandInset label
LatexCommand label
name "lst:Example-deletion"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
if (! myExpense.delete_!) S.error("Couldn't delete the expense!")
\end_layout
\begin_layout Plain Layout
//or
\end_layout
\begin_layout Plain Layout
if (! (Expense delete_! myExpense)) S.error(...)
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Another approach to deleting entities is to use the
\family typewriter
bulkDelete_!!
\family default
method on
\family typewriter
MetaMapper
\family default
.
This method allows you to specify query parameters to control which entities
are deleted.
We will cover query parameters in Section
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:Querying-for-Entities"
\end_inset
(an example is in Listing
\begin_inset CommandInset ref
LatexCommand vref
reference "lst:Bulk-Deletion"
\end_inset
).
\end_layout
\begin_layout Subsection
Querying for Entities
\begin_inset CommandInset label
LatexCommand label
name "sub:Querying-for-Entities"
\end_inset
\end_layout
\begin_layout Standard
There are a variety of methods on
\family typewriter
MetaMapper
\family default
for querying for instances of a given entity.
The simplest method is
\family typewriter
findAll
\family default
\begin_inset Index idx
status collapsed
\begin_layout Plain Layout
findAll
\end_layout
\end_inset
called with no parameters.
The
\begin_inset Quotes eld
\end_inset
bare
\begin_inset Quotes erd
\end_inset
\family typewriter
findAll
\family default
returns a
\family typewriter
List
\family default
of all of the instances of a given entity loaded from the database.
Note that each
\family typewriter
findAll...
\family default
method has a corresponding method that takes a database connection for
sharding or multiple database usage (see sharding in Section
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:Multiple-Databases"
\end_inset
).
Of course, for all but the smallest datasets, pulling the entire model
to get one entity from the database is inefficient and slow.
Instead, the
\family typewriter
MetaMapper
\family default
provides
\begin_inset Quotes eld
\end_inset
flag
\begin_inset Quotes erd
\end_inset
objects to control the query.
\end_layout
\begin_layout Standard
The ability to use fine-grained queries to select data is a fundamental
feature of relational databases, and Mapper provides first-class support
for constructing queries in a manner that is not only easy to use, but
type-safe.
This means that you can catch query errors at compile time instead of runtime.
The basis for this functionality is the
\family typewriter
QueryParam
\family default
trait, which has several concrete implementations that are used to construct
the actual query.
The
\family typewriter
QueryParam
\family default
implementations can be broken up into two main groups:
\end_layout
\begin_layout Enumerate
Comparison - These are typically items that would go in the where clause
of an SQL query.
They are used to refine the set of instances that will be returned
\end_layout
\begin_layout Enumerate
Control - These are items that control things like sort order and pagination
of the results
\end_layout
\begin_layout Standard
Although Mapper provides a large amount of the functionality in SQL, some
features are not covered directly or at all.
In some cases we can define helper methods to make querying easier, particularl
y for joins (Section
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:helper-joins"
\end_inset
).
\end_layout
\begin_layout Subsection
Comparison QueryParams
\end_layout
\begin_layout Standard
The simplest
\family typewriter
QueryParam
\family default
to refine your query is the
\family typewriter
By
\family default
\begin_inset Index idx
status collapsed
\begin_layout Plain Layout
By
\end_layout
\end_inset
object and its related objects.
\family typewriter
By
\family default
is used for a direct value comparison of a given field: essentially an
\begin_inset Quotes eld
\end_inset
=
\begin_inset Quotes erd
\end_inset
in SQL.
For instance, Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Retrieving-by-account"
\end_inset
shows how we can get all of the expenses for a given account.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Retrieving by Account ID
\begin_inset CommandInset label
LatexCommand label
name "lst:Retrieving-by-account"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
val myEntries = Expense.findAll(By(Expense.account, myAccount.id))
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Note that our By criterion is comparing the
\family typewriter
Expense.account
\family default
field to the primary key (
\family typewriter
id
\family default
field) of our account instead of to the account instance itself.
This is because the
\family typewriter
Expense.account
\family default
field is a
\family typewriter
MappedForeignKey
\family default
field, which uses the type of the key instead of the type of the entity
as its underlying value.
In this instance, that means that any queries using
\family typewriter
Expense.account
\family default
need to use a
\family typewriter
Long
\family default
to match the underlying type.
Besides
\family typewriter
By
\family default
, the other basic clauses are:
\end_layout
\begin_layout Itemize
\family typewriter
NotBy
\family default
\begin_inset Index idx
status open
\begin_layout Plain Layout
NotBy
\end_layout
\end_inset
- Selects entities whose queried field is not equal to the given value
\end_layout
\begin_layout Itemize
\family typewriter
By_>
\family default
\begin_inset Index idx
status open
\begin_layout Plain Layout
By_>
\end_layout
\end_inset
- Selects entities whose queried field is larger than the given value
\end_layout
\begin_layout Itemize
\family typewriter
By_<
\family default
\begin_inset Index idx
status open
\begin_layout Plain Layout
By_<
\end_layout
\end_inset
- Selects entities whose queried field is less than the given value
\end_layout
\begin_layout Itemize
\family typewriter
ByList
\family default
\begin_inset Index idx
status open
\begin_layout Plain Layout
ByList
\end_layout
\end_inset
- Selects entities whose queried field is equal to one of the values in
the given List.
This corresponds to the
\begin_inset Quotes eld
\end_inset
field IN (x,y,z)
\begin_inset Quotes erd
\end_inset
syntax in SQL.
\end_layout
\begin_layout Itemize
\family typewriter
NullRef
\family default
\begin_inset Index idx
status open
\begin_layout Plain Layout
NullRef
\end_layout
\end_inset
- Selects entities whose queried field is NULL
\end_layout
\begin_layout Itemize
\family typewriter
NotNullRef
\family default
\begin_inset Index idx
status open
\begin_layout Plain Layout
NotNullRef
\end_layout
\end_inset
- Select entities whose queried field is not NULL
\end_layout
\begin_layout Itemize
\family typewriter
Like
\family default
\begin_inset Index idx
status collapsed
\begin_layout Plain Layout
Like
\end_layout
\end_inset
- Select entities whose queried field is like the given string.
As in SQL, the percent sign is used as a wildcard
\end_layout
\begin_layout Standard
In addition to the basic clauses there are some slightly more complex ways
to control the query.
The first of these is
\family typewriter
ByRef
\family default
\begin_inset Index idx
status collapsed
\begin_layout Plain Layout
ByRef
\end_layout
\end_inset
, which selects entities whose queried field is equal to the value of another
query field
\emph on
on the same entity
\emph default
.
A contrived example would be if we define a tree structure in our table
and root nodes are marked as having themselves as parents:
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
An Example of ByRef
\begin_inset CommandInset label
LatexCommand label
name "lst:An-example-of-byref"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
// select all root nodes from the forest
\end_layout
\begin_layout Plain Layout
TreeNode.findAll(ByRef(TreeNode.parent,TreeNode.id))
\end_layout
\end_inset
\end_layout
\begin_layout Standard
The related
\family typewriter
NotByRef
\family default
tests for inequality between two query fields.
\end_layout
\begin_layout Standard
Getting slightly more complex, we come to the
\family typewriter
In
\family default
\begin_inset Index idx
status collapsed
\begin_layout Plain Layout
In
\end_layout
\end_inset
\family typewriter
QueryParameter
\family default
, which is used just like an
\begin_inset Quotes eld
\end_inset
IN
\begin_inset Quotes erd
\end_inset
clause with a subselect in an SQL statement.
For example, let's say we wanted to get all of the entries that belong
to tags that start with the letter
\begin_inset Quotes eld
\end_inset
c
\begin_inset Quotes erd
\end_inset
.
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Using-In"
\end_inset
shows the full breakdown.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Using In
\begin_inset CommandInset label
LatexCommand label
name "lst:Using-In"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
val cExpenses =
\end_layout
\begin_layout Plain Layout
ExpenseTag.findAll(
\end_layout
\begin_layout Plain Layout
In(ExpenseTag.tag,
\end_layout
\begin_layout Plain Layout
Tag.id,
\end_layout
\begin_layout Plain Layout
Like(Tag.name, "c%"))).map(_.expense.obj.open_!).removeDuplicates
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Note that we use the
\family typewriter
List.removeDuplicates
\family default
method to make sure that the List contains unique entities.
This requires overriding the
\family typewriter
equals
\family default
and
\family typewriter
hashCode
\family default
methods on the Expense class, which we show in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Overriding-equalshash-expense"
\end_inset
.
In our example we're using the primary key (
\family typewriter
id
\family default
field) to define object
\begin_inset Quotes eld
\end_inset
identity
\begin_inset Quotes erd
\end_inset
.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Overriding
\family typewriter
equals
\family default
and
\family typewriter
hashcode
\family default
on the Expense entity
\begin_inset CommandInset label
LatexCommand label
name "lst:Overriding-equalshash-expense"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
class Expense ...
{
\end_layout
\begin_layout Plain Layout
...
\end_layout
\begin_layout Plain Layout
override def equals (other : Any) = other match {
\end_layout
\begin_layout Plain Layout
case e : Expense if e.id.is == this.id.is => true
\end_layout
\begin_layout Plain Layout
case _ => false
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
override def hashCode = this.id.is.hashCode
\end_layout
\begin_layout Plain Layout
...
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
We use the
\family typewriter
ByRef
\family default
params to do the join between the many-to-many entity on the query.
Related to
\family typewriter
In
\family default
is
\family typewriter
InRaw
\family default
\begin_inset Index idx
status collapsed
\begin_layout Plain Layout
InRaw
\end_layout
\end_inset
, which allows you to specify your own SQL subquery for the
\begin_inset Quotes eld
\end_inset
IN
\begin_inset Quotes erd
\end_inset
portion of the where clause.
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Using-InRaw"
\end_inset
shows an example of how we could use
\family typewriter
InRaw
\family default
to find
\family typewriter
Tags
\family default
for expense entries made in the last 30 days.
\end_layout
\begin_layout Standard
\begin_inset listings
lstparams "breaklines=true"
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Using InRaw
\begin_inset CommandInset label
LatexCommand label
name "lst:Using-InRaw"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
def recentTags = {
\end_layout
\begin_layout Plain Layout
val joins = ExpenseTag.findAll(
\end_layout
\begin_layout Plain Layout
InRaw(ExpenseTag.expense,
\end_layout
\begin_layout Plain Layout
"select id from Expense where dateOf > (CURRENT_DATE - interval
'30 days')",
\end_layout
\begin_layout Plain Layout
IHaveValidatedThisSQL("dchenbecker", "2008-12-03"))
\end_layout
\begin_layout Plain Layout
joins.map(_.expense.obj.open_!).removeDuplicates
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Here things are starting to get a little hairy.
The
\family typewriter
InRaw
\family default
only allows us to specify the subquery for the IN clause, so we have to
do some postprocessing to get unique results.
If you want to do this in the query itself you'll have to use the
\family typewriter
findAllByInsecureSql
\family default
or
\family typewriter
findAllByPreparedStatement
\family default
methods, which are covered later in this section on page number
\begin_inset CommandInset ref
LatexCommand pageref
reference "sub:SQL-based-queries"
\end_inset
.
The final parameter for
\family typewriter
InRaw
\family default
,
\family typewriter
IHaveValidatedThisSQL
\family default
acts as a code audit mechanism that says that someone has checked the SQL
to make sure it's safe to use.
The query fragment is added to the master query as-is: no escaping or other
filtering is performed on the string.
That means that if you take user input.
then you need to be very careful about it or you run the risk of an SQL
injection attack on your site.
\end_layout
\begin_layout Standard
The next
\family typewriter
QueryParam
\family default
we'll cover is
\family typewriter
BySql
\family default
, which lets you use a complete SQL fragment that gets put into the where
clause.
An example of this would be if we want to find all expense entries within
the last 30 days, as shown in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Using-BySql"
\end_inset
.
Again, the
\family typewriter
IHaveValidatedThisSQL
\family default
\begin_inset Index idx
status open
\begin_layout Plain Layout
IHaveValidatedThisSQL
\end_layout
\end_inset
case class is required as a code audit mechanism to make sure someone has
verified that the SQL used is safe.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Using BySql
\begin_inset CommandInset label
LatexCommand label
name "lst:Using-BySql"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
val recentEntries = Expense.findAll(
\end_layout
\begin_layout Plain Layout
BySql("dateOf > (CURRENT_DATE - interval '30 days')",
\end_layout
\begin_layout Plain Layout
IHaveValidatedThisSQL("dchenbecker","2008-12-03"))
\end_layout
\end_inset
\end_layout
\begin_layout Standard
The tradeoff with using
\family typewriter
BySql
\family default
is that you need to be careful with what you allow into the query string.
\family typewriter
BySql
\family default
supports parameterized queries as shown in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Parameterized-BySql"
\end_inset
, so use those if you need to have dynamic queries.
Whatever you do, don't use string concatenation unless you really know
what you're doing.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Parameterized BySql
\begin_inset CommandInset label
LatexCommand label
name "lst:Parameterized-BySql"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
val amountRange = Expense.findAll(
\end_layout
\begin_layout Plain Layout
BySql("amount between ? and ?", lowVal, highVal))
\end_layout
\end_inset
\end_layout
\begin_layout Standard
As we mentioned in Section
\begin_inset CommandInset ref
LatexCommand vref
reference "sub:Deleting-an-Instance"
\end_inset
, we can use the query parameters to do bulk deletes in addition to querying
for instances.
Simply use the
\family typewriter
QueryParam
\family default
classes to constrain what you want to delete.
Obviously, the control params that we'll cover next make no sense in this
context, but the compiler won't complain.
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Bulk-Deletion"
\end_inset
shows an example of deleting all entries older than a certain date.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Bulk Deletion
\begin_inset CommandInset label
LatexCommand label
name "lst:Bulk-Deletion"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
def deleteBefore (date : Date) =
\end_layout
\begin_layout Plain Layout
Expense.bulkDelete_!!(By_<(Expense.dateOf, date))
\end_layout
\end_inset
\end_layout
\begin_layout Subsection
Control QueryParams
\begin_inset CommandInset label
LatexCommand label
name "sub:Control-QueryParams"
\end_inset
\end_layout
\begin_layout Standard
Now that we've covered the selection and comparison
\family typewriter
QueryParams
\family default
, we can start to look at the control params.
The first one that we'll look at is
\family typewriter
OrderBy
\family default
.
This operates exactly like the order by clause in SQL, and allows you to
sort on a given field in either ascending or descending order.
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:OrderBy-Clause"
\end_inset
shows an example of ordering our
\family typewriter
Expense
\family default
entries by amount.
The
\family typewriter
Ascending
\family default
and
\family typewriter
Descending
\family default
case objects are in the net.liftweb.mapper package.
The
\family typewriter
OrderBySql
\family default
case class operates similarly, except that you provide your own SQL fragment
for the ordering, as shown in the example.
Again, you need to validate this SQL.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
OrderBy Clause
\begin_inset CommandInset label
LatexCommand label
name "lst:OrderBy-Clause"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
val cheapestFirst =
\end_layout
\begin_layout Plain Layout
Expense.findAll(OrderBy(Expense.amount,Ascending))
\end_layout
\begin_layout Plain Layout
// or
\end_layout
\begin_layout Plain Layout
val cheapestFirst =
\end_layout
\begin_layout Plain Layout
Expense.findAll(OrderBySql("amount asc"),
\end_layout
\begin_layout Plain Layout
IHaveValidatedThisSQL("dchenbecker", "2008-12-03"))
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Pagination of results is another feature that people often want to use,
and Mapper provides a simple means for controlling it with two more
\family typewriter
QueryParam
\family default
classes:
\family typewriter
StartAt
\family default
and
\family typewriter
MaxRows
\family default
, as shown in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Pagination-of-Results"
\end_inset
.
In this example, we take the offset from a parameter passed to our snippet,
with a default of zero.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Pagination of Results
\begin_inset CommandInset label
LatexCommand label
name "lst:Pagination-of-Results"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
val offset = S.param("offset").map(_.toLong) openOr 0
\end_layout
\begin_layout Plain Layout
Expense.findAll(StartAt(offset), MaxRows(20))
\end_layout
\end_inset
\end_layout
\begin_layout Standard
An important feature of the methods that take
\family typewriter
QueryParams
\family default
is that they can take multiple params, as shown in this example.
A more complex example is shown in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Multiple-QueryParams"
\end_inset
.
In this example, we're querying with a
\family typewriter
Like
\family default
clause, sorting on the date of the entries, and paginating the results,
all in one statement!
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Multiple QueryParams
\begin_inset CommandInset label
LatexCommand label
name "lst:Multiple-QueryParams"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
Expense.findAll(Like(Expense.description, "Gift for%"),
\end_layout
\begin_layout Plain Layout
OrderBy(Expense.dateOf,Descending),
\end_layout
\begin_layout Plain Layout
StartAt(offset),
\end_layout
\begin_layout Plain Layout
MaxRows(pageSize))
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Another useful
\family typewriter
QueryParam
\family default
is the
\family typewriter
Distinct
\family default
case class, which acts exactly the same way as the DISTINCT keyword in
SQL.
One caveat is that Mapper doesn't support explicit joins, so this restricts
the situations in which you can use
\family typewriter
Distinct
\family default
.
The final
\begin_inset Quotes eld
\end_inset
control
\begin_inset Quotes erd
\end_inset
\family typewriter
QueryParam
\family default
that we'll cover is
\family typewriter
PreCache
\family default
\begin_inset Index idx
status collapsed
\begin_layout Plain Layout
PreCache
\end_layout
\end_inset
.
It's used when you have a mapped foreign key field on an entity.
Normally, when Mapper loads your main entity it leaves the foreign key
field in a lazy state, so that the query to get the foreign object isn't
executed until you access the field.
This can obviously be inefficient when you have many entities loaded that
you need to access, so the
\family typewriter
PreCache
\family default
parameter forces Mapper to preload the foreign objects as part of the query.
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Using-PreCache"
\end_inset
shows how we can use
\family typewriter
PreCache
\family default
to fetch an
\family typewriter
Expense
\family default
entry as well as the account for the entry.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Using PreCache
\begin_inset CommandInset label
LatexCommand label
name "lst:Using-PreCache"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
def loadExpensePlusAccount (id : Long) =
\end_layout
\begin_layout Plain Layout
Expense.findAll(By(Expense.id, id),
\end_layout
\begin_layout Plain Layout
PreCache(Expense.account))
\end_layout
\end_inset
\end_layout
\begin_layout Subsection
Making Joins a Little Friendlier
\begin_inset CommandInset label
LatexCommand label
name "sub:helper-joins"
\end_inset
\end_layout
\begin_layout Standard
If you prefer to keep your queries type-safe, but you want a little more
convenience in your joins between entities, you can define helper methods
on your entities.
One example is finding all of the tags for a given
\family typewriter
Expense
\family default
, as shown in Listing
\begin_inset CommandInset ref
LatexCommand pageref
reference "lst:Join-Convenience-Method"
\end_inset
.
Using this method in our example has an advantage over using
\family typewriter
HasManyThrough
\family default
:
\family typewriter
hasManyThrough
\family default
is a lazy value that will only retrieve data from the database once per
request.
Using a
\family typewriter
findAll
\family default
will retrieve data from the database every time.
This may be important if you add data to the database during a request,
or if you expect things to change between queries.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Join Convenience Method
\begin_inset CommandInset label
LatexCommand label
name "lst:Join-Convenience-Method"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
def tags =
\end_layout
\begin_layout Plain Layout
ExpenseTag.findAll(By(ExpenseTag.expense, this.id)).map(_.tag.obj.open_!)
\end_layout
\end_inset
\end_layout
\begin_layout Section
Utility Functionality
\end_layout
\begin_layout Standard
In addition to the first-class persistence support in Mapper and Record,
the frameworks provide additional functionality to make writing data-driven
applications much simpler.
This includes things such as automatic XHTML representation of objects
and support for generating everything from simple forms for an individual
entity to a full-fledged CRUD
\begin_inset Foot
status open
\begin_layout Plain Layout
An acronym (Create, Read, Update and Delete) representing the standard operation
s that are performed on database records.
Taken from
\begin_inset Flex URL
status open
\begin_layout Plain Layout
http://provost.uiowa.edu/maui/Glossary.html
\end_layout
\end_inset
.
\end_layout
\end_inset
implementation for your entities.
\end_layout
\begin_layout Subsection
Display Generation
\end_layout
\begin_layout Standard
If you want to display a Mapper instance as XHTML, simply call the
\family typewriter
asHtml
\family default
method (
\family typewriter
toXHtml
\family default
in Record) on your instance.
The default implementation turns each field's value into a
\family typewriter
Text
\family default
node via the
\family typewriter
toString
\family default
method and concatenates the results separated by newlines.
If you want to change this behavior, override the
\family typewriter
asHtml
\family default
on your field definitions.
For example, if we wanted to control formatting on our
\family typewriter
dateOf
\family default
field, we could modify the field as shown in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Custom-field-display"
\end_inset
.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Custom Field Display
\begin_inset CommandInset label
LatexCommand label
name "lst:Custom-field-display"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
import _root_.java.text.DateFormat
\end_layout
\begin_layout Plain Layout
...
\end_layout
\begin_layout Plain Layout
object dateOf extends MappedDateTime(this) {
\end_layout
\begin_layout Plain Layout
final val dateFormat =
\end_layout
\begin_layout Plain Layout
DateFormat.getDateInstance(DateFormat.SHORT)
\end_layout
\begin_layout Plain Layout
override def asHtml = Text(dateFormat.format(is))
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Note that in Record,
\family typewriter
dateOf
\family default
contains a
\family typewriter
java.util.Calendar
\family default
instance and not a
\begin_inset Newline linebreak
\end_inset
\family typewriter
java.util.Date
\family default
, so we would need to use the
\family typewriter
getTime
\family default
method on the value.
Two similar methods,
\family typewriter
asJSON
\family default
and
\family typewriter
asJs
\family default
, will return the JSON and JavaScript object representation of the instance,
respectively.
\end_layout
\begin_layout Subsection
Form Generation
\begin_inset CommandInset label
LatexCommand label
name "sub:Form-generation"
\end_inset
\begin_inset Index idx
status open
\begin_layout Plain Layout
Forms
\end_layout
\end_inset
\end_layout
\begin_layout Standard
One of the biggest pieces of functionality in the Mapper framework is the
ability to generate entry forms for a given record.
The
\family typewriter
toForm
\begin_inset Index idx
status collapsed
\begin_layout Plain Layout
toForm
\end_layout
\end_inset
\family default
method on Mapper is overloaded so that you can control how your form is
created.
All three
\family typewriter
toForm
\family default
methods on Mapper take a
\family typewriter
Box[String]
\family default
as their first parameter to control the submit button; if the Box is Empty,
no submit button is generated, otherwise, the String contents of the Box
are used as the button label.
If you opt to skip the submit button you'll need to provide it yourself
via binding or some other mechanism, or you can rely on implicit form submissio
n (when the user hits enter in a text field, for instance).
The first
\family typewriter
toForm
\family default
method simply takes a function to process the submitted form and returns
the XHTML as shown in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Default-toForm-method"
\end_inset
:
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Default toForm Method
\begin_inset CommandInset label
LatexCommand label
name "lst:Default-toForm-method"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
myEntry.toForm(Full("Save"), { _.save })
\end_layout
\end_inset
\end_layout
\begin_layout Standard
As you can see, this makes it very easy to generate a form for editing an
entity.
The second
\family typewriter
toForm
\family default
method allows you to provide a URL which the Mapper will redirect to if
validation succeeds on form submission (this is not provided in Record).
This can be used for something like a login form, as shown in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Custom-form-redirect"
\end_inset
:
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Custom Submit Button
\begin_inset CommandInset label
LatexCommand label
name "lst:Custom-form-redirect"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
myEntry.toForm (Full("Login"), "/member/profile")
\end_layout
\end_inset
\end_layout
\begin_layout Standard
The third form of the toForm method is similar to the first form, with the
addition of
\begin_inset Quotes eld
\end_inset
redo
\begin_inset Quotes erd
\end_inset
snippet parameter.
This allows you to keep the current state of the snippet when validation
fails so that the user doesn't have to re-enter all of the data in the
form.
\begin_inset Note Note
status open
\begin_layout Plain Layout
Add a redo snippet example
\end_layout
\end_inset
\end_layout
\begin_layout Standard
The Record framework allows for a little more flexibility in controlling
form output.
The MetaRecord object allows you to change the default template
\begin_inset Index idx
status collapsed
\begin_layout Plain Layout
Override form template
\end_layout
\end_inset
that the form uses by setting the formTemplate
\begin_inset Index idx
status collapsed
\begin_layout Plain Layout
formTemplate
\end_layout
\end_inset
var.
The template may contain any XHTML you want, but the
\family typewriter
toForm
\family default
method will provide special handling for the following tags:
\end_layout
\begin_layout Description
<lift:field_label
\begin_inset space ~
\end_inset
name=
\begin_inset Quotes eld
\end_inset
...
\begin_inset Quotes erd
\end_inset
\begin_inset space ~
\end_inset
/> The label for the field with the given name will be rendered here.
\end_layout
\begin_layout Description
<lift:field
\begin_inset space ~
\end_inset
name=
\begin_inset Quotes eld
\end_inset
...
\begin_inset Quotes erd
\end_inset
\begin_inset space ~
\end_inset
/> The field itself (specified by the given name) will be rendered here.
Typically this will be an input field, although it can be anything type-appropr
iate.
For example, a BooleanField would render a checkbox.
\end_layout
\begin_layout Description
<lift:field_msg
\begin_inset space ~
\end_inset
name=
\begin_inset Quotes eld
\end_inset
...
\begin_inset Quotes erd
\end_inset
\begin_inset space ~
\end_inset
/> Any messages, such as from validation, for the field with the given name
will be rendered here.
\end_layout
\begin_layout Standard
As an example, if we wanted to use tables to lay out the form for our ledger
entry, the row for the description field might look like that in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Custom-form-template"
\end_inset
:
\end_layout
\begin_layout Standard
\begin_inset listings
lstparams "language=XML"
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Custom Form Template
\begin_inset CommandInset label
LatexCommand label
name "lst:Custom-form-template"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
<!-- Example description field row for Record's toForm method -->
\end_layout
\begin_layout Plain Layout
<tr>
\end_layout
\begin_layout Plain Layout
<th><lift:field_label name="description" /></th>
\end_layout
\begin_layout Plain Layout
<td><lift:field name="description" />
\end_layout
\begin_layout Plain Layout
<lift:field_msg name="description" /></td>
\end_layout
\begin_layout Plain Layout
</tr>
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Technically, the
\family typewriter
field_msg
\family default
binding looks up Lift messages (Chapter
\begin_inset CommandInset ref
LatexCommand ref
reference "cha:Message-Handling"
\end_inset
) based on the field's
\family typewriter
uniqueId
\family default
, so you can set your own messages outside of validation using the S.{error,
notice, warning} methods as shown in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Setting-messages-via-S"
\end_inset
:
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Setting Messages via S
\begin_inset CommandInset label
LatexCommand label
name "lst:Setting-messages-via-S"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
S.warning(myEntry.amount.uniqueFieldId,
\end_layout
\begin_layout Plain Layout
"You have entered a negative amount!")
\end_layout
\begin_layout Plain Layout
S.warning("amount_id", "This is brittle")
\end_layout
\end_inset
\end_layout
\begin_layout Standard
For most purposes, though, using the validation mechanism discussed in the
next section is the appropriate way to handle error checking and reporting.
\end_layout
\begin_layout Subsection
Validation
\begin_inset CommandInset label
LatexCommand label
name "sub:Mapper-Validation"
\end_inset
\begin_inset Index idx
status collapsed
\begin_layout Plain Layout
Validation
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Validation is the process of checking a field during form processing to
make sure that the submitted value meets requirements.
This can be something as simple as ensuring that a value was submitted,
or as complex as comparing multiple field values together.
Validation is achieved via a
\family typewriter
List
\family default
of functions on a field that take the field value as input and return a
\family typewriter
List[FieldError]
\family default
(
\family typewriter
Box[Node]
\family default
in Record).
To indicate that validation succeeded, simply return an empty List, otherwise
the list of
\family typewriter
FieldErrors
\family default
you return are used as the failure messages to be presented to the user.
A
\family typewriter
FieldError
\family default
is simply a case class that associates an error message with a particular
field.
As an example, let's say we don't want someone to be able to add an
\family typewriter
Expense
\family default
entry for a date in the future.
First, we need to define a function for our
\family typewriter
dateOf
\family default
field that takes a
\family typewriter
Date
\family default
as an input (For Record,
\family typewriter
java.util.Calendar
\family default
\begin_inset Index idx
status collapsed
\begin_layout Plain Layout
Calendar
\end_layout
\end_inset
, not
\family typewriter
Date
\family default
, is the actual value type of
\family typewriter
DateTimeField
\family default
) and returns the proper
\family typewriter
List
\family default
.
We show a simple function in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Date-validation"
\end_inset
.
In the method, we simply check to see if the millisecond count is greater
than
\begin_inset Quotes eld
\end_inset
now
\begin_inset Quotes erd
\end_inset
and return an error message if so.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Date Validation
\begin_inset CommandInset label
LatexCommand label
name "lst:Date-validation"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
import _root_.java.util.Date
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
class Expense extends LongKeyedMapper[Expense] with IdPK {
\end_layout
\begin_layout Plain Layout
...
\end_layout
\begin_layout Plain Layout
object dateOf extends MappedDateTime(this) {
\end_layout
\begin_layout Plain Layout
def noFutureDates (time : Date) = {
\end_layout
\begin_layout Plain Layout
if (time.getTime > System.currentTimeMillis) {
\end_layout
\begin_layout Plain Layout
List(FieldError(this, "You cannot make future expense entries"))
\end_layout
\begin_layout Plain Layout
} else {
\end_layout
\begin_layout Plain Layout
List[FieldError]()
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
...
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
The first argument for the FieldError is the field itself, so you could
use the alternate definition shown in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Alternate-Date-Validation"
\end_inset
if you would prefer to define your validation functions elsewhere (if they're
common to more than one entity, for example).
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Alternate Date Validation
\begin_inset CommandInset label
LatexCommand label
name "lst:Alternate-Date-Validation"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
import _root_.java.util.Date
\end_layout
\begin_layout Plain Layout
import _root_.net.liftweb.http.FieldIdentifier
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
object ValidationMethods {
\end_layout
\begin_layout Plain Layout
def noFutureDates (field : FieldIdentifier)(time : Date) = {
\end_layout
\begin_layout Plain Layout
if (time.getTime > System.currentTimeMillis) {
\end_layout
\begin_layout Plain Layout
List(FieldError(field, "You cannot make future expense entries"))
\end_layout
\begin_layout Plain Layout
} else {
\end_layout
\begin_layout Plain Layout
List[FieldError]()
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
...
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
The next step is to tie the validation into the field itself.
We do this by slightly modifying our field definition for
\family typewriter
date
\family default
to set our list of validators as shown in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Setting-validators"
\end_inset
:
\end_layout