Slick: составные, необязательные столбцы для составного необязательного типа - PullRequest
1 голос
/ 26 июня 2019

Это дано:

case class Money(value: BigDecimal, currency: Currency)
trait Currency
case class EUR ( ... ) extends Currency
... and so on

Я хочу смоделировать необязательный тип Money, который содержит value часть (BigDecimal) и Currency часть (Enum через Enumeratum).

Чтобы отобразить это в БД, у меня есть два необязательных столбца:

    private def moneyOptionalValue: Rep[Option[BigDecimal]]  = column[Option[BigDecimal]]("money_value")     
    private def moneyOptionalCurrency: Rep[Option[Currency]] = column[Option[Currency]]("money_currency")    

Желаемое состояние будет иметь это:

   def money: Rep[Option[Money]] = ... 

Я пробовал с этим подходом, как описано в http://slick.lightbend.com/doc/3.1.0/userdefined.html#using-custom-record-types-in-queries

    private implicit def myCurrencyType: BaseColumnType[Currency] = MappedColumnType.base[Currency, String](
      c => c.shortName,
      s => Currency.withNameUppercaseOnly(s.toUpperCase)
    )

    case class LiftedMoney(value: Rep[BigDecimal], currency: Rep[Currency])
    case class LiftedMoneyOptional(a: Rep[Option[BigDecimal]], b: Rep[Option[Currency]])
    case class MoneyOptional(a: Option[BigDecimal], b: Option[Currency])

    // custom case class mapping
    implicit object MoneyOptionalShape extends CaseClassShape(LiftedMoneyOptional.tupled, MoneyOptional.tupled)
    implicit object MoneyShape         extends CaseClassShape(LiftedMoney.tupled, Money.tupled)

    implicit object MoneyToOptional
        extends Isomorphism[Option[Money], MoneyOptional](
          {
            case Some(m) => MoneyOptional(Some(m.value), Some(m.currency))
            case None    => MoneyOptional(None, None)
          }, {
            case MoneyOptional(Some(v), Some(c)) => Some(Money(v, c))
            case MoneyOptional(_, _)             => None
          }
        )

    private def moneyOptionalValue: Rep[Option[BigDecimal]]  = column[Option[BigDecimal]]("money_value")
    private def moneyOptionalCurrency: Rep[Option[Currency]] = column[Option[Currency]]("money_currency")

    def moneyOptional: Rep[MoneyOptional] =
      moneyOptionalValue.zip(moneyOptionalCurrency).mapTo[MoneyOptional]

    def moneyOptional1: MappedProjection[MoneyOptional, (Option[BigDecimal], Option[Currency])] =
      moneyOptionalValue.zip(moneyOptionalCurrency).mapTo[MoneyOptional]


   // this compiles, if its not optional, but would break at runtime, when null values are in the columns:
    private def moneyValue: Rep[BigDecimal]  = column[BigDecimal]("money_value") // this column can be nullable!
    private def moneyCurrency: Rep[Currency] = column[Currency]("money_currency") // // this column can be nullable!

    def money: Rep[Money] = moneyValue.zip(moneyCurrency).value.mapTo[Money]

// I also tried this, but cannot compile my code with that for some reason

    def optionMoney: Rep[Option[Money]] = RepOption[Money](money, money.toNode)

Любая помощь будет принята с благодарностью!

1 Ответ

0 голосов
/ 22 июля 2019

А как насчет этого?

  def money = (moneyOptionalValue, moneyOptionalCurrency) <> [Option[Money]](
    {
      case (value,currency) if value.isDefined && currency.isDefined => Some(Money(value.get, currency.get))
      case _ => None
    },
    (mapped:Option[Money]) => mapped.map( m => Some(Some(m.value), Some(m.currency))).getOrElse(Some(None,None))
  )
...