DmitryTsepelev/store_model

Dynamic model types

23tux opened this issue · 6 comments

23tux commented

Is it possible to define the type of a store model based on another field? For example something like this:

class Product < ApplicationRecord
  attribute :configs, ->(product) { product.ticket? ? TicketConfig.to_array_type : BaseConfig.to_type }
end

I'm not sure if this is even a StoreModel related question, maybe it has more to do with how ActiveModel includes it's attributes.

So far, I have overwritten the getter of config to achieve this, but maybe there is a way to do it with build in functionality:

class Product < ApplicationRecord
  def configs
    type = ticket? ? TicketConfig.to_array_type : BaseConfig.to_type
    type.cast(super)
  end

  validate do
    config_errors = Array.wrap(config).reject(&:valid?)
    errors.add(:config, config_errors.join(", ")) if config_errors.any?
  end
end

Hi @23tux! Yeah, we have a OneOf for that.

23tux commented

Thanks for your answer, didn't know that!

But as far as I can see, you only have access to the json column itself, and not other columns of the record, am I right?

I also wonder how to decide if you want to use to_type or to_array_type? In the OneOf Example, only the base class is set inside the block, not the type itself.

and not other columns of the record

Yeah, this is how it works now, and there is a chance that we do not have access to the "parent" model at the moment of deserialization (so we won't be able to refer to other fields).

how to decide if you want to use to_type or to_array_type?

to_type is used when there is a single JSON (e.g., { color: "red" }), while to_array_type wraps array of JSONs (e.g., [{ color: "red" }, { color: "green" }])

23tux commented

@DmitryTsepelev thanks, I've managed to inject the type into the json column, so this works.

One last question: Is it possible to use the ActiveRecord::Type::Json type in StoreModel.one_of?

I tried to use

OneOfConfigurations = StoreModel.one_of do |json|
  json["_model"]&.constantize || ActiveRecord::Type::Json
end

but this throws an error

StoreModel::Types::ExpandWrapperError: ActiveRecord::Type::Json is an invalid model klass

For some models I have to provide a fallback if the underlying data doesn't match the StoreModel, and otherwise ActiveSupport throws a

ActiveModel::UnknownAttributeError: unknown attribute 'foo' for MyModel.

StoreModel::Types::ExpandWrapperError is thrown when the type that returned from the block does not include a StoreModel::Model module (code is here). I guess it might be possible to define a custom class including that module that behaves like ActiveRecord::Type::Json.

unknown attribute 'foo'

Interesting, sounds like JSON stored in database does not have foo field, while it's defined in the StoreModel class, am I right? I'm asking because we do handle the opposite scenario using unknown attributes.

23tux commented

@DmitryTsepelev thanks for the hint to the unknown attributes! That solves indeed my problem, I just provide a generic fallback model.