Абстрагирование операций CRUD с использованием Squeryl и базового класса

Я присоединился к гордым рядам людей, пытающихся использовать Squeryl в качестве моей платформы ORM для веб-приложения. (Для справки, я использую Scalatra в качестве реальной веб-инфраструктуры - но я не думаю, что это вопрос Scalatra.) Это означает, что я пополнил ряды людей, пытающихся создать эффективный уровень абстракции для СУШКИ нашего общие операции. Например, довольно часто можно увидеть такие примеры:

// First Model

package com.myproj
import com.myproj.Schema

class Foo() extends KeyedEntity {
  val id = 0
  def getAll() = { from(Schema.Foo)(s => select(s)) }    
}

// Different Model

package com.myproj
import com.myproj.Schema

class Bar() extends KeyedEntity {
  val id = 0    
  def getAll() = { from(Schema.Boo)(s => select(s)) }   
}

Так что, с одной стороны, я копаю, насколько лаконичен синтаксис Squeryl. С другой стороны: это довольно повторяющееся. То, что я хочу, это что-то вроде:

// Base

class BaseEntity extends KeyedEntity {
  val id = 0
  def getAll() = { from(table)(s => select(s)) }
}

// New model

class Foo extends BaseEntity

// New model

class Bar extends BaseEntity

Так что у меня в основном это работает. простирающийся KeyedEntity довольно прямо вперед. Есть только одна проблема: как, черт возьми, вы определяете таблицу в BaseEntity такие классы, которые расширяют его, могут получить к нему доступ? Честно говоря, это может быть принципиальным вопросом о том, что у меня недостаточно глубокое понимание системы типов Scala. Я в любом случае представляю это здесь.

Я попробовал пару вещей:

  1. декларирование val table в аннотации BaseEntity, Это привело меня к довольно нелепому беспорядку проверки типов. val table: Table[T] работает только тогда, когда я также определяю T как тип, и тогда дочерние классы вызывают ошибки компилятора, когда они пытаются предоставить таблицу другого типа.
  2. Напишите базовый класс, который ожидает, что таблицы будут переданы в качестве аргумента каждой функции. Это означает, что каждая модель все еще должна вызывать свой родительский метод для передачи table аргумент.
  3. Я взломал этот пост, который использует TypeTags, Тем не менее, плакат не предоставил мне достаточно информации, чтобы понять его реализацию.
  4. В вышеупомянутом посте SO есть комментарий, в котором предлагается метод org.squeryl.Schema.findTablesFor. Снова с потенциальными проблемами новичка: я не продвинулся с тем, как осуществить это как ответ. Я пробовал такие вещи, как:

    class BaseEntity {
    val table = findTablesFor (this)
    }

Но потом я получаю Iterable, и я немного не уверен, что с ним делать.

Так. Есть ли "правильный" способ сделать это? Конечно, есть чистый способ переместить операции CRUD в базовый класс - я просто не могу понять это.

редактировать

Итак, вот что я получил, используя Squeryl 9.5-6:

// Schema
package com.myproj.schema
object MySchema extends Schema {
  val foo = table[Foo]("foos")
  val bar = table[Bar]("bars")
}

// BaseEntity
package com.myproj.models
import com.myproj.schema.MySchema
abstract class BaseEntity extends Keyedentity[Long] {
  val id: Long = 0
  val table = MySchema.findTablesFor(this).head
}

// Class
package com.myproj.models
case class Foo (
  val name: String,
  val extra: Option[String]
) extends BaseEntity {
  def this() = this("", None)
}

Настройте как это. findTablesFor всегда возвращает пустой итератор. Он компилируется, но выдает ошибки во время выполнения при попытке вызвать head на пустом итераторе (как вы и сказали). Обработка ошибки не является проблемой; не в состоянии найти стол вроде.

Мысли?

1 ответ

Решение

val table: Таблица [T] работает только в том случае, если я также определяю T как тип, а затем дочерние классы вызывают ошибки компилятора, когда они пытаются предоставить таблицу другого типа.

Вы можете сделать это с помощью самопечатания. Я не уверен, что рекомендовал бы это, но это должно работать:

class BaseEntity[T] {
  self: T =>

  val table: Table[T]

}

Тогда ваша реализация будет выглядеть так:

class MyEntity extends BaseEntity[MyEntity]

Использование findTablesFor, вероятно, является лучшим решением. Ничто не мешает вам отобразить класс на несколько таблиц в схеме Squeryl. Вы могли бы иметь:

val tableA = table[MyEntity]

val tableB = table[MyEntity]

Отсюда причина возврата Iterable для всех соответствующих таблиц. Если вы знаете, что не собираетесь этого делать, вы можете просто использовать первый результат:

val table = MySchema.findTableFor(this).head

Обратите внимание, что это вызовет исключение, если соответствующие таблицы не найдены.

Другие вопросы по тегам