Хэш монгоидного поля как структура

Можно ли настроить монгоид field десериализовать как Struct а не Hash? ( со значениями по умолчанию)

Мой вариант использования: компания с планом подписки, хранящимся в моей модели в виде хэша.

Ранее как хеш

class Company
  include Mongoid::Document
  field :subscription, type: Hash, default: {
      ends_at: 0,
      quantity: 0,
      started_at: 0,
      cancelled: false,
    }

Я хотел бы не писать Company.first.subscription[:ends_at]Я бы лучше написал Company.subscription.ends_at

Я подумал, что что-то вроде следующего будет работать лучше

class Company
  include Mongoid::Document
  field :subscription_plan, type: Struct, default: Struct.new(
    :ends_at, :quantity, :started_at, :cancelled
  ) do
    def initialize(
      ends_at: nil, 
      quantity: 0,
      starts_at: nil,
      cancelled: false
    ); super end
  end
end

Было бы еще лучше, если бы план мог быть определен в классе

class SubscriptionPlan < Struct.new(
  ends_at, :quantity, :starts_at, :cancelled
) do
  def initialize(
  ends_at: nil, 
  quantity: 0,
  starts_at: nil,
  cancelled: false
); super; end
end

class Company
  field :subscription_plan, type: SubscriptionPlan, default: SubscriptionPlan.new
end

Как я могу заставить это работать?

2 ответа

Возьмите это с крошкой соли, поскольку я никогда не использовал ни MongoDB, ни Mongoid. Тем не менее, поиск в Google для "нестандартного типа" привел меня к этой документации.

Вот адаптированная версия примера пользовательского типа:

class SubscriptionPlan

  attr_reader :ends_at, :quantity, :started_at, :cancelled

  def initialize(ends_at = 0, quantity = 0, started_at = 0, cancelled = false)
    @ends_at = ends_at
    @quantity = quantity
    @started_at = started_at
    @cancelled = cancelled
  end

  # Converts an object of this instance into a database friendly value.
  def mongoize
    [ends_at, quantity, started_at, cancelled]
  end

  class << self

    # Get the object as it was stored in the database, and instantiate
    # this custom class from it.
    def demongoize(array)
      SubscriptionPlan.new(*array)
    end

    # Takes any possible object and converts it to how it would be
    # stored in the database.
    def mongoize(object)
      case object
      when SubscriptionPlan then object.mongoize
      when Hash then SubscriptionPlan.new(object.values_at(:ends_at, :quantity, :started_at, :cancelled)).mongoize
      else object
      end
    end

    # Converts the object that was supplied to a criteria and converts it
    # into a database friendly form.
    def evolve(object)
      case object
      when SubscriptionPlan then object.mongoize
      else object
      end
    end
  end
end

class Company
  include Mongoid::Document
  field :subscription, type: SubscriptionPlan, default: SubscriptionPlan.new
end

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

Обратите внимание, что по умолчанию SubscriptionPlan будет предоставлен каждой компании по умолчанию. Это может привести к некоторым странным ошибкам, если вы измените план по умолчанию в одной компании.

Я понял, что просто переопределяю вложенный документ без идентификатора. В конце концов я решил перейти на обычный встроенный документ для моего subscription, так как наличие дополнительного поля ID не проблема, и я получаю mongoid области в качестве бонуса. Я всегда могу добавить Mongoid::Attributes::Dynamic в случае, если я хочу поддержать любой ключ.

Тем не менее вопрос и другой ответ остается актуальным для того, кто хочет создавать свои типы.

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