firm 1.0.0 → 1.1.0

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: 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