Module: FIRM::Serializable

Included in:
ID
Defined in:
lib/firm/serializable.rb,
lib/firm/serialize/id.rb,
lib/firm/serialize/core.rb,
lib/firm/serializer/xml.rb,
lib/firm/serializer/json.rb,
lib/firm/serializer/yaml.rb

Overview

Mixin module providing (de-)serialization support for user defined classes.

Defined Under Namespace

Modules: AliasManagement, Aliasing, CoreExt, JSON, SerializeClassMethods, SerializeInstanceMethods, XML, YAML Classes: Exception, ID, MethodResolver, Property

Constant Summary collapse

TLS_VARS_KEY =
:firm_tls_vars.freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.[](format) ⇒ Object

Return a serialization formatting engine

Parameters:

  • format (Symbol, String)

    format id

Returns:

  • (Object)

    formatting engine



175
176
177
178
# File 'lib/firm/serializable.rb', line 175

def [](format)
  ::Kernel.raise ArgumentError, "Format #{format} is not supported." unless formatters.has_key?(format.to_s.downcase)
  formatters[format.to_s.downcase]
end

.default_formatSymbol

Return the default output format symbol id (:json, :yaml, :xml). By default returns :json.

Returns:

  • (Symbol)


183
184
185
# File 'lib/firm/serializable.rb', line 183

def default_format
  @default_format ||= :json
end

.default_format=(format) ⇒ Symbol

Set the default output format.

Parameters:

  • format (Symbol)

    Output format id. By default :json, :yaml and :xml (if nokogiri gem is installed) are supported.

Returns:

  • (Symbol)

    default format



190
191
192
# File 'lib/firm/serializable.rb', line 190

def default_format=(format)
  @default_format = format
end

.deserialize(source, format: Serializable.default_format) ⇒ Object

Deserializes object from source data

Parameters:

  • source (IO, String)

    source data (stream)

  • format (Symbol, String) (defaults to: Serializable.default_format)

    data format of source

Returns:

  • (Object)

    deserialized object



540
541
542
# File 'lib/firm/serializable.rb', line 540

def self.deserialize(source, format: Serializable.default_format)
  self[format].load(::IO === source || source.respond_to?(:read) ? source.read : source)
end

.formattersObject



156
157
158
# File 'lib/firm/serializable.rb', line 156

def formatters
  @formatters ||= {}
end

.included(base) ⇒ Object



571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
# File 'lib/firm/serializable.rb', line 571

def self.included(base)
  ::Kernel.raise RuntimeError, "#{self} should only be included in classes" if base.instance_of?(::Module)

  ::Kernel.raise RuntimeError, "#{self} should be included only once in #{base}" if Serializable.serializables.include?(base.name)

  # register as serializable class
  Serializable.serializables << base

  return if base == Serializable::ID # special case which does not need the rest

  # provide serialized property definition support

  # provide serialized classes with their own serialized properties (exclusion) list
  # and a deserialization finalizer setter/getter
  base.singleton_class.class_eval do
    def serializer_properties
      @serializer_props ||= []
    end
    def excluded_serializer_properties
      @excluded_serializer_props ||= ::Set.new
    end
    def set_deserialize_finalizer(fin)
      @finalize_from_deserialized = fin
    end
    private :set_deserialize_finalizer
    def get_deserialize_finalizer
      case @finalize_from_deserialized
      when Serializable::MethodResolver
        @finalize_from_deserialized = @finalize_from_deserialized.resolve
      else
        @finalize_from_deserialized
      end
    end
    private :get_deserialize_finalizer
    def find_deserialize_finalizer
      get_deserialize_finalizer
    end
  end

  base.class_eval do

    # Initializes a newly allocated instance for subsequent deserialization (optionally initializing
    # using the given data hash).
    # The default implementation calls the standard #initialize method without arguments (default constructor)
    # and leaves the property restoration to a subsequent call to the instance method #from_serialized(data).
    # Classes that do not support a default constructor can override this class method and
    # implement a custom initialization scheme.
    # @param [Object] _data hash-like object containing deserialized property data (symbol keys)
    # @return [Object] the initialized object
    def init_from_serialized(_data)
      initialize
      self
    end
    protected :init_from_serialized

    # Check if the class has the default deserialize finalizer method defined (a #create method
    # without arguments). If so install that method as the deserialize finalizer.
    set_deserialize_finalizer(Serializable::MethodResolver.new(self, :create, true))
  end

  # add class methods
  base.extend(SerializeClassMethods)

  # add instance property (de-)serialization methods for base class
  base.class_eval <<~__CODE
    def for_serialize(hash, excludes = ::Set.new)
      #{base.name}.serializer_properties.each { |prop, h| prop.serialize(self, hash, excludes) }
      hash 
    end
    protected :for_serialize

    def from_serialized(hash)
      #{base.name}.serializer_properties.each { |prop| prop.deserialize(self, hash) }
      self
    end
    protected :from_serialized

    def finalize_from_serialized
      if (f = self.class.find_deserialize_finalizer)
        f.call(self)
      end
      self
    end
    protected :finalize_from_serialized

    def self.has_serializer_property?(id)
      self.serializer_properties.any? { |p| p.id == id.to_sym } 
    end
  __CODE
  # add inheritance support
  base.class_eval do
    def self.inherited(derived)
      # add instance property (de-)serialization methods for derived classes
      derived.class_eval <<~__CODE
        module SerializerMethods 
          def for_serialize(hash, excludes = ::Set.new)
            #{derived.name}.serializer_properties.each { |prop| prop.serialize(self, hash, excludes) }
            super(hash, excludes | #{derived.name}.excluded_serializer_properties) 
          end
          protected :for_serialize
  
          def from_serialized(hash)
            #{derived.name}.serializer_properties.each { |prop| prop.deserialize(self, hash) }
            super(hash)
          end
          protected :from_serialized
        end
        include SerializerMethods
      __CODE
      derived.class_eval do
        def self.has_serializer_property?(id)
          self.serializer_properties.any? { |p| p.id == id.to_sym } || self.superclass.has_serializer_property?(id)
        end
      end
      # add derived class support for deserialization finalizer
      derived.singleton_class.class_eval <<~__CODE
        def find_deserialize_finalizer
          get_deserialize_finalizer || #{derived.name}.superclass.find_deserialize_finalizer 
        end
      __CODE

      # Check if the derived class has the default deserialize finalizer method defined (a #create method
      # without arguments) defined. If so install that method as the deserialize finalizer (it is expected
      # this method will call any superclass finalizer that may be defined).
      derived.class_eval do
        set_deserialize_finalizer(Serializable::MethodResolver.new(self, :create, true))
      end

      # register as serializable class
      Serializable.serializables << derived
    end
  end

  # add instance serialization method
  base.include(SerializeInstanceMethods)
end

.register(format, engine) ⇒ Object

Registers a serialization formatting engine

Parameters:

  • format (Symbol, String)

    format id

  • engine (Object)

    formatting engine



164
165
166
167
168
169
170
# File 'lib/firm/serializable.rb', line 164

def register(format, engine)
  if formatters.has_key?(format.to_s.downcase)
    ::Kernel.raise ArgumentError,
                   "Duplicate serialization formatter registration for #{format}"
  end
  formatters[format.to_s.downcase] = engine
end

.serializablesObject



152
153
154
# File 'lib/firm/serializable.rb', line 152

def serializables
  @serializables ||= ::Set.new
end

.serialize(obj, pretty: false, format: Serializable.default_format) ⇒ String .serialize(obj, io, pretty: false, format: Serializable.default_format) ⇒ IO

Serialize the given object

Overloads:

  • .serialize(obj, pretty: false, format: Serializable.default_format) ⇒ String

    Returns serialized data.

    Parameters:

    • obj (Object)

      object to serialize

    • pretty (Boolean) (defaults to: false)

      if true specifies to generate pretty formatted output if possible

    • format (Symbol, String) (defaults to: Serializable.default_format)

      specifies output format

    Returns:

    • (String)

      serialized data

  • .serialize(obj, io, pretty: false, format: Serializable.default_format) ⇒ IO

    Parameters:

    • obj (Object)

      object to serialize

    • io (IO)

      output stream to write serialized data to

    • pretty (Boolean) (defaults to: false)

      if true specifies to generate pretty formatted output if possible

    • format (Symbol, String) (defaults to: Serializable.default_format)

      specifies output format

    Returns:

    • (IO)


532
533
534
# File 'lib/firm/serializable.rb', line 532

def self.serialize(obj, io = nil, pretty: false, format: Serializable.default_format)
  self[format].dump(obj, io, pretty: pretty)
end

.tls_varsObject



148
149
150
# File 'lib/firm/serializable.rb', line 148

def tls_vars
  Thread.current[TLS_VARS_KEY] ||= {}
end

Instance Method Details

#for_serialize(hash, excludes = Set.new) ⇒ Object

Serializes the properties of a serializable instance to the given hash except when the property id is included in excludes.

Parameters:

  • hash (Object)

    hash-like property serialization container

  • excludes (Set) (defaults to: Set.new)

    set with excluded property ids

Returns:

  • (Object)

    hash-like property serialization container



# File 'lib/firm/serializable.rb', line 501

#from_serialized(hash) ⇒ self

Restores the properties of a deserialized instance.

Parameters:

  • hash (Object)

    hash-like property deserialization container

Returns:

  • (self)


# File 'lib/firm/serializable.rb', line 508