firm 1.0.0 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0ebb1f6594b3ecc4e56863a0f04ce3eb44fc5bfad0218a183298b44e56e9cf8e
4
- data.tar.gz: 4e2349b45ae0479c968406fcd2eafb7ac4e76c8317cbbeeea20ecb784e998115
3
+ metadata.gz: 9bde55f78b14c4b8c8cac20c709c2826391616cf5d30972bcdeff2613d9389f5
4
+ data.tar.gz: f4e7a9b3269ea524699693968c88d75372f6bf51dbe54c7e61b3bee349da3b71
5
5
  SHA512:
6
- metadata.gz: 247ca7e7c9eb24fc5ca31c8cfb3f06ec9e4c3779627f3e35722d2c51a630c255cc4d182d434d35da7207f112b0905635a5717fd767f3c904a251b7ab84873cbd
7
- data.tar.gz: 2953c510c88ed9078f777a0c8e23399f48339ef5f6c85fcf264fe025de795ecc187407d7d7bddf77b89dbb182eb158123e62aad909c91fbfffc04f613cf8280f
6
+ metadata.gz: 3768d2dda3e137c33b54ecfe27eda8f052c9eb8e21809e9e8f82eb434f9ce381fdf5cd430d9d118aacb75a6047c4db2bd175d2e9937be3fccc6b587f8cc0b683
7
+ data.tar.gz: 329dff1413b582f472b099181769c16afc91aeaa9fe6b827b1cb753be68df14d6dbda11e5d60f3da77e97feb22833b7b78db0a231b09a605c5f78d79845f16e0
@@ -11,17 +11,35 @@ module FIRM
11
11
 
12
12
  class Exception < RuntimeError; end
13
13
 
14
+ # This class encapsulates a serializable property definition.
14
15
  class Property
15
- def initialize(klass, prop, proc=nil, force: false, handler: nil, &block)
16
+ def initialize(klass, prop, proc=nil, force: false, handler: nil, optional: false, &block)
16
17
  ::Kernel.raise ArgumentError, "Invalid property id [#{prop}]" unless ::String === prop || ::Symbol === prop
17
18
  ::Kernel.raise ArgumentError, "Duplicate property id [#{prop}]" if klass.has_serializer_property?(prop)
18
19
  @klass = klass
19
20
  @id = prop.to_sym
20
21
  @forced = force
22
+ @optional = optional.nil? || optional
23
+ @default = if @optional
24
+ case optional
25
+ when Proc
26
+ ::Kernel.raise ArgumentError,
27
+ 'Invalid optional value proc' unless optional.arity.abs == 2
28
+ optional
29
+ when UnboundMethod
30
+ ::Kernel.raise ArgumentError,
31
+ 'Invalid optional value method' unless optional.arity.abs == 1
32
+ ->(obj, id) { optional.bind(obj).call(id) }
33
+ else
34
+ optional == true ? nil : optional
35
+ end
36
+ else
37
+ nil
38
+ end
21
39
  if block || handler
22
40
  if handler
23
41
  ::Kernel.raise ArgumentError,
24
- "Invalid property handler #{handler} for #{prop}" unless ::Proc === handler || ::Symbol === handler
42
+ "Invalid property handler #{handler} for #{prop}" unless ::Proc === handler || ::Symbol === handler || ::String === handler
25
43
  if handler.is_a?(::Proc)
26
44
  ::Kernel.raise ArgumentError, "Invalid property block #{proc} for #{prop}" unless block.arity == -3
27
45
  @getter = ->(obj) { handler.call(@id, obj) }
@@ -53,10 +71,15 @@ module FIRM
53
71
 
54
72
  attr_reader :id
55
73
 
74
+ # Serializes the defined property for the given object and inserts the serialized data
75
+ # into the given data object unless included in the given excludes list.
76
+ # @param [Object] obj
77
+ # @param [Object] data hash-like object
78
+ # @param [Array<Symbol>] excludes
56
79
  def serialize(obj, data, excludes)
57
80
  unless excludes.include?(@id)
58
81
  val = getter.call(obj)
59
- unless Serializable === val && val.serialize_disabled? && !@forced
82
+ unless optional?(obj, val) || (Serializable === val && val.serialize_disabled? && !@forced)
60
83
  data[@id] = case val
61
84
  when ::Array
62
85
  val.select { |elem| !(Serializable === elem && elem.serialize_disabled?) }
@@ -69,15 +92,27 @@ module FIRM
69
92
  end
70
93
  end
71
94
 
95
+ # Returns the (unserialized) property value for the given object.
96
+ # @param [Object] obj
97
+ def get(obj)
98
+ getter.call(obj)
99
+ end
100
+
101
+ # Restores the defined property for the given object using the deserialized data
102
+ # extracted from the given data object.
103
+ # @param [Object] obj
104
+ # @param [Object] data hash-like object
105
+ # @return [void]
72
106
  def deserialize(obj, data)
73
107
  if data.has_key?(@id)
74
108
  setter.call(obj, data[@id])
75
109
  end
76
110
  end
77
111
 
78
- def get(obj)
79
- getter.call(obj)
112
+ def optional?(obj, val)
113
+ @optional && val == (Proc === @default ? @default.call(obj, @id) : @default)
80
114
  end
115
+ private :optional?
81
116
 
82
117
  def get_method(id)
83
118
  begin
@@ -312,15 +347,22 @@ module FIRM
312
347
  module SerializeClassMethods
313
348
 
314
349
  # Adds (a) serializable property(-ies) for instances of his class (and derived classes)
315
- # @overload property(*props, force: false)
350
+ # @overload property(*props, force: false, optional: false)
316
351
  # Specifies one or more serialized properties.
317
352
  # The serialization framework will determine the availability of setter and getter methods
318
353
  # automatically by looking for methods <code>"#{prop_id}=(v)"</code>, <code>"#set_{prop_id}(v)"</code> or <code>"#{prop_id}(v)"</code>
319
354
  # for setters and <code>"#{prop_id}()"</code> or <code>"#get_{prop_id}"</code> for getters.
320
355
  # @param [Symbol,String] props one or more ids of serializable properties
321
356
  # @param [Boolean] force overrides any #disable_serialize for the properties specified
357
+ # @param [Object] optional indicates optionality;
358
+ # if `false` the property will not be optional;
359
+ # `true` means optional if the serialized value == `nil`;
360
+ # any value other than 'false' or 'true' means optional if the serialize value equals that value;
361
+ # alternatively a Proc, Lambda (gets the object and the property id passed) or
362
+ # UnboundMethod (gets the property id passed) can be specified which
363
+ # is called at serialization time to determine the default (optional) value
322
364
  # @return [void]
323
- # @overload property(hash, force: false)
365
+ # @overload property(hash, force: false, optional: false)
324
366
  # Specifies one or more serialized properties with associated setter/getter method ids/procs/lambda-s.
325
367
  # @example
326
368
  # property(
@@ -340,8 +382,15 @@ module FIRM
340
382
  # to be able to support setting explicit nil values.
341
383
  # @param [Hash] hash a hash of pairs of property ids and getter/setter procs
342
384
  # @param [Boolean] force overrides any #disable_serialize for the properties specified
385
+ # @param [Object] optional indicates optionality;
386
+ # if `false` the property will not be optional;
387
+ # `true` means optional if the serialized value == `nil`;
388
+ # any value other than 'false' or 'true' means optional if the serialize value equals that value;
389
+ # alternatively a Proc, Lambda (gets the object and the property id passed) or
390
+ # UnboundMethod (gets the property id passed) can be specified which
391
+ # is called at serialization time to determine the default (optional) value
343
392
  # @return [void]
344
- # @overload property(*props, force: false, handler: nil, &block)
393
+ # @overload property(*props, force: false, handler: nil, optional: false, &block)
345
394
  # Specifies one or more serialized properties with a getter/setter handler proc/method/block.
346
395
  # The getter/setter proc or block should accept either 2 (property id and object for getter) or 3 arguments
347
396
  # (property id, object and value for setter) and is assumed to handle getter/setter requests
@@ -364,29 +413,38 @@ module FIRM
364
413
  # to be able to support setting explicit nil values.
365
414
  # @param [Symbol,String] props one or more ids of serializable properties
366
415
  # @param [Boolean] force overrides any #disable_serialize for the properties specified
416
+ # @param [Symbol,String,Proc] handler serialization handler method name or Proc
417
+ # @param [Object] optional indicates optionality;
418
+ # if `false` the property will not be optional;
419
+ # `true` means optional if the serialized value == `nil`;
420
+ # any value other than 'false' or 'true' means optional if the serialize value equals that value;
421
+ # alternatively a Proc, Lambda (gets the object and the property id passed) or
422
+ # UnboundMethod (gets the property id passed) can be specified which
423
+ # is called at serialization time to determine the default (optional) value
367
424
  # @yieldparam [Symbol,String] id property id
368
425
  # @yieldparam [Object] obj object instance
369
426
  # @yieldparam [Object] val optional property value to set in case of setter request
370
427
  # @return [void]
371
428
  def property(*props, **kwargs, &block)
372
429
  forced = !!kwargs.delete(:force)
430
+ optional = kwargs.has_key?(:optional) ? kwargs.delete(:optional) : false
373
431
  if block || kwargs[:handler]
374
432
  props.each do |prop|
375
- serializer_properties << Property.new(self, prop, force: forced, handler: kwargs[:handler], &block)
433
+ serializer_properties << Property.new(self, prop, force: forced, handler: kwargs[:handler], optional: optional, &block)
376
434
  end
377
435
  else
378
436
  props.flatten.each do |prop|
379
437
  if ::Hash === prop
380
438
  prop.each_pair do |pn, pp|
381
- serializer_properties << Property.new(self, pn, pp, force: forced)
439
+ serializer_properties << Property.new(self, pn, pp, force: forced, optional: optional)
382
440
  end
383
441
  else
384
- serializer_properties << Property.new(self, prop, force: forced)
442
+ serializer_properties << Property.new(self, prop, force: forced, optional: optional)
385
443
  end
386
444
  end
387
445
  unless kwargs.empty?
388
446
  kwargs.each_pair do |pn, pp|
389
- serializer_properties << Property.new(self, pn, pp, force: forced)
447
+ serializer_properties << Property.new(self, pn, pp, force: forced, optional: optional)
390
448
  end
391
449
  end
392
450
  end
data/lib/firm/version.rb CHANGED
@@ -4,6 +4,6 @@
4
4
  module FIRM
5
5
 
6
6
  # FIRM version
7
- VERSION = "1.0.0"
7
+ VERSION = "1.1.1"
8
8
 
9
9
  end
@@ -691,6 +691,69 @@ module SerializerTestMixin
691
691
 
692
692
  end
693
693
 
694
+ class SomeClass
695
+
696
+ include FIRM::Serializable
697
+
698
+ property :always
699
+ property :not_when_nil, optional: true # alternatively you can use `optional: nil`
700
+ property :not_when_some_value, optional: -1
701
+ property :not_when_some_derived_value, optional: ->(_obj, _id) { :derived_value }
702
+
703
+ def initialize(always, not_when_nil = nil, not_when_some_value = -1, not_when_some_derived_value = :derived_value)
704
+ @always = always
705
+ @not_when_nil = not_when_nil
706
+ @not_when_some_value = not_when_some_value
707
+ @not_when_some_derived_value = not_when_some_derived_value
708
+ end
709
+
710
+ attr_accessor :always, :not_when_nil, :not_when_some_value, :not_when_some_derived_value
711
+
712
+ class << self
713
+
714
+ def deserialize_initializer
715
+ @init ||= ->(obj, _data) { obj }
716
+ end
717
+
718
+ def deserialize_initializer=(init)
719
+ @init = init
720
+ end
721
+
722
+ end
723
+
724
+
725
+ def init_from_serialized(_data)
726
+ initialize(nil) # first call the initialize method
727
+ self.class.deserialize_initializer.call(self, _data)
728
+ self
729
+ end
730
+ protected :init_from_serialized
731
+
732
+ end
733
+
734
+ def test_optional
735
+ SomeClass.deserialize_initializer = ->(obj, _data) {
736
+ obj.always = 'NOT'
737
+ obj.not_when_nil = :not_nil
738
+ obj.not_when_some_value = 10
739
+ obj.not_when_some_derived_value = nil
740
+ }
741
+
742
+ obj_serial = SomeClass.new('Always').serialize
743
+ obj_new = assert_nothing_raised { SomeClass.deserialize(obj_serial) }
744
+ assert_equal('Always', obj_new.always) # always (de-)serialized, so overwrites custom init on deserialize
745
+ assert_equal(:not_nil, obj_new.not_when_nil) # not serialized since nil, so leaves custom init
746
+ assert_equal(10, obj_new.not_when_some_value) # not serialized since default value, so leaves custom init
747
+ assert_nil(obj_new.not_when_some_derived_value) # not serialized since default value, so leaves custom init
748
+
749
+ obj_serial = SomeClass.new('Always', :ok, 99, :specific_value).serialize
750
+ obj_new = assert_nothing_raised { SomeClass.deserialize(obj_serial) }
751
+ assert_equal('Always', obj_new.always) # always (de-)serialized, so overwrites custom init on deserialize
752
+ assert_equal(:ok, obj_new.not_when_nil) # serialized since not default, so overwrites custom init
753
+ assert_equal(99, obj_new.not_when_some_value) # serialized since not default, so overwrites custom init
754
+ assert_equal(:specific_value, obj_new.not_when_some_derived_value) # serialized since not default, so overwrites custom init
755
+ end
756
+
694
757
  class House
695
758
 
696
759
  include FIRM::Serializable
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: firm
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Martin Corino
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-08 00:00:00.000000000 Z
11
+ date: 2024-11-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -103,7 +103,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
103
103
  - !ruby/object:Gem::Version
104
104
  version: '0'
105
105
  requirements: []
106
- rubygems_version: 3.5.16
106
+ rubygems_version: 3.5.22
107
107
  signing_key:
108
108
  specification_version: 4
109
109
  summary: Format independent Ruby object marshalling