simple-immutable 1.0.3 → 1.1.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: d55dfd0cc8370b9bb68d8c1923770bc248d2eb4a2a71fe89717585366e17da78
4
- data.tar.gz: 50baec5315bdd9a6b6b9cfa389fa8cda388047b38f517f6ca7571d5a96de8926
3
+ metadata.gz: 4ecf8356b446062c2cc5eb9237759cb91eb8a91cba3f311b527f2c73aa675bd7
4
+ data.tar.gz: 2f6c6952068ff100208296f0715d7f8b444b34deb673c285c3fe24d34be38bf8
5
5
  SHA512:
6
- metadata.gz: 59c0ce8e1ae74f98e4c8b13cf6d1642182f83440a2eb111b1df853748adc4a21635900011da503f00d02990b2a5895a54fcde1fc66001b2679ed3169a719db47
7
- data.tar.gz: beb2b47f7096c2e3bf98a1b7bbf4c4a50360a992a4ae96a213512ce31ff1e00cf7106351fedc118846d0f9463c5f82ed04d8bad85bc79200106d1d32ea92965f
6
+ metadata.gz: e71dff987fe8ee9853348389d6d1f262bda8355e88ef0dd8847585ce803adeae1de65c2153df6904bf4727fd32f4baddc3cef72bdd6e6b973126927938a07a9e
7
+ data.tar.gz: daf98517ec14374466755c6075a5ee2b009f63cb245f213b39d39c98915ff8597ba6a1ba7dfa961a1ada6785bde8720e907a19fa6dfda8b405ae7e7e893b8885
data/.rubocop.yml CHANGED
@@ -30,3 +30,6 @@ Style/NumericPredicate:
30
30
 
31
31
  Metrics/MethodLength:
32
32
  Max: 20
33
+
34
+ Lint/IneffectiveAccessModifier:
35
+ Enabled: false
data/Gemfile CHANGED
@@ -3,4 +3,4 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in {gemname}.gemspec
4
4
  gemspec
5
5
 
6
- gem "rubocop", "~> 0.87.1"
6
+ gem "rubocop", "~> 1.20"
data/Makefile CHANGED
@@ -1,9 +1,11 @@
1
- default: test
1
+ default: test rubocop
2
2
 
3
- .PHONY: test
3
+ .PHONY: test rubocop release
4
4
 
5
5
  test:
6
- ruby test/immutable_test.rb
6
+ scripts/test
7
+
8
+ rubocop:
7
9
  bin/rubocop
8
10
 
9
11
  release:
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.3
1
+ 1.1.0
@@ -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)
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
- def method_missing(sym, *args, &block)
40
- if args.empty? && !block
41
- begin
42
- value = @hsh.fetch(sym.to_sym) { @hsh.fetch(sym.to_s) }
43
- return SELF.create(value)
44
- rescue KeyError
45
- # STDERR.puts "Missing attribute #{sym} for Immutable w/#{@hsh.inspect}"
46
- nil
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
- super
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
- "<#{self.class.name}: #{@hsh.inspect}>"
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
- @hsh.key?(method_name.to_sym) ||
61
- @hsh.key?(method_name.to_s) ||
62
- super
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
- def respond_to?(sym)
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
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ Dir.glob("test/**/*_test.rb").sort.each do |file|
3
+ load file
4
+ end
@@ -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(NoMethodError) do
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 NoMethodError when called with arguments
77
- assert_raise(NoMethodError) do
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 NoMethodError when called with a block argument
82
- assert_raise(NoMethodError) do
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[:children], Immutable.raw_data(immutable.children))
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.3
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: 2020-07-08 00:00:00.000000000 Z
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.0.6
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