safe_and_sound 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +18 -0
- data/lib/safe_and_sound/json.rb +30 -8
- data/lib/safe_and_sound/variant.rb +38 -2
- data/lib/safe_and_sound/version.rb +1 -1
- metadata +21 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9890b2b829aa88cdea2c36e78a032c747d148acf3f6e48c2fc3146db811309b5
|
4
|
+
data.tar.gz: 92316a4c52f9cb868615e219172cdc04392e12af889353612e11eb6aef9a4ed9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e41b58b277085199a6fa2b70bedae6323cfaae551f56198da26ef6d004c0f63a2109ae4589f002eb139464da5e6ffcb6e8738608720ea4f00379bc96f30ebb41
|
7
|
+
data.tar.gz: f38e036748e434f4fca480c973b26b1eb8dbb385df483456ef78f89fa88f2f2a74117bfd20f62beb8d0a0fc7d608c8f93f9bbb99ad1c7a61e34264f08d9c0a3b
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 0.0.3 (21-Apr-22)
|
4
|
+
|
5
|
+
* Add serialization support for array fields
|
6
|
+
* Add ability to declare Array types
|
7
|
+
* Allow to omit 'type' specifier in JSON when type has only one variant
|
8
|
+
* Test and fix deserialization
|
9
|
+
* Update minimum Ruby version to 3
|
10
|
+
* Make sure JSON serialization works on nested types
|
11
|
+
* Add deserialization for nested types
|
12
|
+
|
3
13
|
## 0.0.2 (17-Apr-22)
|
4
14
|
|
5
15
|
* added `#to_json` and `#from_json` methods for serialization
|
data/README.md
CHANGED
@@ -32,6 +32,8 @@ bike = Vehicle.bike(gears: 'twentyone')
|
|
32
32
|
|
33
33
|
To add polymorphic behavior we can write functions __without__ having to touch the new types themselves.
|
34
34
|
|
35
|
+
## Safe, polymorphic functions
|
36
|
+
|
35
37
|
By including the `SafeAndSound::Functions` module we get access to the `chase` function.
|
36
38
|
It immitates the `case` statement but uses the knowledge about our types to make it more safe.
|
37
39
|
|
@@ -60,4 +62,20 @@ of making a syntax for it look like the syntax in languages where this concept i
|
|
60
62
|
|
61
63
|
Check out more examples in the [examples folder](examples).
|
62
64
|
|
65
|
+
## JSON serialization/deserialization included
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
irb(main)> car = Vehicle.Car(horsepower: 100)
|
69
|
+
|
70
|
+
irb(main)> car.as_json # converts to a Hash of primitives
|
71
|
+
=> {"type"=>"Car", "horsepower"=>100}
|
63
72
|
|
73
|
+
irb(main)> puts car.to_json # converts to actual JSON string
|
74
|
+
{"type":"Car","horsepower":100}
|
75
|
+
|
76
|
+
irb(main)> Vehicle.from_hash({"type"=>"Car", "horsepower"=>100})
|
77
|
+
=> #<Vehicle::Car:0x000000010ef49a48 @horsepower=100>
|
78
|
+
|
79
|
+
irb(main)> Vehicle.from_json('{"type":"Car","horsepower":100}')
|
80
|
+
=> #<Vehicle::Car:0x000000010ef6ae00 @horsepower=100>
|
81
|
+
```
|
data/lib/safe_and_sound/json.rb
CHANGED
@@ -1,11 +1,27 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'json'
|
4
|
+
|
3
5
|
module SafeAndSound
|
4
6
|
module ToJson
|
5
7
|
def as_json
|
6
8
|
hash = { 'type' => self.class.variant_name.to_s }
|
7
9
|
self.class.fields.each do |field_name, _|
|
8
|
-
|
10
|
+
value = send(field_name)
|
11
|
+
hash[field_name.to_s] =
|
12
|
+
if value.is_a?(Array)
|
13
|
+
value.map do |item|
|
14
|
+
if item.respond_to?(:as_json)
|
15
|
+
item.as_json
|
16
|
+
else
|
17
|
+
item
|
18
|
+
end
|
19
|
+
end
|
20
|
+
elsif value.respond_to?(:as_json)
|
21
|
+
value.as_json
|
22
|
+
else
|
23
|
+
value
|
24
|
+
end
|
9
25
|
end
|
10
26
|
hash
|
11
27
|
end
|
@@ -17,13 +33,19 @@ module SafeAndSound
|
|
17
33
|
|
18
34
|
module FromJson
|
19
35
|
def from_json(json_string)
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
36
|
+
from_hash(JSON.parse(json_string))
|
37
|
+
end
|
38
|
+
|
39
|
+
def from_hash(hash_)
|
40
|
+
hash = hash_.transform_keys(&:to_sym)
|
41
|
+
variant_name = hash[:type].to_sym if hash[:type]
|
42
|
+
variant =
|
43
|
+
if variant_name.nil? && variants.length == 1
|
44
|
+
variants.first
|
45
|
+
else
|
46
|
+
variants.find { |v| v.variant_name == variant_name }
|
47
|
+
end
|
48
|
+
raise ArgumentError, "Unable to find #{variant_name} for #{name}" unless variant
|
27
49
|
|
28
50
|
variant.new(**hash.except(:type))
|
29
51
|
end
|
@@ -41,11 +41,47 @@ module SafeAndSound
|
|
41
41
|
"#{self.class.name} does not have a constructor argument #{field_name}"
|
42
42
|
end
|
43
43
|
|
44
|
-
|
44
|
+
if field_type_matches?(field_type, field_name, value)
|
45
|
+
instance_variable_set("@#{field_name}", value)
|
46
|
+
else
|
47
|
+
instance_variable_set("@#{field_name}", try_to_initialize_from_hash(field_type, field_name, value))
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def field_type_matches?(field_type, field_name, value)
|
52
|
+
return value.is_a?(field_type) unless field_type.is_a?(Array)
|
53
|
+
|
54
|
+
expected_item_type = field_type.first
|
55
|
+
unless value.is_a?(Array)
|
56
|
+
raise WrgonConstructorArgType,
|
57
|
+
"#{field_name} must be an Array of #{expected_item_type} but was #{value.class}"
|
58
|
+
end
|
59
|
+
|
60
|
+
mismatched_types =
|
61
|
+
value.map do |item|
|
62
|
+
if item.is_a?(expected_item_type)
|
63
|
+
nil
|
64
|
+
else
|
65
|
+
item.class
|
66
|
+
end
|
67
|
+
end.compact
|
68
|
+
mismatched_types.empty?
|
69
|
+
end
|
70
|
+
|
71
|
+
def try_to_initialize_from_hash(field_type, field_name, value)
|
72
|
+
if field_type.is_a?(Array)
|
73
|
+
item_type = field_type.first
|
74
|
+
return value.map { |item_hash| item_type.from_hash(item_hash) } if item_type.superclass == Type
|
75
|
+
|
76
|
+
raise WrgonConstructorArgType,
|
77
|
+
"#{field_name} must be array of type #{item_type}"
|
78
|
+
|
79
|
+
end
|
80
|
+
unless field_type.superclass == Type && value.is_a?(Hash)
|
45
81
|
raise WrgonConstructorArgType,
|
46
82
|
"#{field_name} must be of type #{field_type} but was #{value.class.name}"
|
47
83
|
end
|
48
|
-
|
84
|
+
field_type.from_hash(value)
|
49
85
|
end
|
50
86
|
end
|
51
87
|
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: safe_and_sound
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Axel Tetzlaff
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-04-
|
11
|
+
date: 2022-04-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: debug
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.0.0
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.0.0
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: minitest
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -89,7 +103,7 @@ licenses:
|
|
89
103
|
- MIT
|
90
104
|
metadata:
|
91
105
|
rubygems_mfa_required: 'true'
|
92
|
-
post_install_message:
|
106
|
+
post_install_message:
|
93
107
|
rdoc_options: []
|
94
108
|
require_paths:
|
95
109
|
- lib
|
@@ -97,15 +111,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
97
111
|
requirements:
|
98
112
|
- - ">="
|
99
113
|
- !ruby/object:Gem::Version
|
100
|
-
version:
|
114
|
+
version: 3.1.1
|
101
115
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
116
|
requirements:
|
103
117
|
- - ">="
|
104
118
|
- !ruby/object:Gem::Version
|
105
119
|
version: '0'
|
106
120
|
requirements: []
|
107
|
-
rubygems_version: 3.
|
108
|
-
signing_key:
|
121
|
+
rubygems_version: 3.3.7
|
122
|
+
signing_key:
|
109
123
|
specification_version: 4
|
110
124
|
summary: Sum Data Types
|
111
125
|
test_files: []
|