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 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: []