resource-struct 0.1.0 → 0.3.1

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: 07376a21fe1fd7e877cdd8755e5a269e8336d1ac9906cf046e2aa39947938117
4
- data.tar.gz: 070d252877489353d147fc93ee84d3ab0e74608f24d82d6d08163e9acd65c97f
3
+ metadata.gz: baaac467d70f8f08aed004cc989e61b19f1ba5c8af5d6c70e32157431ac5cce2
4
+ data.tar.gz: 640b07696f3f7332587859afdbe742fc6ea08e33fc6563d4e1ebc4d3430abb28
5
5
  SHA512:
6
- metadata.gz: b72654d78fbdb3df534c058a4e1df047f0ed8c40a063dbfc77ff869b8123d370f34277878354fe5d537802637fec52490d89374428e1bd5a937455bf59b4f0a1
7
- data.tar.gz: c3673ddd8220bc52a130ccfd5a512dd1f03dd3c7bfcadde3d28b075ba6b2619f2d8bfa346c853fbe20103ae35630cc6f8a6224bd6d67335a0a31ec63a16abfea
6
+ metadata.gz: c47966d7c7c3b96993594514eaaa21f879ad7c03f96a893d15255867491d990de7779dffb7c01dbf5189a2ac77992fd4f049a7601d4c5b42523a6b619fda27e9
7
+ data.tar.gz: 2a129ceadc0026557fca3e10834eca614f00f7415e282b39b7ea29473176cb8996ed67ef2b4682f45c45bd88e85b25745e00e0139928b7d5c01277420e949301
data/.rubocop.yml CHANGED
@@ -15,6 +15,15 @@ Layout/LineLength:
15
15
  Metrics/MethodLength:
16
16
  Max: 20
17
17
 
18
+ Metrics/AbcSize:
19
+ Max: 25
20
+
21
+ Metrics/CyclomaticComplexity:
22
+ Max: 15
23
+
24
+ Metrics/PerceivedComplexity:
25
+ Max: 15
26
+
18
27
  Naming/FileName:
19
28
  Enabled: true
20
29
  Exclude:
data/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.1] - 2022-01-05
4
+ ### Fix
5
+ - Support nil meaning empty hash as first argument to `FlexStruct` and `StrictStruct`
6
+
7
+ ## [0.3.0] - 2022-01-04
8
+ ### Feature
9
+ - Support for `as_json` and `to_json`
10
+ - Support for `#[]=`, allowing modification on LooseStruct
11
+ - Support for `JSON.parse(STR, object_class: ResourceStruct::FlexStruct)`
12
+ - Refactor common code between LooseStruct and FirmStruct into `ResourceStruct::Extension::IndifferentLookup`
13
+ - No longer support wrong arity for method based access patterns
14
+ - Rename `LooseStruct` -> `FlexStruct`; `FirmStruct` -> `StrictStruct`
15
+
16
+ ## [0.2.1] - 2022-01-01
17
+ ### Fix
18
+ - Correct handling of #== operator on Structs with hashes
19
+
20
+ ## [0.2.0] - 2022-01-01
21
+ ### Changed
22
+ - Indifferent access support for input hash (support for symbol-based hashes in initializer)
23
+
3
24
  ## [0.1.0] - 2021-12-14
4
25
 
5
26
  - Initial release
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- resource-struct (0.1.0)
4
+ resource-struct (0.3.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -48,6 +48,8 @@ GEM
48
48
 
49
49
  PLATFORMS
50
50
  x86_64-darwin-18
51
+ x86_64-darwin-19
52
+ x86_64-linux
51
53
 
52
54
  DEPENDENCIES
53
55
  rake (~> 13.0)
@@ -58,4 +60,4 @@ DEPENDENCIES
58
60
  rubocop-rspec
59
61
 
60
62
  BUNDLED WITH
61
- 2.2.33
63
+ 2.3.4
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
 
data/bin/console CHANGED
@@ -2,7 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "bundler/setup"
5
- require "resource/struct"
5
+ require "resource-struct"
6
6
 
7
7
  # You can add fixtures and/or initialization code here to make experimenting
8
8
  # with your gem easier. You can also use a different console, if you like.
@@ -0,0 +1,93 @@
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 key.is_a?(String)
39
+ @ro_struct[ckey] = ___convert_value(@hash[key] || @hash[key.to_sym])
40
+ else
41
+ @ro_struct[ckey] = ___convert_value(@hash[key] || @hash[ckey])
42
+ end
43
+
44
+ return result if sub_keys.empty?
45
+
46
+ return unless result
47
+
48
+ raise TypeError, "#{result.class.name} does not have #dig method" unless result.respond_to?(:dig)
49
+
50
+ result.dig(*sub_keys)
51
+ end
52
+ alias [] dig
53
+
54
+ private
55
+
56
+ def ___convert_value(value)
57
+ case value
58
+ when ::Array
59
+ value.map { |v| ___convert_value(v) }.freeze
60
+ when Hash
61
+ self.class.new(value)
62
+ else
63
+ value
64
+ end
65
+ end
66
+
67
+ def ___key?(key)
68
+ @hash.key?(key) || @hash.key?(___convert_key(key))
69
+ end
70
+
71
+ def ___convert_key(key)
72
+ key.is_a?(::Symbol) ? key.to_s : key
73
+ end
74
+
75
+ def ___all_keys_equal(other)
76
+ return false unless @hash.count == other.count
77
+
78
+ @hash.reduce(true) do |acc, (k, _)|
79
+ value = self[k]
80
+ if other.key?(k)
81
+ acc && value == other[k]
82
+ elsif k.is_a?(String)
83
+ ck = k.to_sym
84
+ acc && other.key?(ck) && value == other[ck]
85
+ else
86
+ ck = ___convert_key(k)
87
+ acc && other.key?(ck) && value == other[ck]
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,51 @@
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) || key.is_a?(Symbol) && @hash.key?(key.to_sym)
31
+ @hash[key.to_sym] = value
32
+ else
33
+ @hash[key] = value
34
+ end
35
+ end
36
+
37
+ def method_missing(name, *args)
38
+ args_length = args.length
39
+ return self[name] if ___key?(name) && args_length.zero?
40
+ return !!self[name[...-1]] if name.end_with?("?") && args_length.zero?
41
+ return self[name[...-1]] = args.first if name.end_with?("=") && args_length == 1
42
+
43
+ nil
44
+ end
45
+
46
+ def respond_to_missing?(_name, _include_private = false)
47
+ true
48
+ end
49
+ end
50
+ LooseStruct = FlexStruct
51
+ 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) || name.end_with?("?") || 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.1.0"
4
+ VERSION = "0.3.1"
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.1.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Riedler
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-12-16 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:
@@ -55,7 +56,7 @@ 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.4
59
+ rubygems_version: 3.1.6
59
60
  signing_key:
60
61
  specification_version: 4
61
62
  summary: Ruby structs for resource responses
@@ -1,102 +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[___convert_key(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
- @hash == other.instance_variable_get(:@hash)
59
- end
60
-
61
- def [](key, *sub_keys)
62
- ckey = ___convert_key(key)
63
-
64
- result =
65
- if @ro_struct.key?(ckey)
66
- @ro_struct[ckey]
67
- else
68
- @ro_struct[ckey] = ___convert_value(@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?(___convert_key(key))
96
- end
97
-
98
- def ___convert_key(key)
99
- key.is_a?(::Symbol) ? key.to_s : key
100
- end
101
- end
102
- end
@@ -1,99 +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[___convert_key(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
- @hash == other.instance_variable_get(:@hash)
56
- end
57
-
58
- def [](key, *sub_keys)
59
- ckey = ___convert_key(key)
60
-
61
- result =
62
- if @ro_struct.key?(ckey)
63
- @ro_struct[ckey]
64
- else
65
- @ro_struct[ckey] = ___convert_value(@hash[ckey])
66
- end
67
-
68
- return result if sub_keys.empty?
69
-
70
- return unless result
71
-
72
- raise TypeError, "#{result.class.name} does not have #dig method" unless result.respond_to?(:dig)
73
-
74
- result.dig(*sub_keys)
75
- end
76
- alias dig []
77
-
78
- private
79
-
80
- def ___convert_value(value)
81
- case value
82
- when ::Array
83
- value.map { |v| ___convert_value(v) }.freeze
84
- when Hash
85
- self.class.new(value)
86
- else
87
- value
88
- end
89
- end
90
-
91
- def ___key?(key)
92
- @hash.key?(___convert_key(key))
93
- end
94
-
95
- def ___convert_key(key)
96
- key.is_a?(::Symbol) ? key.to_s : key
97
- end
98
- end
99
- end