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 +4 -4
- data/lib/firm/serializable.rb +53 -12
- data/lib/firm/version.rb +1 -1
- data/tests/serializer_tests.rb +63 -0
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f36086871785c4f7a5db75ed9e4b9272a4285c80c76335c7e09bd4eccdb2c939
|
4
|
+
data.tar.gz: 9936ecd659270cedddfd0d0d18fac176783b97aa7f9c52739fbe45abf1a107d8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d4800195157b45b817aa2a700113c126b466b9ec53c342fb6e4473ae4bf6d9b463fe05b839ef65f1aa12db1d4e0a128d7026b10b6eacf5bb5e31c4891de1349b
|
7
|
+
data.tar.gz: 6893d0692e160627a8a2e1f5ed32de5181dde889778874d05955469e12074c90d7a3f377321dea7b25dd2178e3a97eddf55986f5ea65d6fe4e64f0130e9f0c4b
|
data/lib/firm/serializable.rb
CHANGED
@@ -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
|
79
|
-
|
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
data/tests/serializer_tests.rb
CHANGED
@@ -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.
|
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-
|
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.
|
106
|
+
rubygems_version: 3.5.22
|
107
107
|
signing_key:
|
108
108
|
specification_version: 4
|
109
109
|
summary: Format independent Ruby object marshalling
|