All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.IO.scala Maven / Gradle / Ivy

The newest version!
package com.github.aselab.activerecord.io

import com.github.aselab.activerecord._
import validations._
import inner._
import reflections._

trait IO extends Validatable { this: ProductModel =>
  import ReflectionUtil._

  def toMap: Map[String, Any] = _companion.fields.flatMap { f =>
    this.getOption[Any](f.name).map(f.name -> _)
  }.toMap

  def toFormValues: Map[String, String] = toFormValues(None)

  def toFormValues(prefix: Option[String]): Map[String, String] = {
    def serialize(c: Class[_], value: Any, key: String): Map[String, String] =
      if (classOf[IO].isAssignableFrom(c)) {
        value.asInstanceOf[IO].toFormValues(Some(key))
      } else {
        Map(FormConverter.get(c).map(key -> _.serialize(value)).getOrElse(
          throw ActiveRecordException.unsupportedType(key)
        ))
      }

    toMap.flatMap { case (k, v) =>
      val info = _companion.fieldInfo(k)
      val key = prefix.map(FormUtil.join(_, k)).getOrElse(k)

      if (info.isSeq) {
        v.asInstanceOf[Seq[_]].zipWithIndex.flatMap { case (value, index) =>
          serialize(info.fieldType, value, FormUtil.join(key, index))
        }
      } else {
        serialize(info.fieldType, v, key)
      }
    }
  }

  def assign(data: Map[String, Any]): Unit = {
    data.foreach{ case (k, v) =>
      val info = _companion.fieldInfo(k)
      val value = if (info.isOption) Some(v) else v
      this.setValue(k, value)
    }
  }

  def assignFormValues(data: Map[String, String]): Unit = {
    assign(_companion.fieldInfo.flatMap {
      case (name, info) =>
        def converter = FormConverter.get(info.fieldType).getOrElse(
          throw ActiveRecordException.unsupportedType(name)
        )

        def deserialize(data: Map[String, String], key: String) = if (info.isModel) {
          val companion = classToCompanion(info.fieldType).asInstanceOf[FormSupport[ProductModel with IO]]
          val map = data.collect {
            case (k, v) if k.startsWith(key + "[") => FormUtil.shift(k) -> v
          }
          if (!(info.isOption && map.nonEmpty)) Some(companion.bind(map)) else None
        } else {
          data.get(key).collect {
            case v if !(info.isOption && v.isEmpty) => converter.deserialize(v)
          }
        }

        try {
          if (info.isSeq) {
            val dataList = Stream.from(0).map(i => data.collect {
              case (k, v) if k.startsWith("%s[%d]".format(name, i)) => FormUtil.shift(k) -> v
            }.toMap).takeWhile(_.nonEmpty)
            Some(name -> dataList.zipWithIndex.flatMap {
              case (d, i) => deserialize(d, i.toString)
            }.toList)
          } else {
            deserialize(data, name).map(name -> _)
          }
        } catch {
          case e: Throwable =>
            this.errors.add(name, Validator.ERROR_PREFIX + "invalid")
            None
        }
    })
  }

  def formErrors: Seq[ValidationError] = {
    val nestErrors = _companion.validatableFields.flatMap { f =>
      f.toSeq[IO](this).zipWithIndex.flatMap { case (m, i) =>
        m.formErrors.map {
          case e if e.isGlobal => e.copy(model = this.getClass, key = f.name + e.key)
          case e if f.isSeq => e.copy(key = FormUtil.join(f.name, i, e.key))
          case e => e.copy(key = FormUtil.join(f.name, e.key))
        }
      }
    }
    errors.toSeq ++ nestErrors
  }

  override def validate() = super.validate && formErrors.isEmpty
}

object FormUtil {
  /** a[b][c] => b[c] */
  def shift(s: String): String = s.replaceFirst("""[^\[]+\[([^\[\]]+)\]""", "$1")

  /** a[b][c] => a, b, c */
  def split(s: String): Seq[String] = s.replaceAll("""\[([^\[\]]*)\]""", ",$1").split(",")

  /** a, b, c[d] => a[b][c][d] */
  def join(a: String, b: Any*) =
    a + b.flatMap(s => split(s.toString)).map("[%s]".format(_)).mkString
}

trait FormSupport[T <: ProductModel with IO] { self: ProductModelCompanion[T] =>
  import ReflectionUtil._

  type C = ProductModelCompanion[ProductModel with IO] with FormSupport[ProductModel with IO]

  def isRequired(name: String): Boolean = {
    def inner(c: C, names: Seq[String]): Boolean = {
      (names.headOption, names.tail) match {
        case (Some(name), tail) =>
          c.fieldInfo.get(name).map { info =>
            if (tail.isEmpty)
              info.isRequired
            else
              inner(classToCompanion(info.fieldType).asInstanceOf[C], tail)
          }.getOrElse(false)
        case _ => false
      }
    }
    inner(this.asInstanceOf[C], FormUtil.split(name).filterNot(s => s.isEmpty || s.matches("^[0-9]+$")))
  }

  def bind(data: Map[String, String])(implicit source: T = self.newInstance): T = {
    source.assignFormValues(data)
    source
  }

  def unbind(m: T): Map[String, String] = m.toFormValues
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy