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 +4 -4
- data/lib/firm/serializable.rb +70 -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: 9bde55f78b14c4b8c8cac20c709c2826391616cf5d30972bcdeff2613d9389f5
|
4
|
+
data.tar.gz: f4e7a9b3269ea524699693968c88d75372f6bf51dbe54c7e61b3bee349da3b71
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3768d2dda3e137c33b54ecfe27eda8f052c9eb8e21809e9e8f82eb434f9ce381fdf5cd430d9d118aacb75a6047c4db2bd175d2e9937be3fccc6b587f8cc0b683
|
7
|
+
data.tar.gz: 329dff1413b582f472b099181769c16afc91aeaa9fe6b827b1cb753be68df14d6dbda11e5d60f3da77e97feb22833b7b78db0a231b09a605c5f78d79845f16e0
|
data/lib/firm/serializable.rb
CHANGED
@@ -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
|
79
|
-
|
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
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.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-
|
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.
|
106
|
+
rubygems_version: 3.5.22
|
107
107
|
signing_key:
|
108
108
|
specification_version: 4
|
109
109
|
summary: Format independent Ruby object marshalling
|