safe_and_sound 0.2.0 → 0.3.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 84fe70ccca99e40ef3520238a5d0228fbaba056a19d6ced57e0ec7c96054b899
4
- data.tar.gz: 366a938712e572477d6065d7f56f31b8fbf88891438834c7812ab0bf2f59f93d
3
+ metadata.gz: 9890b2b829aa88cdea2c36e78a032c747d148acf3f6e48c2fc3146db811309b5
4
+ data.tar.gz: 92316a4c52f9cb868615e219172cdc04392e12af889353612e11eb6aef9a4ed9
5
5
  SHA512:
6
- metadata.gz: 6b4b69ea28043773a8a508fc1d0b1f61b6ca580ebf8dfaf7af9725645b544d134c19381088a525541de6b597bfa92352e8daa2a26ed110eaa82c81587afdfb0c
7
- data.tar.gz: a1c4937537fe9c000ae2c45a2d0120bf00ed808ffd4830fd570e1b8db44497d1cc769261ed307fc62769e60ada42dea66418ece4387129501c3d2d792429f8c1
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
+ ```
@@ -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
- hash[field_name.to_s] = send(field_name)
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
- hash =
21
- JSON
22
- .parse(json_string)
23
- .transform_keys(&:to_sym)
24
- variant_name = hash[:type]
25
- variant = variants.find { |v| v.name.gsub(/.*::/, '') == variant_name }
26
- raise ArgumentError, "Unable to find #{variant_name} for #{type.name}" unless variant
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
- unless value.is_a?(field_type)
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
- instance_variable_set("@#{field_name}", value)
84
+ field_type.from_hash(value)
49
85
  end
50
86
  end
51
87
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SafeAndSound
4
- VERSION = '0.2.0'
4
+ VERSION = '0.3.0'
5
5
  end
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.2.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-17 00:00:00.000000000 Z
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: 2.5.0
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.1.6
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: []