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



210
211
212
213
# File 'lib/firm/serializable.rb', line 210

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)


218
219
220
# File 'lib/firm/serializable.rb', line 218

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



225
226
227
# File 'lib/firm/serializable.rb', line 225

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



598
599
600
# File 'lib/firm/serializable.rb', line 598

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

.formattersObject



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

def formatters
  @formatters ||= {}
end

.included(base) ⇒ Object



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
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
# File 'lib/firm/serializable.rb', line 629

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



199
200
201
202
203
204
205
# File 'lib/firm/serializable.rb', line 199

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



187
188
189
# File 'lib/firm/serializable.rb', line 187

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)


590
591
592
# File 'lib/firm/serializable.rb', line 590

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

.tls_varsObject



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

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 559

#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 566