firm 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0ebb1f6594b3ecc4e56863a0f04ce3eb44fc5bfad0218a183298b44e56e9cf8e
4
- data.tar.gz: 4e2349b45ae0479c968406fcd2eafb7ac4e76c8317cbbeeea20ecb784e998115
3
+ metadata.gz: f36086871785c4f7a5db75ed9e4b9272a4285c80c76335c7e09bd4eccdb2c939
4
+ data.tar.gz: 9936ecd659270cedddfd0d0d18fac176783b97aa7f9c52739fbe45abf1a107d8
5
5
  SHA512:
6
- metadata.gz: 247ca7e7c9eb24fc5ca31c8cfb3f06ec9e4c3779627f3e35722d2c51a630c255cc4d182d434d35da7207f112b0905635a5717fd767f3c904a251b7ab84873cbd
7
- data.tar.gz: 2953c510c88ed9078f777a0c8e23399f48339ef5f6c85fcf264fe025de795ecc187407d7d7bddf77b89dbb182eb158123e62aad909c91fbfffc04f613cf8280f
6
+ metadata.gz: d4800195157b45b817aa2a700113c126b466b9ec53c342fb6e4473ae4bf6d9b463fe05b839ef65f1aa12db1d4e0a128d7026b10b6eacf5bb5e31c4891de1349b
7
+ data.tar.gz: 6893d0692e160627a8a2e1f5ed32de5181dde889778874d05955469e12074c90d7a3f377321dea7b25dd2178e3a97eddf55986f5ea65d6fe4e64f0130e9f0c4b
@@ -12,16 +12,33 @@ module FIRM
12
12
  class Exception < RuntimeError; end
13
13
 
14
14
  class Property
15
- def initialize(klass, prop, proc=nil, force: false, handler: nil, &block)
15
+ def initialize(klass, prop, proc=nil, force: false, handler: nil, optional: false, &block)
16
16
  ::Kernel.raise ArgumentError, "Invalid property id [#{prop}]" unless ::String === prop || ::Symbol === prop
17
17
  ::Kernel.raise ArgumentError, "Duplicate property id [#{prop}]" if klass.has_serializer_property?(prop)
18
18
  @klass = klass
19
19
  @id = prop.to_sym
20
20
  @forced = force
21
+ @optional = optional.nil? || optional
22
+ @default = if @optional
23
+ case optional
24
+ when Proc
25
+ ::Kernel.raise ArgumentError,
26
+ 'Invalid optional value proc' unless optional.arity.abs == 2
27
+ optional
28
+ when UnboundMethod
29
+ ::Kernel.raise ArgumentError,
30
+ 'Invalid optional value method' unless optional.arity.abs == 1
31
+ ->(obj, id) { optional.bind(obj).call(id) }
32
+ else
33
+ optional == true ? nil : optional
34
+ end
35
+ else
36
+ nil
37
+ end
21
38
  if block || handler
22
39
  if handler
23
40
  ::Kernel.raise ArgumentError,
24
- "Invalid property handler #{handler} for #{prop}" unless ::Proc === handler || ::Symbol === handler
41
+ "Invalid property handler #{handler} for #{prop}" unless ::Proc === handler || ::Symbol === handler || ::String === handler
25
42
  if handler.is_a?(::Proc)
26
43
  ::Kernel.raise ArgumentError, "Invalid property block #{proc} for #{prop}" unless block.arity == -3
27
44
  @getter = ->(obj) { handler.call(@id, obj) }
@@ -56,7 +73,7 @@ module FIRM
56
73
  def serialize(obj, data, excludes)
57
74
  unless excludes.include?(@id)
58
75
  val = getter.call(obj)
59
- unless Serializable === val && val.serialize_disabled? && !@forced
76
+ unless optional?(obj, val) || (Serializable === val && val.serialize_disabled? && !@forced)
60
77
  data[@id] = case val
61
78
  when ::Array
62
79
  val.select { |elem| !(Serializable === elem && elem.serialize_disabled?) }
@@ -75,9 +92,10 @@ module FIRM
75
92
  end
76
93
  end
77
94
 
78
- def get(obj)
79
- getter.call(obj)
95
+ def optional?(obj, val)
96
+ @optional && val == (Proc === @default ? @default.call(obj, @id) : @default)
80
97
  end
98
+ private :optional?
81
99
 
82
100
  def get_method(id)
83
101
  begin
@@ -312,15 +330,22 @@ module FIRM
312
330
  module SerializeClassMethods
313
331
 
314
332
  # Adds (a) serializable property(-ies) for instances of his class (and derived classes)
315
- # @overload property(*props, force: false)
333
+ # @overload property(*props, force: false, optional: false)
316
334
  # Specifies one or more serialized properties.
317
335
  # The serialization framework will determine the availability of setter and getter methods
318
336
  # automatically by looking for methods <code>"#{prop_id}=(v)"</code>, <code>"#set_{prop_id}(v)"</code> or <code>"#{prop_id}(v)"</code>
319
337
  # for setters and <code>"#{prop_id}()"</code> or <code>"#get_{prop_id}"</code> for getters.
320
338
  # @param [Symbol,String] props one or more ids of serializable properties
321
339
  # @param [Boolean] force overrides any #disable_serialize for the properties specified
340
+ # @param [Object] optional indicates optionality;
341
+ # if `false` the property will not be optional;
342
+ # `true` means optional if the serialized value == `nil`;
343
+ # any value other than 'false' or 'true' means optional if the serialize value equals that value;
344
+ # alternatively a Proc, Lambda (gets the object and the property id passed) or
345
+ # UnboundMethod (gets the property id passed) can be specified which
346
+ # is called at serialization time to determine the default (optional) value
322
347
  # @return [void]
323
- # @overload property(hash, force: false)
348
+ # @overload property(hash, force: false, optional: false)
324
349
  # Specifies one or more serialized properties with associated setter/getter method ids/procs/lambda-s.
325
350
  # @example
326
351
  # property(
@@ -340,8 +365,15 @@ module FIRM
340
365
  # to be able to support setting explicit nil values.
341
366
  # @param [Hash] hash a hash of pairs of property ids and getter/setter procs
342
367
  # @param [Boolean] force overrides any #disable_serialize for the properties specified
368
+ # @param [Object] optional indicates optionality;
369
+ # if `false` the property will not be optional;
370
+ # `true` means optional if the serialized value == `nil`;
371
+ # any value other than 'false' or 'true' means optional if the serialize value equals that value;
372
+ # alternatively a Proc, Lambda (gets the object and the property id passed) or
373
+ # UnboundMethod (gets the property id passed) can be specified which
374
+ # is called at serialization time to determine the default (optional) value
343
375
  # @return [void]
344
- # @overload property(*props, force: false, handler: nil, &block)
376
+ # @overload property(*props, force: false, handler: nil, optional: false, &block)
345
377
  # Specifies one or more serialized properties with a getter/setter handler proc/method/block.
346
378
  # The getter/setter proc or block should accept either 2 (property id and object for getter) or 3 arguments
347
379
  # (property id, object and value for setter) and is assumed to handle getter/setter requests
@@ -364,29 +396,38 @@ module FIRM
364
396
  # to be able to support setting explicit nil values.
365
397
  # @param [Symbol,String] props one or more ids of serializable properties
366
398
  # @param [Boolean] force overrides any #disable_serialize for the properties specified
399
+ # @param [Symbol,String,Proc] handler serialization handler method name or Proc
400
+ # @param [Object] optional indicates optionality;
401
+ # if `false` the property will not be optional;
402
+ # `true` means optional if the serialized value == `nil`;
403
+ # any value other than 'false' or 'true' means optional if the serialize value equals that value;
404
+ # alternatively a Proc, Lambda (gets the object and the property id passed) or
405
+ # UnboundMethod (gets the property id passed) can be specified which
406
+ # is called at serialization time to determine the default (optional) value
367
407
  # @yieldparam [Symbol,String] id property id
368
408
  # @yieldparam [Object] obj object instance
369
409
  # @yieldparam [Object] val optional property value to set in case of setter request
370
410
  # @return [void]
371
411
  def property(*props, **kwargs, &block)
372
412
  forced = !!kwargs.delete(:force)
413
+ optional = kwargs.has_key?(:optional) ? kwargs.delete(:optional) : false
373
414
  if block || kwargs[:handler]
374
415
  props.each do |prop|
375
- serializer_properties << Property.new(self, prop, force: forced, handler: kwargs[:handler], &block)
416
+ serializer_properties << Property.new(self, prop, force: forced, handler: kwargs[:handler], optional: optional, &block)
376
417
  end
377
418
  else
378
419
  props.flatten.each do |prop|
379
420
  if ::Hash === prop
380
421
  prop.each_pair do |pn, pp|
381
- serializer_properties << Property.new(self, pn, pp, force: forced)
422
+ serializer_properties << Property.new(self, pn, pp, force: forced, optional: optional)
382
423
  end
383
424
  else
384
- serializer_properties << Property.new(self, prop, force: forced)
425
+ serializer_properties << Property.new(self, prop, force: forced, optional: optional)
385
426
  end
386
427
  end
387
428
  unless kwargs.empty?
388
429
  kwargs.each_pair do |pn, pp|
389
- serializer_properties << Property.new(self, pn, pp, force: forced)
430
+ serializer_properties << Property.new(self, pn, pp, force: forced, optional: optional)
390
431
  end
391
432
  end
392
433
  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.0"
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.0
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-05 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