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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8b64e7bb0ef88ead17af2b5d1bca5ea4dc3a19c566a441ee8fe519afe7ac47c3
4
- data.tar.gz: 4dedaf9317e816780dc86427b99a2b070a1ec46da22ca56446e8ceb120ba615a
3
+ metadata.gz: 4ecf8356b446062c2cc5eb9237759cb91eb8a91cba3f311b527f2c73aa675bd7
4
+ data.tar.gz: 2f6c6952068ff100208296f0715d7f8b444b34deb673c285c3fe24d34be38bf8
5
5
  SHA512:
6
- metadata.gz: '0029d88bfa9788f39948eacc0707af394d2702b31800ec86ef83e76f9e1b4dbeb672ce619877a5f0e9f09da93b9b1edcdf3a0dc471c84b73fd1fd8415f168a6e'
7
- data.tar.gz: abd71b228155f72f7953868cd445722c013b3ce9067d9ecc2075cd5c518a12905a0cd3ed768d7adec505291b53f6f383ce0fc6b63e2b1181ff0887f2fdf7c09c
6
+ metadata.gz: e71dff987fe8ee9853348389d6d1f262bda8355e88ef0dd8847585ce803adeae1de65c2153df6904bf4727fd32f4baddc3cef72bdd6e6b973126927938a07a9e
7
+ data.tar.gz: daf98517ec14374466755c6075a5ee2b009f63cb245f213b39d39c98915ff8597ba6a1ba7dfa961a1ada6785bde8720e907a19fa6dfda8b405ae7e7e893b8885
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,10 +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
7
- ruby test/immutable_w_null_record_test.rb
6
+ scripts/test
7
+
8
+ rubocop:
8
9
  bin/rubocop
9
10
 
10
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.6
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, 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, null_record: null_record, max_depth: max_depth - 1 }
17
+ object.map { |obj| create obj, fallback: fallback, max_depth: max_depth - 1 }
15
18
  when Hash
16
- new(object, null_record)
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
- null_record = immutable.instance_variable_get :@null_record
29
+ fallback = immutable.instance_variable_get :@fallback
27
30
 
28
- if null_record
29
- null_record.merge(hsh)
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
- @hsh.as_json(opts)
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, null_record = nil)
51
+ def initialize(hsh, fallback = nil)
48
52
  @hsh = hsh
49
- @null_record = null_record
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
- if args.empty? && !block
68
- value = SELF.fetch_symbol_or_string_from_hash(@hsh, sym) do
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
- return SELF.create(value)
76
- end
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
- super
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
- "<#{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
85
107
  end
86
108
 
87
109
  def respond_to_missing?(method_name, include_private = false)
88
- @hsh.key?(method_name.to_sym) ||
89
- @hsh.key?(method_name.to_s) ||
90
- 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
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
@@ -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,19 +83,22 @@ 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
@@ -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[:children], Immutable.raw_data(immutable.children))
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::WithNullRecordTestCase < Test::Unit::TestCase
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 null_record
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, null_record: null_record
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 test_null_record_access
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(NoMethodError) do
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 null_record
60
- expected = hsh.merge(null_record)
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.6
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-07-13 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,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/immutable_w_null_record_test.rb
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/immutable_w_null_record_test.rb
62
+ - test/immutable_w_fallback_test.rb