sedovalx / taxi

6 stars 3 forks source link

Поддержка параметров фильтрации в контроллерах #27

Closed sedovalx closed 9 years ago

sedovalx commented 9 years ago

У контроллеров доменных объектов есть метод read, который возвращает список объектов данного типа. Сейчас он не принимает никаких параметров. Нужна возможность задавать параметры фильтрации, которые будут приходить в виде queryParams GET-запроса. Фильтр может прийти на любое поле доменного типа, причем обязательных значений в фильтре нет. Например, для типа Пользователь:

GET /api/users?lastName=Иванов&role=Administrator

В будущем возможно появится необходимость в скоупах параметров GET-запросов, если кроме параметров фильтрации появятся другие. Гарантируется соответствие имен параметров фильтрации именам свойств запрашиваемого доменного типа. Т.е. можно попробовать сделать generic-алгоритм, если в Scala можно получить значение свойства по его строковому имени.

sedovalx commented 9 years ago

@v1pka давай все же посмотрим в сторону generic подхода к фильтрации с использованием рефлексии?

sedovalx commented 9 years ago

Можно наверное просто генерировать sql-запрос как тут http://slick.typesafe.com/doc/2.1.0/sql.html

Типа

val q = Q.queryNA[Account]("select * from account where ?")
val results = q("сгенерированное на основе json условие")

Это уже представляется гораздо более простым чем рефлексия.

sedovalx commented 9 years ago

А вот так я сегодня упоролся, если интересно:

import java.sql.Timestamp

import models.generated.Tables
import models.generated.Tables.AccountTable
import play.api.libs.json._
import reflect.runtime.universe._
import scala.slick.lifted.Column
import play.api.db.slick.Config.driver.simple._
case class PropertyInfo(name: String, tpe: reflect.runtime.universe.Type, isOption: Boolean)
val tableQuery = models.generated.Tables.AccountTable
val filter = Json.obj(
  "login" -> "admin"
)

// все столбцы таблицы и их типы
val properties = typeOf[models.generated.Tables.AccountTable].members.view
  .filter { p => !p.isMethod && p.typeSignature.baseClasses.count { c => c.name.toString == "Column" } > 0}
  .map { i =>
    val propertyName = i.name.toString
    var propertyType = (i.typeSignature match { case TypeRef(_, _, args) => args }).head
    val isOption = propertyType.baseClasses.count { c => c.name.toString == "Option" } > 0
    if (isOption)
      propertyType = (propertyType match { case TypeRef(_, _, args) => args }).head
    PropertyInfo(propertyName.trim, propertyType, isOption)
  }.toList
// для каждого столбца смотрим, есть ли соответствие по имени в json
val criterias = properties.map { p =>
  val filterValue = filter \ p.name match {
    case x: JsString => Some(x)
    case _ => p.name match {
      case x: String if x.endsWith("Id") => filter \ x.substring(0, x.length - 2) match {
        case x: JsString => Some(x)
        case _ => None
      }
      case _ => None
    }
  }

  filterValue match {
    case None => None// ничего не делаем
    case Some(x) =>
      print(p.name + ": " + x + " of type ")
      // если соответствие есть, то для каждого
      // используемого в модели примитивного типа столбца
      // генерируем функцию, которую потом
      // можно будет передать в tableQuery.filter(...)
      p.tpe match {
//        case t if t <:< typeTag[Int].tpe =>
//          println("Int")

        case t if t <:< typeTag[String].tpe =>
          println("String")
          val realX = x.toString()
          Some((row: Tables.AccountTable) => {
            val m = runtimeMirror(row.getClass.getClassLoader)
            val propTermSymb = typeOf[AccountTable].decl(TermName(p.name)).asTerm
            val im = m.reflect(row)
            val propFieldMirror = im.reflectField(propTermSymb)
            val rowPropValue: Any = propFieldMirror.get
            if (p.isOption){
              val col = rowPropValue.asInstanceOf[Column[Option[String]]]
              col === realX
            } else {
              val col = rowPropValue.asInstanceOf[Column[String]]
              col === realX
            }
          })
//        case t if t <:< typeTag[BigDecimal].tpe =>
//          println("BigDecimal")
//        case t if t <:< typeTag[Timestamp].tpe =>
//          println("Timestamp")
//        case t if t <:< typeTag[models.entities.Role.Role].tpe =>
//          println("Role")
        case t =>
          println(t)
          None
      }
  }
} filter { _.isDefined } map { _.get }

val a = tableQuery.filter(criterias.head).run
sedovalx commented 9 years ago
sedovalx commented 9 years ago

Для пользователей базовый функционал фильтрации работает.