Skip to content

Commit

Permalink
changed nullable fields
Browse files Browse the repository at this point in the history
  • Loading branch information
abuntakov committed Aug 26, 2015
1 parent c262b26 commit b36895c
Show file tree
Hide file tree
Showing 7 changed files with 63 additions and 36 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
.session
*.log
bower_components
.idea/
23 changes: 15 additions & 8 deletions app/controllers/Contacts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,27 @@ package controllers
import org.sample.models.Contact
import converters.ContactJson.contactReader
import play.api.mvc.{Action, Controller}
import play.api.libs.json.JsSuccess
import play.api.libs.json.{JsObject, JsSuccess}
import play.api.Logger

object Contacts extends Controller {
val logger = Logger(this.getClass)

def update(id: Long) = Action(parse.json) { implicit request =>
request.body.validate[Contact] match {
case JsSuccess(contact, _) =>
logger.info(s"contact: $contact")
Ok("Success")
// request.body.validate[Contact] match {
// case JsSuccess(contact, _) =>
// logger.info(s"contact: $contact")
// Ok("Success")
//
// case _ =>
// Ok("Error")
// }
val js = request.body

case _ =>
Ok("Error")
}
logger.info("hello world")

logger.info(js.asInstanceOf[JsObject].keys.mkString(","))

Ok
}
}
12 changes: 11 additions & 1 deletion app/controllers/converters/ContactJson.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package controllers.converters

import org.sample.models.{Location, Boundary, Contact}
import play.api.libs.json.{Json, Reads, JsPath}
import play.api.data.validation.ValidationError
import play.api.libs.json._
import play.api.libs.functional.syntax._

object ContactJson {
Expand All @@ -14,4 +15,13 @@ object ContactJson {
((JsPath \ "firstName").read(Reads.optionNoError[String]) or Reads.pure(null.asInstanceOf[Option[String]])) and
((JsPath \ "lastName").read(Reads.optionNoError[String]) or Reads.pure(null.asInstanceOf[Option[String]]))
)(Contact.apply _)


implicit val contactWithFieldsReader: Reads[(Contact, Seq[String])] = new Reads[(Contact, Seq[String])] {
override def reads(json: JsValue): JsResult[(Contact, Seq[String])] = {
json.validate[Contact].map { contact =>
(contact, json.asInstanceOf[JsObject].keys.toSeq)
}
}
}
}
6 changes: 3 additions & 3 deletions modules/core/src/main/scala/org/sample/Application.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ object Application extends App {
firstName = Some("alex"),
lastName = null)

val id = Contact.create( contact )
val id = Contact.create( contact, Contact.entityFields diff Seq("lastName") )

logger.info("Saved with id=" + id)

Expand All @@ -41,11 +41,11 @@ object Application extends App {
logger.info(s"Updated contact: $updatedContact")


var reqErrors = ContactValidator.validate(Contact(NoId, null, null, None), true)
var reqErrors = ContactValidator.validate(Contact(NoId, null, null, None), Contact.entityFields diff Seq("id", "email", "location"), true)

logger.info(s"Errors: $reqErrors")

reqErrors = ContactValidator.validate(Contact(NoId, null, null, None, Some("shor")), true)
reqErrors = ContactValidator.validate(Contact(NoId, null, null, None, Some("shor")), Contact.entityFields diff Seq("id", "email", "location"), true)

logger.info(s"Errors: $reqErrors")

Expand Down
14 changes: 8 additions & 6 deletions modules/core/src/main/scala/org/sample/models/Contact.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,22 @@ object Contact extends SQLSyntaxSupport[Contact] with EntityWrapper {

override val tableName = "contacts"

val creatableFields = extractFieldNames(classOf[Contact]) diff Seq("id")
val entityFields = extractFieldNames(classOf[Contact])

val updatableFields = extractFieldNames(classOf[Contact]) diff Seq("id", "email")
val creatableFields = entityFields diff Seq("id")

val updatableFields = entityFields diff Seq("id", "email")

private def apply(c: SyntaxProvider[Contact])(rs: WrappedResultSet): Contact = apply(c.resultName)(rs)

private def apply(c: ResultName[Contact])(rs: WrappedResultSet): Contact = autoConstruct(rs, c)

def create(contact: Contact)(implicit session: DBSession = autoSession): Long = withSQL {
insert.into(Contact).namedValues( wrapEntity(contact, creatableFields):_ * )
def create(contact: Contact, definedFields: Seq[String] = entityFields)(implicit session: DBSession = autoSession): Long = withSQL {
insert.into(Contact).namedValues( wrapEntity(contact, definedFields intersect creatableFields):_ * )
}.updateAndReturnGeneratedKey().apply()

def update(contact: Contact, id: Long)(implicit session: DBSession = autoSession) = withSQL {
QueryDSL.update(Contact).set( wrapEntity(contact, updatableFields):_ * ).where.eq(column.field("id"), id)
def update(contact: Contact, id: Long, definedFields: Seq[String] = entityFields)(implicit session: DBSession = autoSession) = withSQL {
QueryDSL.update(Contact).set( wrapEntity(contact, definedFields intersect updatableFields):_ * ).where.eq(column.field("id"), id)
}.update.apply()

def find(id: Long)(implicit session: DBSession = autoSession): Option[Contact] = withSQL {
Expand Down
22 changes: 10 additions & 12 deletions modules/core/src/main/scala/org/sample/models/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ package object models {

//------------------------ Helpers -------------------------

val isNullable: PartialFunction[(_, Any), Boolean] = { case (_, value) =>
value == null
}

/** without inheritable fields */
def extractFieldNames(clazz: Class[_]): Seq[String] = {
clazz.getDeclaredFields.filterNot(_.isSynthetic).map(_.getName)
Expand Down Expand Up @@ -41,7 +37,7 @@ package object models {
type FieldWrappers = Map[Class[_], FieldWrapper[_]]

def wrapEntity[T](entity: T, allowedFields: Seq[String])(implicit wrappers: FieldWrappers) = {
convertToMap(entity).filterKeys(allowedFields.contains).filterNot(isNullable).map {
convertToMap(entity).filterKeys(allowedFields.contains).map {
case(fieldName, Some(fieldValue)) => column.field(fieldName) -> wrapField(fieldValue)
case(fieldName, None) => column.field(fieldName) -> None
case(fieldName, fieldValue) => column.field(fieldName) -> wrapField(fieldValue)
Expand Down Expand Up @@ -100,17 +96,19 @@ package object models {

val requiredFields: Seq[String]

val validators:Map[String, Seq[ (String, Any) => Errors ]]
val validators: Map[String, Seq[ (String, Any) => Errors ]]

def validate(entity: T, definedFields: Seq[String], checkForRequire: Boolean = false): Errors = {
val requireErrors = if(checkForRequire)
(requiredFields diff definedFields).map( Error(_, "required", "This field is required") )
else
NoErrors

def validate(entity: T, checkForRequire: Boolean = false): Errors = {
convertToMap(entity).flatMap {
case (fieldName, null) if checkForRequire && requiredFields.contains(fieldName) =>
Seq[Error](Error(fieldName, "required", "This field is required"))
case (fieldName, null) => NoErrors
convertToMap(entity).filterKeys(definedFields.contains).flatMap {
case (fieldName, None) => NoErrors
case (fieldName, Some(fieldValue)) => validate(fieldName,fieldValue)
case (fieldName, fieldValue) => validate(fieldName,fieldValue)
}.toSeq
}.toSeq ++ requireErrors
}

protected def validate[V](fieldName:String, fieldValue: V): Errors = {
Expand Down
21 changes: 15 additions & 6 deletions modules/core/src/test/scala/org/sample/models/ValidatorSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ class ValidatorSpec extends UnitSpec {

case class Sample(id: Long, email: String, name: Option[String])

object Sample {
val entityFields = extractFieldNames(classOf[Sample])
}

object SampleValidator extends Validator[Sample] {
val requiredFields = Seq("id", "email")

Expand All @@ -18,34 +22,39 @@ class ValidatorSpec extends UnitSpec {

it should "pass without required fields" in {
val sample = Sample(1L, null, None)
SampleValidator.validate(sample, checkForRequire = false) shouldBe NoErrors
val definedFields = Sample.entityFields diff Seq("email")
SampleValidator.validate(sample, definedFields, checkForRequire = false) shouldBe NoErrors
}

it should "not pass without required fields" in {
val sample = Sample(1L, null, None)
val errors = SampleValidator.validate(sample, checkForRequire = true)
val definedFields = Sample.entityFields diff Seq("email")
val errors = SampleValidator.validate(sample, definedFields, checkForRequire = true)
errors should have size 1
errors.head should matchPattern { case Error("email", "required", _) => }
}

it should "validate option field" in {
val sample = Sample(1L, null, Some("short"))
val errors = SampleValidator.validate(sample, checkForRequire = false)
val definedFields = Sample.entityFields diff Seq("email")
val errors = SampleValidator.validate(sample, definedFields, checkForRequire = false)
errors should have size 1
errors.head should matchPattern { case Error("name", "minLength", _) => }
}

it should "return several errors for one field" in {
val sample = Sample(1L, "short", None)
val errors = SampleValidator.validate(sample, checkForRequire = false)
val definedFields = Sample.entityFields
val errors = SampleValidator.validate(sample, definedFields, checkForRequire = false)
val expected = Seq(Error("email", "email", ""), Error("email", "minLength", ""))
errors should have size 2
errors.map(_.copy(message = "")) should contain theSameElementsAs expected
}

it should "return several errors for more then one fields" in {
val sample = Sample(null, "john@smith", Some("short"))
val errors = SampleValidator.validate(sample, checkForRequire = true)
val sample = Sample(NoId, "john@smith", Some("short"))
val definedFields = Sample.entityFields diff Seq("id")
val errors = SampleValidator.validate(sample, definedFields, checkForRequire = true)
val expected = Seq( Error("email", "email", ""), Error("id", "required", ""), Error("name", "minLength", "") )
errors should have size 3
errors.map(_.copy(message = "")) should contain theSameElementsAs expected
Expand Down

0 comments on commit b36895c

Please sign in to comment.