resource-struct 0.2.1 → 0.3.3

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: 3435cd92f2c4374cfd84a3411fd5a9d42fdd80f3e9ef1a13b847d302ba3fd313
4
- data.tar.gz: 79ffe2f000b766de16130742bfd1a087222bf2d6c9ebf70dc9b328f2cdeb3394
3
+ metadata.gz: 7dae7c6c56053d571217150d28f4f70dc6c8c28524d360460e328d09034c17ea
4
+ data.tar.gz: 85a4dc5b15dcb2af42f30ad4e9daa66e3e16b727934305c46e06536a23e6a90c
5
5
  SHA512:
6
- metadata.gz: 6c74aece00d81f98c1cd6bc7b42cd4d6fdf43a60493cd247bc30f949e844cc048b28853c33f73acc18bc3a2fa6ad70bbe9438ec3a7ab56086cc5f73ca0db3de7
7
- data.tar.gz: 413595f3fc61327671dc6c499477f7208bd8d6095e7b429e276c5b9082b4dc670bab7b40424f7ef4d4bb2a7cc8850e40809010258c007916cab0cb6141e6566b
6
+ metadata.gz: 781fab03b76d80ba0169635168f46e5cefef893ea7f9dd0a15d5dbed1c9dc211b7ebfef024288f2335a1d3dd50bd920b056b22f7e23d0d2ebddae0178b32b6f6
7
+ data.tar.gz: 64b857102c619e60462d4919021735162c2c991e4bb65ad5096576af4620f866c15b91dbff3062cc1494aa1c94c05d4e29fbe9ad5745f895468e8d0ed80a232a
data/.rubocop.yml CHANGED
@@ -16,7 +16,7 @@ Metrics/MethodLength:
16
16
  Max: 20
17
17
 
18
18
  Metrics/AbcSize:
19
- Max: 25
19
+ Max: 30
20
20
 
21
21
  Metrics/CyclomaticComplexity:
22
22
  Max: 15
data/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.3] - 2022-01-05
4
+ ### Fix
5
+ - Support false values correctly, instead of returning nil
6
+ - Support `respond_to?` properly on `FlexStruct`
7
+
8
+ ## [0.3.2] - 2022-01-05
9
+ ### Fix
10
+ - Support for proper marshalling of object
11
+
12
+ ## [0.3.1] - 2022-01-05
13
+ ### Fix
14
+ - Support nil meaning empty hash as first argument to `FlexStruct` and `StrictStruct`
15
+
16
+ ## [0.3.0] - 2022-01-04
17
+ ### Feature
18
+ - Support for `as_json` and `to_json`
19
+ - Support for `#[]=`, allowing modification on LooseStruct
20
+ - Support for `JSON.parse(STR, object_class: ResourceStruct::FlexStruct)`
21
+ - Refactor common code between LooseStruct and FirmStruct into `ResourceStruct::Extension::IndifferentLookup`
22
+ - No longer support wrong arity for method based access patterns
23
+ - Rename `LooseStruct` -> `FlexStruct`; `FirmStruct` -> `StrictStruct`
24
+
3
25
  ## [0.2.1] - 2022-01-01
4
26
  ### Fix
5
27
  - Correct handling of #== operator on Structs with hashes
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- resource-struct (0.2.1)
4
+ resource-struct (0.3.3)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -49,6 +49,7 @@ GEM
49
49
  PLATFORMS
50
50
  x86_64-darwin-18
51
51
  x86_64-darwin-19
52
+ x86_64-linux
52
53
 
53
54
  DEPENDENCIES
54
55
  rake (~> 13.0)
data/README.md CHANGED
@@ -1,8 +1,17 @@
1
- # Resource::Struct
1
+ # ResourceStruct
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/resource/struct`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ ![Continous Integration](https://github.com/AlexRiedler/resource-struct/actions/workflows/default.yml/badge.svg)
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
5
+ This is a gem for working with JSON resources from a network source with indifferent and method based access.
6
+
7
+ Instead of overriding Hash implementation, this wraps a Hash with indifferent access (by symbol or string keys).
8
+ This makes it fast at runtime, while still providing the necessary lookup method of choice.
9
+
10
+ There are two types `ResouceStruct::StrictStruct` and `ResourceStruct::FlexStruct`.
11
+
12
+ `ResourceStruct::StrictStruct` provides a way of wrapping a Hash such that accesses to invalid keys will raise an exception through the method lookup method; it also is immutable.
13
+
14
+ `ResouceStruct::FlexStruct` provides a way of wrapping a Hash such that it returns nil instead of raising an exception when the key is not present in the hash.
6
15
 
7
16
  ## Installation
8
17
 
@@ -22,7 +31,37 @@ Or install it yourself as:
22
31
 
23
32
  ## Usage
24
33
 
25
- TODO: Write usage instructions here
34
+ ### StrictStruct
35
+
36
+ ```ruby
37
+ struct = ResourceStruct::StrictStruct.new({ "foo" => 1, "bar" => [{ "baz" => 2 }, 3] })
38
+ struct.foo? # => true
39
+ struct.brr? # => false
40
+ struct.foo # => 1
41
+ struct.bar # => [StrictStruct<{ "baz" => 2 }>, 3]
42
+ struct.brr # => NoMethodError
43
+ struct[:foo] # => 1
44
+ struct[:brr] # => nil
45
+ struct[:bar, 0, :baz] # => 2
46
+ struct[:bar, 0, :brr] # => nil
47
+ ```
48
+
49
+ ### FlexStruct
50
+
51
+ ```ruby
52
+ struct = ResourceStruct::FlexStruct.new({ "foo" => 1, "bar" => [{ "baz" => 2 }, 3] })
53
+
54
+ struct.foo? # => true
55
+ struct.brr? # => false
56
+ struct.foo # => 1
57
+ struct.bar # => [FlexStruct<{ "baz" => 2 }>, 3]
58
+ struct.brr # => nil
59
+ struct[:foo] # => 1
60
+ struct[:brr] # => nil
61
+ struct[:bar, 0, :baz] # => 2
62
+ struct[:bar, 0, :brr] # => nil
63
+ ```
64
+
26
65
 
27
66
  ## Development
28
67
 
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ module ResourceStruct
6
+ module Extensions
7
+ #
8
+ # Common code between FirmStruct and LooseStruct
9
+ #
10
+ module IndifferentLookup
11
+ extend Forwardable
12
+
13
+ def_delegators :@hash, :to_h, :to_hash, :to_s, :as_json, :to_json
14
+
15
+ def initialize(hash = {})
16
+ @hash = hash || {}
17
+ @ro_struct = {}
18
+
19
+ raise ::ArgumentError, "first argument must be a Hash, found #{@hash.class.name}" unless @hash.is_a?(Hash)
20
+ end
21
+
22
+ def inspect
23
+ "#{self.class.name}<#{@hash.inspect}>"
24
+ end
25
+
26
+ def ==(other)
27
+ other.is_a?(Hash) && ___all_keys_equal(other) ||
28
+ (other.is_a?(LooseStruct) || other.is_a?(FirmStruct)) &&
29
+ ___all_keys_equal(other.instance_variable_get(:@hash))
30
+ end
31
+
32
+ def dig(key, *sub_keys)
33
+ ckey = ___convert_key(key)
34
+
35
+ result =
36
+ if @ro_struct.key?(ckey)
37
+ @ro_struct[ckey]
38
+ elsif @hash.key?(key)
39
+ @ro_struct[ckey] = ___convert_value(@hash[key])
40
+ elsif key.is_a?(String) && @hash.key?(key.to_sym)
41
+ @ro_struct[ckey] = ___convert_value(@hash[key.to_sym])
42
+ elsif key.is_a?(Symbol) && @hash.key?(key.to_s)
43
+ @ro_struct[ckey] = ___convert_value(@hash[key.to_s])
44
+ end
45
+
46
+ return result if sub_keys.empty?
47
+
48
+ return unless result
49
+
50
+ raise TypeError, "#{result.class.name} does not have #dig method" unless result.respond_to?(:dig)
51
+
52
+ result.dig(*sub_keys)
53
+ end
54
+ alias [] dig
55
+
56
+ def marshal_dump
57
+ {
58
+ data: @hash
59
+ }
60
+ end
61
+
62
+ def marshal_load(obj)
63
+ @ro_struct = {}
64
+ @hash = obj[:data]
65
+ end
66
+
67
+ private
68
+
69
+ def ___convert_value(value)
70
+ case value
71
+ when ::Array
72
+ value.map { |v| ___convert_value(v) }.freeze
73
+ when Hash
74
+ self.class.new(value)
75
+ else
76
+ value
77
+ end
78
+ end
79
+
80
+ def ___key?(key)
81
+ @hash.key?(key) ||
82
+ @hash.key?(___convert_key(key)) ||
83
+ key.is_a?(String) && @hash.key?(key.to_sym)
84
+ end
85
+
86
+ def ___convert_key(key)
87
+ key.is_a?(::Symbol) ? key.to_s : key
88
+ end
89
+
90
+ def ___all_keys_equal(other)
91
+ return false unless @hash.count == other.count
92
+
93
+ @hash.reduce(true) do |acc, (k, _)|
94
+ value = self[k]
95
+ if other.key?(k)
96
+ acc && value == other[k]
97
+ elsif k.is_a?(String)
98
+ ck = k.to_sym
99
+ acc && other.key?(ck) && value == other[ck]
100
+ else
101
+ ck = ___convert_key(k)
102
+ acc && other.key?(ck) && value == other[ck]
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ResourceStruct
4
+ #
5
+ # FlexStruct provides a struct by which accessing undefined fields returns nil
6
+ #
7
+ # struct = FlexStruct.new({ "foo" => 1, "bar" => [{ "baz" => 2 }, 3] })
8
+ #
9
+ # struct.foo? # => true
10
+ # struct.brr? # => false
11
+ # struct.foo # => 1
12
+ # struct.bar # => [FlexStruct<{ "baz" => 2 }>, 3]
13
+ # struct.brr # => nil
14
+ # struct[:foo] # => 1
15
+ # struct[:brr] # => nil
16
+ # struct[:bar, 0, :baz] # => 2
17
+ # struct[:bar, 0, :brr] # => nil
18
+ #
19
+ class FlexStruct
20
+ include ::ResourceStruct::Extensions::IndifferentLookup
21
+
22
+ def []=(key, value)
23
+ ckey = ___convert_key(key)
24
+ @ro_struct.delete(ckey)
25
+
26
+ value = value.instance_variable_get(:@hash) if value.is_a?(FlexStruct) || value.is_a?(StrictStruct)
27
+
28
+ if @hash.key?(key)
29
+ @hash[key] = value
30
+ elsif key.is_a?(String) && @hash.key?(key.to_sym)
31
+ @hash[key.to_sym] = value
32
+ elsif key.is_a?(Symbol) && @hash.key?(key.to_s)
33
+ @hash[key.to_s] = value
34
+ else
35
+ @hash[key] = value
36
+ end
37
+ end
38
+
39
+ def method_missing(name, *args)
40
+ args_length = args.length
41
+ return self[name] if ___key?(name) && args_length.zero?
42
+ return !!self[name[...-1]] if name.end_with?("?") && args_length.zero?
43
+ return self[name[...-1]] = args.first if name.end_with?("=") && args_length == 1
44
+
45
+ nil
46
+ end
47
+
48
+ def respond_to_missing?(name, include_private = false)
49
+ ___key?(name) || ___key?(name.to_s.chomp("?")) || super
50
+ end
51
+ end
52
+ LooseStruct = FlexStruct
53
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ResourceStruct
4
+ #
5
+ # StrictStruct provides a struct by which accessing undefined fields raises a MethodMissing error.
6
+ # This protects against accessing fields that are not present in API Responses.
7
+ #
8
+ # If you need to check whether a field exists in an api response, you can via name? methods.
9
+ #
10
+ # struct = StrictStruct.new({ "foo" => 1, "bar" => [{ "baz" => 2 }, 3] })
11
+ #
12
+ # struct.foo? # => true
13
+ # struct.brr? # => false
14
+ # struct.foo # => 1
15
+ # struct.bar # => [StrictStruct<{ "baz" => 2 }>, 3]
16
+ # struct.brr # => NoMethodError
17
+ # struct[:foo] # => 1
18
+ # struct[:brr] # => nil
19
+ # struct[:bar, 0, :baz] # => 2
20
+ # struct[:bar, 0, :brr] # => nil
21
+ #
22
+ class StrictStruct
23
+ include ::ResourceStruct::Extensions::IndifferentLookup
24
+
25
+ def method_missing(name, *args, &blk)
26
+ args_length = args.length
27
+ return self[name] if ___key?(name) && args_length.zero?
28
+ return !!self[name[...-1]] if name.end_with?("?") && args_length.zero?
29
+
30
+ super
31
+ end
32
+
33
+ def respond_to_missing?(name, include_private = false)
34
+ ___key?(name) || ___key?(name.to_s.chomp("?")) || super
35
+ end
36
+ end
37
+ FirmStruct = StrictStruct
38
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ResourceStruct
4
- VERSION = "0.2.1"
4
+ VERSION = "0.3.3"
5
5
  end
@@ -2,9 +2,16 @@
2
2
 
3
3
  require_relative "resource_struct/version"
4
4
 
5
+ #
6
+ # ResourceStruct
7
+ #
8
+ # includes the factory method ResourceStruct.new(hash, opts)
9
+ # for building the various types of structs provided by the library.
10
+ #
5
11
  module ResourceStruct
6
12
  class Error < StandardError; end
7
13
  end
8
14
 
9
- require_relative "resource_struct/firm_struct"
10
- require_relative "resource_struct/loose_struct"
15
+ require_relative "resource_struct/extensions/indifferent_lookup"
16
+ require_relative "resource_struct/strict_struct"
17
+ require_relative "resource_struct/flex_struct"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: resource-struct
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Riedler
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-01-01 00:00:00.000000000 Z
11
+ date: 2022-01-05 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Openstruct like access without all the headaches of Hash method overrides
14
14
  etc...
@@ -30,8 +30,9 @@ files:
30
30
  - bin/setup
31
31
  - lib/resource-struct.rb
32
32
  - lib/resource_struct.rb
33
- - lib/resource_struct/firm_struct.rb
34
- - lib/resource_struct/loose_struct.rb
33
+ - lib/resource_struct/extensions/indifferent_lookup.rb
34
+ - lib/resource_struct/flex_struct.rb
35
+ - lib/resource_struct/strict_struct.rb
35
36
  - lib/resource_struct/version.rb
36
37
  homepage: https://github.com/AlexRiedler/resource-struct
37
38
  licenses:
@@ -40,7 +41,7 @@ metadata:
40
41
  homepage_uri: https://github.com/AlexRiedler/resource-struct
41
42
  source_code_uri: https://github.com/AlexRiedler/resource-struct
42
43
  changelog_uri: https://raw.githubusercontent.com/AlexRiedler/resource-struct/master/CHANGELOG.md
43
- post_install_message:
44
+ post_install_message:
44
45
  rdoc_options: []
45
46
  require_paths:
46
47
  - lib
@@ -55,8 +56,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
55
56
  - !ruby/object:Gem::Version
56
57
  version: '0'
57
58
  requirements: []
58
- rubygems_version: 3.1.6
59
- signing_key:
59
+ rubygems_version: 3.0.3
60
+ signing_key:
60
61
  specification_version: 4
61
62
  summary: Ruby structs for resource responses
62
63
  test_files: []
@@ -1,122 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ResourceStruct
4
- #
5
- # FirmStruct provides a struct by which accessing undefined fields raises a MethodMissing error.
6
- # This protects against accessing fields that are not present in API Responses.
7
- #
8
- # If you need to check whether a field exists in an api response, you can via name? methods.
9
- #
10
- # struct = FirmStruct.new({ "foo" => 1, "bar" => [{ "baz" => 2 }, 3] })
11
- #
12
- # struct.foo? # => true
13
- # struct.brr? # => false
14
- # struct.foo # => 1
15
- # struct.bar # => [FirmStruct<{ "baz" => 2 }>, 3]
16
- # struct.brr # => NoMethodError
17
- # struct[:foo] # => 1
18
- # struct[:brr] # => nil
19
- # struct[:bar, 0, :baz] # => 2
20
- # struct[:bar, 0, :brr] # => nil
21
- #
22
- class FirmStruct
23
- def initialize(hash)
24
- raise ::ArgumentError, "first argument must be a Hash, found #{hash.class.name}" unless hash.is_a?(Hash)
25
-
26
- @hash = hash
27
- @ro_struct = {}
28
- end
29
-
30
- def method_missing(name, *args, &blk)
31
- return self[name] if ___key?(name)
32
- return !!self[name[...-1]] if name.end_with?("?")
33
-
34
- super
35
- end
36
-
37
- def respond_to_missing?(name, include_private = false)
38
- ___key?(name) || name.end_with?("?") || super
39
- end
40
-
41
- def to_h
42
- @hash.to_h
43
- end
44
-
45
- def to_hash
46
- @hash.to_hash
47
- end
48
-
49
- def inspect
50
- "#{self.class.name}<#{@hash.inspect}>"
51
- end
52
-
53
- def to_s
54
- @hash.to_s
55
- end
56
-
57
- def ==(other)
58
- other.is_a?(Hash) && ___all_keys_equal(other) ||
59
- (other.is_a?(LooseStruct) || other.is_a?(FirmStruct)) && ___all_keys_equal(other.instance_variable_get(:@hash))
60
- end
61
-
62
- def dig(key, *sub_keys)
63
- ckey = ___convert_key(key)
64
-
65
- result =
66
- if @ro_struct.key?(ckey)
67
- @ro_struct[ckey]
68
- elsif key.is_a?(String)
69
- @ro_struct[ckey] = ___convert_value(@hash[key] || @hash[key.to_sym])
70
- else
71
- @ro_struct[ckey] = ___convert_value(@hash[key] || @hash[ckey])
72
- end
73
-
74
- return result if sub_keys.empty?
75
-
76
- return unless result
77
-
78
- raise TypeError, "#{result.class.name} does not have #dig method" unless result.respond_to?(:dig)
79
-
80
- result.dig(*sub_keys)
81
- end
82
- alias [] dig
83
-
84
- private
85
-
86
- def ___convert_value(value)
87
- case value
88
- when ::Array
89
- value.map { |v| ___convert_value(v) }.freeze
90
- when Hash
91
- self.class.new(value)
92
- else
93
- value
94
- end
95
- end
96
-
97
- def ___key?(key)
98
- @hash.key?(key) || @hash.key?(___convert_key(key))
99
- end
100
-
101
- def ___convert_key(key)
102
- key.is_a?(::Symbol) ? key.to_s : key
103
- end
104
-
105
- def ___all_keys_equal(other)
106
- return false unless @hash.count == other.count
107
-
108
- @hash.reduce(true) do |acc, (k, _)|
109
- value = self[k]
110
- if other.key?(k)
111
- acc && value == other[k]
112
- elsif k.is_a?(String)
113
- ck = k.to_sym
114
- acc && other.key?(ck) && value == other[ck]
115
- else
116
- ck = ___convert_key(k)
117
- acc && other.key?(ck) && value == other[ck]
118
- end
119
- end
120
- end
121
- end
122
- end
@@ -1,119 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ResourceStruct
4
- #
5
- # LooseStruct provides a struct by which accessing undefined fields returns nil
6
- #
7
- # struct = LooseStruct.new({ "foo" => 1, "bar" => [{ "baz" => 2 }, 3] })
8
- #
9
- # struct.foo? # => true
10
- # struct.brr? # => false
11
- # struct.foo # => 1
12
- # struct.bar # => [LooseStruct<{ "baz" => 2 }>, 3]
13
- # struct.brr # => nil
14
- # struct[:foo] # => 1
15
- # struct[:brr] # => nil
16
- # struct[:bar, 0, :baz] # => 2
17
- # struct[:bar, 0, :brr] # => nil
18
- #
19
- class LooseStruct
20
- def initialize(hash)
21
- raise ::ArgumentError, "first argument must be a Hash, found #{hash.class.name}" unless hash.is_a?(Hash)
22
-
23
- @hash = hash
24
- @ro_struct = {}
25
- end
26
-
27
- def method_missing(name, *_args)
28
- return self[name] if ___key?(name)
29
- return !!self[name[...-1]] if name.end_with?("?")
30
-
31
- nil
32
- end
33
-
34
- def respond_to_missing?(_name, _include_private = false)
35
- true
36
- end
37
-
38
- def to_h
39
- @hash.to_h
40
- end
41
-
42
- def to_hash
43
- @hash.to_hash
44
- end
45
-
46
- def inspect
47
- "#{self.class.name}<#{@hash.inspect}>"
48
- end
49
-
50
- def to_s
51
- @hash.to_s
52
- end
53
-
54
- def ==(other)
55
- other.is_a?(Hash) && ___all_keys_equal(other) ||
56
- (other.is_a?(LooseStruct) || other.is_a?(FirmStruct)) && ___all_keys_equal(other.instance_variable_get(:@hash))
57
- end
58
-
59
- def dig(key, *sub_keys)
60
- ckey = ___convert_key(key)
61
-
62
- result =
63
- if @ro_struct.key?(ckey)
64
- @ro_struct[ckey]
65
- elsif key.is_a?(String)
66
- @ro_struct[ckey] = ___convert_value(@hash[key] || @hash[key.to_sym])
67
- else
68
- @ro_struct[ckey] = ___convert_value(@hash[key] || @hash[ckey])
69
- end
70
-
71
- return result if sub_keys.empty?
72
-
73
- return unless result
74
-
75
- raise TypeError, "#{result.class.name} does not have #dig method" unless result.respond_to?(:dig)
76
-
77
- result.dig(*sub_keys)
78
- end
79
- alias [] dig
80
-
81
- private
82
-
83
- def ___convert_value(value)
84
- case value
85
- when ::Array
86
- value.map { |v| ___convert_value(v) }.freeze
87
- when Hash
88
- self.class.new(value)
89
- else
90
- value
91
- end
92
- end
93
-
94
- def ___key?(key)
95
- @hash.key?(key) || @hash.key?(___convert_key(key))
96
- end
97
-
98
- def ___convert_key(key)
99
- key.is_a?(::Symbol) ? key.to_s : key
100
- end
101
-
102
- def ___all_keys_equal(other)
103
- return false unless @hash.count == other.count
104
-
105
- @hash.reduce(true) do |acc, (k, _)|
106
- value = self[k]
107
- if other.key?(k)
108
- acc && value == other[k]
109
- elsif k.is_a?(String)
110
- ck = k.to_sym
111
- acc && other.key?(ck) && value == other[ck]
112
- else
113
- ck = ___convert_key(k)
114
- acc && other.key?(ck) && value == other[ck]
115
- end
116
- end
117
- end
118
- end
119
- end