Абстрагирование операций 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. Я в любом случае представляю это здесь.
Я попробовал пару вещей:
- декларирование
val table
в аннотацииBaseEntity
, Это привело меня к довольно нелепому беспорядку проверки типов.val table: Table[T]
работает только тогда, когда я также определяю T как тип, и тогда дочерние классы вызывают ошибки компилятора, когда они пытаются предоставить таблицу другого типа. - Напишите базовый класс, который ожидает, что таблицы будут переданы в качестве аргумента каждой функции. Это означает, что каждая модель все еще должна вызывать свой родительский метод для передачи
table
аргумент. - Я взломал этот пост, который использует
TypeTags
, Тем не менее, плакат не предоставил мне достаточно информации, чтобы понять его реализацию. В вышеупомянутом посте 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
Обратите внимание, что это вызовет исключение, если соответствующие таблицы не найдены.