simple-immutable 1.0.6 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/Makefile +5 -4
- data/README.md +13 -2
- data/VERSION +1 -1
- data/lib/simple/immutable.rb +54 -23
- data/scripts/test +4 -0
- data/test/immutable_test.rb +27 -8
- data/test/{immutable_w_null_record_test.rb → immutable_w_fallback_test.rb} +9 -9
- metadata +8 -7
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/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: 8, null_record: nil)
|
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,
|
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
|
@@ -23,10 +26,10 @@ class Simple::Immutable
|
|
23
26
|
case immutable
|
24
27
|
when SELF
|
25
28
|
hsh = immutable.instance_variable_get :@hsh
|
26
|
-
|
29
|
+
fallback = immutable.instance_variable_get :@fallback
|
27
30
|
|
28
|
-
if
|
29
|
-
|
31
|
+
if fallback
|
32
|
+
fallback.merge(hsh)
|
30
33
|
else
|
31
34
|
hsh
|
32
35
|
end
|
@@ -39,14 +42,15 @@ class Simple::Immutable
|
|
39
42
|
|
40
43
|
# adds to_json support.
|
41
44
|
def as_json(opts)
|
42
|
-
|
45
|
+
data = SELF.raw_data(self)
|
46
|
+
data.as_json(opts)
|
43
47
|
end
|
44
48
|
|
45
49
|
private
|
46
50
|
|
47
|
-
def initialize(hsh,
|
51
|
+
def initialize(hsh, fallback = nil)
|
48
52
|
@hsh = hsh
|
49
|
-
@
|
53
|
+
@fallback = fallback
|
50
54
|
end
|
51
55
|
|
52
56
|
# rubycop:disable Lint/IneffectiveAccessModifier
|
@@ -64,32 +68,59 @@ class Simple::Immutable
|
|
64
68
|
end
|
65
69
|
|
66
70
|
def method_missing(sym, *args, &block)
|
67
|
-
|
68
|
-
|
69
|
-
SELF.fetch_symbol_or_string_from_hash(@null_record, sym) do
|
70
|
-
# STDERR.puts "Missing attribute #{sym} for Immutable w/#{@hsh.inspect}"
|
71
|
-
super
|
72
|
-
end
|
73
|
-
end
|
71
|
+
raise ArgumentError, "Immutable: called attribute method with arguments" unless args.empty?
|
72
|
+
raise ArgumentError, "Immutable: called attribute method with arguments" if block
|
74
73
|
|
75
|
-
|
76
|
-
|
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]
|
77
78
|
|
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
|
79
85
|
end
|
80
86
|
|
81
87
|
public
|
82
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
|
+
|
83
101
|
def inspect
|
84
|
-
|
102
|
+
if @fallback
|
103
|
+
"<#{self.class.name}: #{@hsh.inspect}, w/fallback: #{@fallback.inspect}>"
|
104
|
+
else
|
105
|
+
"<#{self.class.name}: #{@hsh.inspect}>"
|
106
|
+
end
|
85
107
|
end
|
86
108
|
|
87
109
|
def respond_to_missing?(method_name, include_private = false)
|
88
|
-
|
89
|
-
|
90
|
-
|
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
|
91
121
|
end
|
92
122
|
|
123
|
+
# rubocop:disable Style/OptionalBooleanParameter
|
93
124
|
def respond_to?(sym, include_all = false)
|
94
125
|
super || @hsh.key?(sym.to_s) || @hsh.key?(sym.to_sym)
|
95
126
|
end
|
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,19 +83,22 @@ 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
|
@@ -88,6 +107,6 @@ class Simple::Immutable::TestCase < Test::Unit::TestCase
|
|
88
107
|
expected = hsh
|
89
108
|
|
90
109
|
assert_equal(expected, Immutable.raw_data(immutable))
|
91
|
-
assert_equal(expected[
|
110
|
+
assert_equal(expected["children"], Immutable.raw_data(immutable.children))
|
92
111
|
end
|
93
112
|
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
require "test-unit"
|
4
4
|
require_relative "../lib/simple-immutable"
|
5
5
|
|
6
|
-
class Simple::Immutable::
|
6
|
+
class Simple::Immutable::WithFallbackTestCase < Test::Unit::TestCase
|
7
7
|
Immutable = ::Simple::Immutable
|
8
8
|
|
9
9
|
def hsh
|
@@ -16,11 +16,11 @@ class Simple::Immutable::WithNullRecordTestCase < 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
|
}
|
@@ -28,7 +28,7 @@ class Simple::Immutable::WithNullRecordTestCase < Test::Unit::TestCase
|
|
28
28
|
}
|
29
29
|
end
|
30
30
|
|
31
|
-
def
|
31
|
+
def fallback
|
32
32
|
{
|
33
33
|
foo: "foo-value",
|
34
34
|
"bar" => "bar-value"
|
@@ -36,7 +36,7 @@ class Simple::Immutable::WithNullRecordTestCase < Test::Unit::TestCase
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def immutable
|
39
|
-
Immutable.create hsh,
|
39
|
+
Immutable.create hsh, fallback: fallback
|
40
40
|
end
|
41
41
|
|
42
42
|
def test_hash_access
|
@@ -44,20 +44,20 @@ class Simple::Immutable::WithNullRecordTestCase < Test::Unit::TestCase
|
|
44
44
|
assert_equal "b-value", immutable.b
|
45
45
|
end
|
46
46
|
|
47
|
-
def
|
47
|
+
def test_fallback_access
|
48
48
|
assert_equal "foo-value", immutable.foo
|
49
49
|
assert_equal "bar-value", immutable.bar
|
50
50
|
end
|
51
51
|
|
52
52
|
def test_missing_keys
|
53
|
-
assert_raise(
|
53
|
+
assert_raise(NameError) do
|
54
54
|
immutable.unknown
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
58
|
def test_raw_data
|
59
|
-
# raw data does contain
|
60
|
-
expected = hsh.merge(
|
59
|
+
# raw data does contain fallback
|
60
|
+
expected = hsh.merge(fallback)
|
61
61
|
assert_equal(expected, Immutable.raw_data(immutable))
|
62
62
|
end
|
63
63
|
end
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
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
|
8
8
|
- mediapeers GmbH
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2021-
|
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,13 +31,14 @@ 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
|
36
|
-
- test/
|
37
|
+
- test/immutable_w_fallback_test.rb
|
37
38
|
homepage: http://github.com/radiospiel/simple-immutable
|
38
39
|
licenses: []
|
39
40
|
metadata: {}
|
40
|
-
post_install_message:
|
41
|
+
post_install_message:
|
41
42
|
rdoc_options: []
|
42
43
|
require_paths:
|
43
44
|
- lib
|
@@ -53,9 +54,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
53
54
|
version: '0'
|
54
55
|
requirements: []
|
55
56
|
rubygems_version: 3.1.4
|
56
|
-
signing_key:
|
57
|
+
signing_key:
|
57
58
|
specification_version: 4
|
58
59
|
summary: Immutable ruby objects
|
59
60
|
test_files:
|
60
61
|
- test/immutable_test.rb
|
61
|
-
- test/
|
62
|
+
- test/immutable_w_fallback_test.rb
|