2017-02-22 25 views
2

Я изучаю scala, playframework и slick, но я нашел проблему. Я пытаюсь создать простой CRUD с контроллерами списков, которые получают настраиваемое поле фильтра, некоторые данные разбивки на страницы (размер и число страниц) и Seq строковых кортежей с именем поля и порядком (asc или desc) и все работает нормально, за исключением порядка по seq, я не могу сделать заказ динамическим.Динамический порядок по scala slick с несколькими столбцами

Я получил базовую структуру от Scadiddle blog. Итак, основной код выглядит следующим образом:

У меня есть основной цвет модели:

case class Color(
    id: Int, 
    name: String) 

Это простое определение таблицы:

class ColorsTable(tag: Tag) extends Table[Color](tag, "color") { 
     def id = column[Int]("id", O.PrimaryKey, O.AutoInc) 
     def name = column[String]("name") 
     def * = (id, name) <> ((Color.apply _).tupled, Color.unapply) 
     } 

В моем репо у меня есть метод поиска :

def findAll(searchTerm: Option[String], page: Int, top: Int, sortsBy: Seq[(String, SortDirection)]): Future[Seq[Color]] = { 
    var query = searchTerm match { 
     case Some(term) => colors.filter(_.name like s"%$term%") 
     case _ => colors 
    } 
    val offset = (page - 1) * top 
    // This is building the sort clause, matching on each column in the sort 
    sortsBy.foreach { 
     sortTuple => 
     val (sortColumn, sortDirection) = sortTuple 
     query = query.sortBy(sortColumn match { 
      case "id" => if (sortDirection == Desc) _.id.desc else _.id.asc 
      case _ => if (sortDirection == Desc) _.name.desc else _.name.asc 
     }) 
    } 
    // The "list" method actually executes the query 
    val colorsQuery = query.drop(offset).take(top).result 

    db.run(colorsQuery) 
    } 

Проблема заключается в том, что я вызываю метод поиска с помощью этой последовательности:

val sortsBy = Seq[(String, SortDirection)](("name", Desc), ("id", Asc)) 
    colorService.getColors(None, 1, 10, sortsBy).map(colorList => Ok(Json.toJson(colorList))) 

Этот запрос генерируется:

select "id", "name" from "color" order by "id", "name" desc limit 10 offset 0 

Как вы можете видеть, порядок SortBy был перевернутый (идентификатор, а затем имя, а не имя и идентификатор в виде последовательности).

Если я использую кортеж вместо этого Еогеаспа порядок уважается:

query = query.sortBy(
     s => (s.name.desc, s.id.asc) 
    ) 

Но нет никакого способа для создания динамического размера кортежа. Для того, чтобы добавить еще несколько confusionm другую вещь, которая вызывает у меня проблемы в пятно документации this part:

Имейте в виду, что один ORDER BY с несколькими столбцами не эквивалентно многократным .sortBy но требует к одному .sortBy звоните проходящий кортеж

Итак, на самом деле можно ли использовать foreach и объединить заказы? Или это потому, что это ограничение, что порядок отменяется?

В случае, если для сортировки могут использоваться только кортежи, как я могу достичь порядка динамического размера?

PD: Спасибо за внимание и извините за плохой английский

EDIT:

Спасибо за быстрый ответ, я попробовал ваш код и выглядит хорошо, к сожалению, я почти не идея как это работает :((scala - очень хороший, но трудный для изучения язык: S).

Когда я увидел Higher Kinded Type Should be Enabled, я просто испугался, ища ответы this не дал мне большой надежды, чтобы получить легкое понимание, надеюсь, когда я закончу Программирование в Scala, 3-я книга, у меня будет больше понимания и знаний о том, что черт возьми.

Еще один вопрос, является ли это эквивалентом делать несколько вызовов sortBy? как это сравнить с использованием кортежа? Я до сих пор путает эту часть скользких документов:

один ORDER BY с несколькими столбцами не эквивалентен многократным .sortBy но вызывает к одному вызову .sortBy прохождения кортежа

Я проверил метод и добавив обратное к seq, чтобы он работал нормально, конечно, не как функциональный и красивый, как ваш код, поэтому я буду использовать ваше предложение и работать над тем, чтобы сделать остальные фильтры с помощью помощников и избегать vars , (да, в другой части я все еще использую var, но я сделаю все более приятным, когда узнаю больше о Scala).

Исповедь: после более чем восьми лет программирования на нескольких языках (от JavaScript до Java, C#, Python и других), я должен повторить, Scala выглядит красивым, но очень сложным языком, но я не буду отказываться от обучения это

ответ

3

Давайте определим DynamicSortBySupport помощник

object DynamicSortBySupport { 
    import slick.ast.Ordering.Direction 
    import slick.ast.Ordering 
    import slick.lifted.Query 
    import slick.lifted.ColumnOrdered 
    import slick.lifted.Ordered 
    type ColumnOrdering = (String, Direction) //Just a type alias 
    trait ColumnSelector { 
    val select: Map[String, Rep[_]] //The runtime map between string names and table columns 
    } 
    implicit class MultiSortableQuery[A <: ColumnSelector, B, C[_]](query: Query[A, B, C]) { 
    def dynamicSortBy(sortBy: Seq[ColumnOrdering]): Query[A, B, C] = 
     sortBy.foldRight(query){ //Fold right is reversing order 
     case ((sortColumn, sortOrder), queryToSort) => 
      val sortOrderRep: Rep[_] => Ordered = ColumnOrdered(_, Ordering(sortOrder)) 
      val sortColumnRep: A => Rep[_] = _.select(sortColumn) 
      queryToSort.sortBy(sortColumnRep)(sortOrderRep) 
     } 
    } 
} 

и пересмотреть свой Table добавив "сортировки карту"

class ColorsTable(tag: Tag) extends Table[Color](tag, "color") with DynamicSortBySupport.ColumnSelector { 
    def id = column[Int]("id", O.PrimaryKey, O.AutoInc) 
    def name = column[String]("name") 
    def * = (id, name) <> ((Color.apply _).tupled, Color.unapply) 
    val select = Map(
    "id" -> (this.id), 
    "name" -> (this.name) 
) 
} 

и, наконец, использовать все это в вашем коде:

object FindAll extends App { 
    import DynamicSortBySupport._ 
    import slick.ast.Ordering.Direction 
    import slick.ast.Ordering 
    object colors extends TableQuery(new ColorsTable(_)) 
    val sortsBy = Seq[(String, Direction)](("name", Ordering.Desc), ("id", Ordering.Asc)) //Replaced 
    val db = Database.forURL("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", driver="org.h2.Driver") //Just for testing 
    findAll(sortsBy) 
    def findAll(sortsBy: Seq[(String, Direction)]): Future[Seq[Color]] = { 
    val query = colors.dynamicSortBy(sortsBy).result 
    db.run(query) 
    } 
} 

Примечания и комментарии:

  • Среда карта Map[String, Rep[_]] между именами строк и столбцов таблицы могут быть улучшены с обработка ошибок (теперь она просто бросает исключение во время выполнения, которое должно управляться должным образом) или автоматический вывод из самого определения таблицы;
  • Я заменил SortDirection надлежащим образом slick.ast.Ordering.Direction, не стесняйтесь писать конвертер;
  • Вы также можете написать помощник для дополнительных фильтров, что-то вроде filterOption;
  • Обратите внимание на использование foldRight для обратного сортировки;
  • Если можно избежать var с и быть функциональным :-)
  • Если вы хотите иметь ColumnSelector за пределами слоя данных (это хорошая практика, на самом деле) можно переписать требуя беспрекословно что-то вроде ColumnSelector[T];
+0

Я только что пробовал ваш код и отлично работает, я редактировал свой вопрос, чтобы добавить некоторые замечания, потому что раздел комментариев очень ограничен – rekiem87