require 'enumerable_observer' require 'active_support/concern' module JSONAPIonify::Structure module Helpers module ObjectDefaults extend ActiveSupport::Concern include EnumerableObserver module ClassMethods # Forces the setter to a type def implements(key, as:, **opts) type_of! key, must_be: as, allow_nil: true track_implementation key, as, **opts end # Forces the setter to the collection type # Allows unset types to expose collection and type_map methods def collects(key, as:, **opts) type_of! key, must_be: as track_collection key, as, **opts end # implements_or_collects(:data, implements: A, collects: B, if: ->(obj){ obj.has_key? :attributes }) # implements_or_collects(:data, implements: C, collects: D, unless: ->(obj){ obj.has_key? :attributes }) def collects_or_implements(key, implements:, collects:, allow_nil: false, **opts) allowed = [implements, collects] allowed << NilClass if allow_nil type_of! key, must_be: allowed track_collection key, collects, **opts track_implementation key, implements, **opts end alias_method :implements_or_collects, :collects_or_implements # Defaults def default(key, &block) after_initialize do self[key] ||= instance_eval(&block) end end private def track_implementation(key, klass, **opts) (implementations[key] ||= {})[klass] = opts end def track_collection(key, klass, **opts) (collections[key] ||= {})[klass] = opts end end included do include JSONAPIonify::InheritedAttributes # Class Attributes attr_reader :unset inherited_hash_attribute :implementations, :collections before_initialize do @unset = {} newly_set = unset.select { |_, v| v.present? } newly_set.each do |k, v| self[k] = v end end end def [](k) if has_key? k super elsif collections[k] @unset[k] ||= [].tap do |ary| observe(ary).added { self[k] = ary } end else nil end end def []=(k, v) unset.delete_if { |unset_key, _| unset_key == k } super k, coerce_value(k, v) end private def coerce_value(k, v) if implementations[k] && v.is_a?(Hash) coerce_implementation(k, v) elsif collections[k] && v.is_a?(Array) coerce_collection(k, v) else v end end def coerce_implementation(k, v) klass, * = (implementations[k] || {}).find do |_, options| JSONAPIonify::Continuation.new(**options).check(v) { true } end return v unless klass && !v.is_a?(klass) klass.new(v) end def coerce_collection(k, v) klass, * = (collections[k] || {}).find do |_, options| v.all? do |obj| JSONAPIonify::Continuation.new(**options).check(obj) { true } end end return v unless klass && !v.is_a?(klass) klass.new(v) end end end end