simple-immutable 1.0.3 → 1.1.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 +4 -4
- data/.rubocop.yml +3 -0
- data/Gemfile +1 -1
- data/Makefile +5 -3
- data/README.md +13 -2
- data/VERSION +1 -1
- data/lib/simple/immutable.rb +78 -19
- data/scripts/test +4 -0
- data/test/immutable_test.rb +27 -9
- data/test/immutable_w_fallback_test.rb +63 -0
- metadata +6 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4ecf8356b446062c2cc5eb9237759cb91eb8a91cba3f311b527f2c73aa675bd7
|
|
4
|
+
data.tar.gz: 2f6c6952068ff100208296f0715d7f8b444b34deb673c285c3fe24d34be38bf8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e71dff987fe8ee9853348389d6d1f262bda8355e88ef0dd8847585ce803adeae1de65c2153df6904bf4727fd32f4baddc3cef72bdd6e6b973126927938a07a9e
|
|
7
|
+
data.tar.gz: daf98517ec14374466755c6075a5ee2b009f63cb245f213b39d39c98915ff8597ba6a1ba7dfa961a1ada6785bde8720e907a19fa6dfda8b405ae7e7e893b8885
|
data/.rubocop.yml
CHANGED
data/Gemfile
CHANGED
data/Makefile
CHANGED
data/README.md
CHANGED
|
@@ -17,11 +17,11 @@ Turns a nested data structure into a immutable ruby object implementing dot and
|
|
|
17
17
|
name: "grandchildname"
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
|
-
"children"
|
|
20
|
+
"children" => [
|
|
21
21
|
"anna",
|
|
22
22
|
"arthur",
|
|
23
23
|
{
|
|
24
|
-
action
|
|
24
|
+
"action" => {
|
|
25
25
|
keep_your_mouth_shut: true
|
|
26
26
|
}
|
|
27
27
|
}
|
|
@@ -36,3 +36,14 @@ Turns a nested data structure into a immutable ruby object implementing dot and
|
|
|
36
36
|
imm.children.foo # NoMethodError (undefined method `foo' for #<Array:0x00007fb90f1be390>)
|
|
37
37
|
imm.children.first.foo # NoMethodError (undefined method `foo' for "anna":String)
|
|
38
38
|
|
|
39
|
+
If an attribute is not defined in the immutable (or in any sub-immutable) we raise a NameError
|
|
40
|
+
|
|
41
|
+
imm.unknown # NameError (unknown immutable attribute 'unknown')
|
|
42
|
+
|
|
43
|
+
One can check for an attribute by appending a "?". If the attribute is not defined this returns +nil+ instead of raising an error.
|
|
44
|
+
|
|
45
|
+
imm.a? # 'a-value'
|
|
46
|
+
imm.unknown? # nil
|
|
47
|
+
imm.foo?.bar? # also nil
|
|
48
|
+
|
|
49
|
+
When building an immutable one can provide a "fallback" hash in addition to the returned hash. If a key does not exist in the passed in data it will be fetched from the "fallback" data. This is rarely useful, but can provide effectove optimization in some cases.
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.0
|
|
1
|
+
1.1.0
|
data/lib/simple/immutable.rb
CHANGED
|
@@ -6,14 +6,17 @@ class Simple::Immutable
|
|
|
6
6
|
|
|
7
7
|
# turns an object, which can be a hash or array of hashes, arrays, and scalars
|
|
8
8
|
# into an object which you can use to access with dot methods.
|
|
9
|
-
def self.create(object, max_depth
|
|
9
|
+
def self.create(object, max_depth: 8, null_record: nil, fallback: nil)
|
|
10
|
+
# Note that null_record is deprecated.
|
|
11
|
+
fallback ||= null_record
|
|
12
|
+
|
|
10
13
|
case object
|
|
11
14
|
when Array
|
|
12
15
|
raise ArgumentError, "Object nested too deep (or inner loop?)" if max_depth < 0
|
|
13
16
|
|
|
14
|
-
object.map { |obj| create obj, max_depth - 1 }
|
|
17
|
+
object.map { |obj| create obj, fallback: fallback, max_depth: max_depth - 1 }
|
|
15
18
|
when Hash
|
|
16
|
-
new(object)
|
|
19
|
+
new(object, fallback)
|
|
17
20
|
else
|
|
18
21
|
object
|
|
19
22
|
end
|
|
@@ -22,7 +25,14 @@ class Simple::Immutable
|
|
|
22
25
|
def self.raw_data(immutable)
|
|
23
26
|
case immutable
|
|
24
27
|
when SELF
|
|
25
|
-
immutable.instance_variable_get :@hsh
|
|
28
|
+
hsh = immutable.instance_variable_get :@hsh
|
|
29
|
+
fallback = immutable.instance_variable_get :@fallback
|
|
30
|
+
|
|
31
|
+
if fallback
|
|
32
|
+
fallback.merge(hsh)
|
|
33
|
+
else
|
|
34
|
+
hsh
|
|
35
|
+
end
|
|
26
36
|
when Array
|
|
27
37
|
immutable.map { |e| raw_data(e) }
|
|
28
38
|
else
|
|
@@ -30,39 +40,88 @@ class Simple::Immutable
|
|
|
30
40
|
end
|
|
31
41
|
end
|
|
32
42
|
|
|
43
|
+
# adds to_json support.
|
|
44
|
+
def as_json(opts)
|
|
45
|
+
data = SELF.raw_data(self)
|
|
46
|
+
data.as_json(opts)
|
|
47
|
+
end
|
|
48
|
+
|
|
33
49
|
private
|
|
34
50
|
|
|
35
|
-
def initialize(hsh)
|
|
51
|
+
def initialize(hsh, fallback = nil)
|
|
36
52
|
@hsh = hsh
|
|
53
|
+
@fallback = fallback
|
|
37
54
|
end
|
|
38
55
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
56
|
+
# rubycop:disable Lint/IneffectiveAccessModifier
|
|
57
|
+
|
|
58
|
+
# fetches key from a hsh, regardless of it being a Symbol or a String.
|
|
59
|
+
# Yields the block if the key cannot be found.
|
|
60
|
+
def self.fetch_symbol_or_string_from_hash(hsh, key, &block)
|
|
61
|
+
if hsh
|
|
62
|
+
hsh.fetch(key.to_sym) do
|
|
63
|
+
hsh.fetch(key.to_s, &block)
|
|
47
64
|
end
|
|
65
|
+
else
|
|
66
|
+
yield
|
|
48
67
|
end
|
|
68
|
+
end
|
|
49
69
|
|
|
50
|
-
|
|
70
|
+
def method_missing(sym, *args, &block)
|
|
71
|
+
raise ArgumentError, "Immutable: called attribute method with arguments" unless args.empty?
|
|
72
|
+
raise ArgumentError, "Immutable: called attribute method with arguments" if block
|
|
73
|
+
|
|
74
|
+
if sym.end_with?("?")
|
|
75
|
+
# If the symbol ends in "?" we do not raise a NameError, but instead
|
|
76
|
+
# returns nil if the attribute is not known.
|
|
77
|
+
sym = sym[0...-1]
|
|
78
|
+
|
|
79
|
+
# Note that sym is now a String. However, we are String/Symbol agnostic
|
|
80
|
+
# (in fetch_symbol_or_string_from_hash), so this is ok.
|
|
81
|
+
fetch_attribute!(sym, raise_when_missing: false)
|
|
82
|
+
else
|
|
83
|
+
fetch_attribute!(sym, raise_when_missing: true)
|
|
84
|
+
end
|
|
51
85
|
end
|
|
52
86
|
|
|
53
87
|
public
|
|
54
88
|
|
|
89
|
+
def fetch_attribute!(sym, raise_when_missing:)
|
|
90
|
+
value = SELF.fetch_symbol_or_string_from_hash(@hsh, sym) do
|
|
91
|
+
SELF.fetch_symbol_or_string_from_hash(@fallback, sym) do
|
|
92
|
+
raise NameError, "unknown immutable attribute '#{sym}'" if raise_when_missing
|
|
93
|
+
|
|
94
|
+
nil
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
SELF.create(value)
|
|
99
|
+
end
|
|
100
|
+
|
|
55
101
|
def inspect
|
|
56
|
-
|
|
102
|
+
if @fallback
|
|
103
|
+
"<#{self.class.name}: #{@hsh.inspect}, w/fallback: #{@fallback.inspect}>"
|
|
104
|
+
else
|
|
105
|
+
"<#{self.class.name}: #{@hsh.inspect}>"
|
|
106
|
+
end
|
|
57
107
|
end
|
|
58
108
|
|
|
59
109
|
def respond_to_missing?(method_name, include_private = false)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
110
|
+
return true if method_name.end_with?("?")
|
|
111
|
+
|
|
112
|
+
key = method_name.to_sym
|
|
113
|
+
return true if @hsh.key?(key)
|
|
114
|
+
return true if @fallback&.key?(key)
|
|
115
|
+
|
|
116
|
+
key = method_name.to_s
|
|
117
|
+
return true if @hsh.key?(key)
|
|
118
|
+
return true if @fallback&.key?(key)
|
|
119
|
+
|
|
120
|
+
super
|
|
63
121
|
end
|
|
64
122
|
|
|
65
|
-
|
|
123
|
+
# rubocop:disable Style/OptionalBooleanParameter
|
|
124
|
+
def respond_to?(sym, include_all = false)
|
|
66
125
|
super || @hsh.key?(sym.to_s) || @hsh.key?(sym.to_sym)
|
|
67
126
|
end
|
|
68
127
|
|
data/scripts/test
ADDED
data/test/immutable_test.rb
CHANGED
|
@@ -16,11 +16,11 @@ class Simple::Immutable::TestCase < Test::Unit::TestCase
|
|
|
16
16
|
name: "grandchildname"
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
|
-
"children"
|
|
19
|
+
"children" => [
|
|
20
20
|
"anna",
|
|
21
21
|
"arthur",
|
|
22
22
|
{
|
|
23
|
-
action
|
|
23
|
+
"action" => {
|
|
24
24
|
keep_your_mouth_shut: true
|
|
25
25
|
}
|
|
26
26
|
}
|
|
@@ -60,6 +60,22 @@ class Simple::Immutable::TestCase < Test::Unit::TestCase
|
|
|
60
60
|
assert_equal true, immutable.children[2].action.keep_your_mouth_shut
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
+
def test_child_access_w_question_mark
|
|
64
|
+
child = immutable.child?
|
|
65
|
+
assert_kind_of(Immutable, child)
|
|
66
|
+
assert_equal "childname", immutable.child.name?
|
|
67
|
+
assert_equal "grandchildname", immutable.child?.grandchild?.name?
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def test_array_access_w_question_mark
|
|
71
|
+
assert_kind_of(Array, immutable.children?)
|
|
72
|
+
assert_equal 3, immutable.children?.length
|
|
73
|
+
assert_equal "anna", immutable.children?[0]
|
|
74
|
+
|
|
75
|
+
assert_kind_of(Immutable, immutable.children?[2])
|
|
76
|
+
assert_equal true, immutable.children[2].action?.keep_your_mouth_shut?
|
|
77
|
+
end
|
|
78
|
+
|
|
63
79
|
def test_base_class
|
|
64
80
|
assert_nothing_raised do
|
|
65
81
|
immutable.object_id
|
|
@@ -67,28 +83,30 @@ class Simple::Immutable::TestCase < Test::Unit::TestCase
|
|
|
67
83
|
end
|
|
68
84
|
|
|
69
85
|
def test_missing_keys
|
|
70
|
-
assert_raise(
|
|
86
|
+
assert_raise(NameError) do
|
|
71
87
|
immutable.foo
|
|
72
88
|
end
|
|
73
89
|
end
|
|
74
90
|
|
|
75
91
|
def test_skip_when_args_or_block
|
|
76
|
-
# raise
|
|
77
|
-
assert_raise(
|
|
92
|
+
# raise ArgumentError when called with arguments
|
|
93
|
+
assert_raise(ArgumentError) do
|
|
78
94
|
immutable.a(1, 2, 3)
|
|
79
95
|
end
|
|
96
|
+
assert_raise(ArgumentError) do
|
|
97
|
+
immutable.a(one: 1)
|
|
98
|
+
end
|
|
80
99
|
|
|
81
|
-
# raise
|
|
82
|
-
assert_raise(
|
|
100
|
+
# raise ArgumentError when called with a block argument
|
|
101
|
+
assert_raise(ArgumentError) do
|
|
83
102
|
immutable.a { :dummy }
|
|
84
103
|
end
|
|
85
104
|
end
|
|
86
105
|
|
|
87
106
|
def test_raw_data
|
|
88
|
-
immutable = Immutable.create(hsh)
|
|
89
107
|
expected = hsh
|
|
90
108
|
|
|
91
109
|
assert_equal(expected, Immutable.raw_data(immutable))
|
|
92
|
-
assert_equal(expected[
|
|
110
|
+
assert_equal(expected["children"], Immutable.raw_data(immutable.children))
|
|
93
111
|
end
|
|
94
112
|
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# rubocop:disable Metrics/AbcSize
|
|
2
|
+
|
|
3
|
+
require "test-unit"
|
|
4
|
+
require_relative "../lib/simple-immutable"
|
|
5
|
+
|
|
6
|
+
class Simple::Immutable::WithFallbackTestCase < Test::Unit::TestCase
|
|
7
|
+
Immutable = ::Simple::Immutable
|
|
8
|
+
|
|
9
|
+
def hsh
|
|
10
|
+
{
|
|
11
|
+
a: "a-value",
|
|
12
|
+
"b": "b-value",
|
|
13
|
+
"child": {
|
|
14
|
+
name: "childname",
|
|
15
|
+
grandchild: {
|
|
16
|
+
name: "grandchildname"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"children" => [
|
|
20
|
+
"anna",
|
|
21
|
+
"arthur",
|
|
22
|
+
{
|
|
23
|
+
"action" => {
|
|
24
|
+
keep_your_mouth_shut: true
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def fallback
|
|
32
|
+
{
|
|
33
|
+
foo: "foo-value",
|
|
34
|
+
"bar" => "bar-value"
|
|
35
|
+
}
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def immutable
|
|
39
|
+
Immutable.create hsh, fallback: fallback
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def test_hash_access
|
|
43
|
+
assert_equal "a-value", immutable.a
|
|
44
|
+
assert_equal "b-value", immutable.b
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def test_fallback_access
|
|
48
|
+
assert_equal "foo-value", immutable.foo
|
|
49
|
+
assert_equal "bar-value", immutable.bar
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_missing_keys
|
|
53
|
+
assert_raise(NameError) do
|
|
54
|
+
immutable.unknown
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def test_raw_data
|
|
59
|
+
# raw data does contain fallback
|
|
60
|
+
expected = hsh.merge(fallback)
|
|
61
|
+
assert_equal(expected, Immutable.raw_data(immutable))
|
|
62
|
+
end
|
|
63
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: simple-immutable
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- radiospiel
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date:
|
|
12
|
+
date: 2021-10-06 00:00:00.000000000 Z
|
|
13
13
|
dependencies: []
|
|
14
14
|
description: Immutable ruby objects implementing dot and [] accessors.
|
|
15
15
|
email: eno@radiospiel.org
|
|
@@ -31,8 +31,10 @@ files:
|
|
|
31
31
|
- lib/simple/immutable.rb
|
|
32
32
|
- scripts/release
|
|
33
33
|
- scripts/release.rb
|
|
34
|
+
- scripts/test
|
|
34
35
|
- simple-immutable.gemspec
|
|
35
36
|
- test/immutable_test.rb
|
|
37
|
+
- test/immutable_w_fallback_test.rb
|
|
36
38
|
homepage: http://github.com/radiospiel/simple-immutable
|
|
37
39
|
licenses: []
|
|
38
40
|
metadata: {}
|
|
@@ -51,9 +53,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
51
53
|
- !ruby/object:Gem::Version
|
|
52
54
|
version: '0'
|
|
53
55
|
requirements: []
|
|
54
|
-
rubygems_version: 3.
|
|
56
|
+
rubygems_version: 3.1.4
|
|
55
57
|
signing_key:
|
|
56
58
|
specification_version: 4
|
|
57
59
|
summary: Immutable ruby objects
|
|
58
60
|
test_files:
|
|
59
61
|
- test/immutable_test.rb
|
|
62
|
+
- test/immutable_w_fallback_test.rb
|