firm 1.0.0 → 1.1.1
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 +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
|