Определить пользовательскую сериализацию с помощью Casbah / Salat или делегировать сериализацию для члена? - PullRequest
3 голосов
/ 02 марта 2012

Я нахожусь в процессе изучения Scala для нового проекта Rails. Я определил тип, который будет использоваться в ряде моих моделей, которые в основном можно рассматривать как набор «атрибутов». По сути, это просто оболочка для хэш-карты, которая делегирует ему большую часть своих обязанностей:

case class Description(attributes: Map[String, String]) {

  override def hashCode: Int = attributes.hashCode

  override def equals(other: Any) = other match {
    case that: Description => this.attributes == that.attributes
    case _ => false
  }
}

Итак, я бы определил класс модели с Description, что-то вроде:

case class Person(val name: String, val description: Description)

Однако, когда я сохраняю Person с SalatDAO, я получаю документ, который выглядит следующим образом:

{
  name : "Russell",
  description: 
  {
    attributes: 
    {
      hair: "brown",
      favourite_color: "blue"
    }
  }
}

Когда на самом деле мне не нужно вложение тега attributes в тег description - то, что я на самом деле хочу, это:

{
  name : "Russell",
  description: 
  {
    hair: "brown",
    favourite_color: "blue"
  }
}

Я не пробовал, но я считаю, что мог бы заставить это работать, если бы заставил Description расширить Map вместо того, чтобы содержать его, но я бы предпочел нет, потому что Description не тип Map, это то, что имеет некоторое поведение Map, а также другое его поведение, которое я добавлю позже. Композиция над наследованием и пр.

Итак, мой вопрос, как я могу сказать Salat (или Casbah, я на самом деле немного не уверен относительно того, что делает преобразование, поскольку я только начал их использовать), как сериализовать и десериализовать Description учебный класс? В уроке касба здесь написано:

Также можно создавать собственные сериализаторы пользовательских типов и deserializers. См. Пользовательские сериализаторы и десериализаторы.

Но эта страница, кажется, не существует. Или я поступаю неправильно? Есть ли действительно простой способ указать, что это то, что я хочу, аннотации или что-то в этом роде? Или я могу просто каким-то образом делегировать сериализацию в карту атрибутов?

РЕДАКТИРОВАТЬ: После просмотра источника помощника по преобразованию JodaTime я попробовал следующее, но пока не получилось заставить его работать:

import org.bson.{ BSON, Transformer }
import com.mongodb.casbah.commons.conversions.MongoConversionHelper

object RegisterCustomConversionHelpers extends Serializers
  with Deserializers {
  def apply() = {
    super.register()
  }
}

trait Serializers extends MongoConversionHelper
  with DescriptionSerializer {

  override def register() = {
    super.register()
  }
  override def unregister() = {
    super.unregister()
  }
}

trait Deserializers extends MongoConversionHelper {
  override def register() = {
    super.register()
  }
  override def unregister() = {
    super.unregister()
  }
}

trait DescriptionSerializer extends MongoConversionHelper {
  private val transformer = new Transformer {
    def transform(o: AnyRef): AnyRef = o match {
      case d: Description => d.attributes.asInstanceOf[AnyRef]
      case _ => o
    }
  }

  override def register() = {
    BSON.addEncodingHook(classOf[Description], transformer)
    super.register()
  }
}

Когда я звоню RegisterCustomConversionHelpers(), затем сохраняю Person Я не получаю никаких ошибок, это просто не имеет никакого эффекта, сохраняя документ так же, как и всегда. Это также, кажется, довольно много для того, что я хочу.

Ответы [ 2 ]

4 голосов
/ 02 марта 2012

Сопровождающий Салат здесь.

Я не понимаю значение Описания здесь как обертки.Он оборачивает карту атрибутов, переопределяет значения по умолчанию equals и impl хеш-кода класса case - что кажется ненужным, так как impl все равно делегируется карте, и это именно то, что в любом случае делает класс case - и вводит дополнительный уровень косвенности длясериализованный объект.

Вы рассмотрели только:

case class Person(val name: String, val description: Map[String, String])

Это будет делать именно то, что вы хотите из коробки.

В другой ситуации я бы порекомендовал простой псевдоним типа, но, к сожалению, Salatне может поддерживать псевдонимы типов сейчас из-за некоторых проблем с тем, как они изображены в маринованных сигнатурах Scala.

(Вы, вероятно, опускаете это из своего примера для краткости, но для вашей модели Mongo рекомендуется иметьполе _id - если вы этого не сделаете, драйвер Mongo Java предоставит его вам)

В тестовом пакете salat-core есть рабочий пример настраиваемого перехвата BSON (он обрабатывает java.net.URL).Может быть, ваш хук не работает просто потому, что вы не регистрируете его в нужном месте?Но все же я бы порекомендовал избавиться от Description, если оно не добавляет какое-то значение, которое не видно из вашего примера выше.

0 голосов
/ 02 марта 2012

Основываясь на ответе @prasinous, я решил, что это будет не так просто, поэтому я немного изменил свой дизайн на следующий, что в значительной степени дает мне то, что я хочу.Вместо того, чтобы сохранять Description как поле, я сохраняю ванильную карту, а затем смешиваю черту Described с классами моделей, которые я хочу получить описание, которое автоматически преобразует карту в Description при создании объекта.Буду признателен, если кто-нибудь может указать на какие-либо очевидные проблемы с этим подходом или какие-либо предложения по улучшению.

class Description(val attributes: Map[String, String]){
  //rest of class omitted
}

trait Described {
  val attributes: Map[String, String]
  val description = new Description(attributes)
}

case class Person(name: String, attributes: Map[String, String]) extends Described
...