invariable 0.1.0 → 0.1.5

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: 9c53305cea17a18d1408e1a877638284b336815349579758e185c01a9a5cc344
4
- data.tar.gz: aeb9efc0033747f03a137d39c118e5d961e6cca2c805b4736043edf37b7ae5ad
3
+ metadata.gz: 16fb490a9818d273fc7871107dbe7f3e0c6c2bd8c4e3900c4d72e612e2979b53
4
+ data.tar.gz: 827067332845002a0a6d2c58782ba664d393a19cf0177160e4ae7f9dfb005357
5
5
  SHA512:
6
- metadata.gz: 437de015755448224c646e7e4a7cd07063658e75181442a437018869d40ee845eabd9f89823b7d6cefdcb3f0483193f0437ff19d1760184a76385e5089826fc2
7
- data.tar.gz: a4a18c103fcfa25451c7be911640f870e236f782385283099829580fc3373b667c9a1c2e21fbcfcbd03d4ce3319c7c3b81520fa14abc0a3c38a96d5c05bc0cde
6
+ metadata.gz: 188bd0745fd85c54e7a41c9f0d5efd6e4ae2635791214c86d021791142bf5eacf1311b1839577d7f47e84966a903d86748929ee5efa5b5c0942f127a8fd39cc9
7
+ data.tar.gz: 2bbbf4d596a92b8e13f10a445ad5cedd48c989cdd4ceec065f20c5385da7b0240b0748f225f5efb35e747328ba3cc1fad43ab9292bbb9d34607f759200152e3e
data/.yardopts CHANGED
@@ -1,5 +1,5 @@
1
1
  --readme README.md
2
- --title 'tcp-client Documentation'
2
+ --title 'Invariable Documentation'
3
3
  --charset utf-8
4
4
  --markup markdown
5
5
  lib/**/*.rb - LICENSE
data/README.md CHANGED
@@ -7,7 +7,7 @@ An Invariable can be created explicitly as a Class like a Struct. Or existing cl
7
7
 
8
8
  - Gem: [rubygems.org](https://rubygems.org/gems/invariable)
9
9
  - Source: [github.com](https://github.com/mblumtritt/invariable)
10
- - Help: [rubydoc.info](https://rubydoc.info/github/mblumtritt/invariable/main/index)
10
+ - Help: [rubydoc.info](https://rubydoc.info/gems/invariable)
11
11
 
12
12
  ## Sample
13
13
 
@@ -34,13 +34,13 @@ john.dig(:address, :city) #=> "Anytown"
34
34
 
35
35
  ```
36
36
 
37
- For more samples see [the samples dir](https://github.com/mblumtritt/invariable/tree/main/samples)
37
+ For more samples see [the samples dir](./examples)
38
38
 
39
39
  ## Installation
40
40
 
41
- Use [Bundler](http://gembundler.com/) to use TCPClient in your own project:
41
+ Use [Bundler](http://gembundler.com/) to use Invariiable in your own project:
42
42
 
43
- Add to your `Gemfile`:
43
+ Include in your `Gemfile`:
44
44
 
45
45
  ```ruby
46
46
  gem 'invariable'
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Invariable
4
4
  # current version number
5
- VERSION = '0.1.0'
5
+ VERSION = '0.1.5'
6
6
  end
data/lib/invariable.rb CHANGED
@@ -36,12 +36,12 @@ module Invariable
36
36
  # @!method attributes(*names, **defaults)
37
37
  # Defines new attributes
38
38
  # @param names [Array<Symbol>] attribute names
39
- # @param defaults [Hash<Symbol,Object|Class>] attribute names with default
39
+ # @param defaults [{Symbol => Object, Class}] attribute names with default
40
40
  # values
41
- # @return [Array<Symbols>] names of defined attributes
41
+ # @return [Array<Symbol>] names of defined attributes
42
42
  #
43
43
  # @!method member?(name)
44
- # @return [Boolean] wheter the given name is a valid attribute name for
44
+ # @return [Boolean] whether the given name is a valid attribute name for
45
45
  # this class
46
46
  #
47
47
 
@@ -77,7 +77,7 @@ module Invariable
77
77
  # Person.members #=> [:name, :last_name, :city, :zip, :street]
78
78
  #
79
79
  # @param names [Array<Symbol>] attribute names
80
- # @param defaults [Hash<Symbol,Object|Class>] attribute names with default
80
+ # @param defaults [{Symbol => Object, Class}] attribute names with default
81
81
  # values
82
82
  # @yieldparam new_class [Class] the created class
83
83
  #
@@ -128,7 +128,7 @@ module Invariable
128
128
  # This means that the given object needs to implement the same attributes and
129
129
  # all it's attribute values have to be equal.
130
130
  #
131
- # @return [Boolean] wheter the attribute values are equal
131
+ # @return [Boolean] whether the attribute values are equal
132
132
  #
133
133
  def ==(other)
134
134
  @__attr__.each_pair do |k, v|
@@ -223,7 +223,7 @@ module Invariable
223
223
  # Compares its class and all attributes of itself with the class and
224
224
  # attributes of a given other Object.
225
225
  #
226
- # @return [Boolean] wheter the classes and each attribute value are equal
226
+ # @return [Boolean] whether the classes and each attribute value are equal
227
227
  #
228
228
  # @see ==
229
229
  #
@@ -233,7 +233,7 @@ module Invariable
233
233
 
234
234
  # @!visibility private
235
235
  def hash
236
- (to_a << self.class).hash
236
+ @__hash__ ||= (to_a << self.class).hash
237
237
  end
238
238
 
239
239
  #
@@ -246,7 +246,7 @@ module Invariable
246
246
  alias to_s inspect
247
247
 
248
248
  #
249
- # @return [Boolean] wheter the given name is a valid attribute name
249
+ # @return [Boolean] whether the given name is a valid attribute name
250
250
  #
251
251
  def member?(name)
252
252
  @__attr__.key?(name)
@@ -284,10 +284,10 @@ module Invariable
284
284
 
285
285
  #
286
286
  # @overload to_h
287
- # @return [Hash<Symbol,Object>] names and values of all attributes
287
+ # @return [{Symbol => Object}] names and values of all attributes
288
288
  #
289
289
  # @overload to_h(compact: true)
290
- # @return [Hash<Symbol,Object>] names and values of all attributes which
290
+ # @return [{Symbol => Object}] names and values of all attributes which
291
291
  # are not `nil` and which are not empty Invariable results
292
292
  #
293
293
  # @overload to_h(&block)
@@ -297,7 +297,7 @@ module Invariable
297
297
  # @yieldparam [Object] value the attribute value
298
298
  # @yieldreturn [Array<Symbol,Object>] the pair to be stored in the result
299
299
  #
300
- # @return [Hash<Object,Object>] pairs returned by the `block`
300
+ # @return [{Object => Object}] pairs returned by the `block`
301
301
  #
302
302
  def to_h(compact: false, &block)
303
303
  return to_compact_h if compact
@@ -308,7 +308,7 @@ module Invariable
308
308
  #
309
309
  # Updates all given attributes.
310
310
  #
311
- # @return [Invariable] a new updated instance of itself
311
+ # @return [Invariable] a new updated instance
312
312
  def update(attributes)
313
313
  opts = {}
314
314
  @__attr__.each_pair do |k, v|
@@ -318,7 +318,7 @@ module Invariable
318
318
  end
319
319
 
320
320
  #
321
- # @return [Array<Object>] Array whose elements are the atttributes of self at
321
+ # @return [Array<Object>] Array whose elements are the attributes of self at
322
322
  # the given Integer indexes
323
323
  def values_at(...)
324
324
  @__attr__.values.values_at(...)
metadata CHANGED
@@ -1,71 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: invariable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Blumtritt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-12-26 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: bundler
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
- - !ruby/object:Gem::Dependency
28
- name: rake
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: rspec
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: yard
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
11
+ date: 2022-07-31 00:00:00.000000000 Z
12
+ dependencies: []
69
13
  description: |
70
14
  An Invariable bundles a number of read-only attributes.
71
15
  It can be used like a Hash as well as an Array.
@@ -77,28 +21,18 @@ extra_rdoc_files:
77
21
  - README.md
78
22
  - LICENSE
79
23
  files:
80
- - ".gitignore"
81
24
  - ".yardopts"
82
25
  - LICENSE
83
26
  - README.md
84
- - gems.rb
85
- - invariable.gemspec
86
27
  - lib/invariable.rb
87
28
  - lib/invariable/version.rb
88
- - rakefile.rb
89
- - samples/http_options.rb
90
- - samples/person.rb
91
- - spec/helper.rb
92
- - spec/invariable_include_spec.rb
93
- - spec/invariable_new_spec.rb
94
- - spec/invariable_spec.rb
95
29
  homepage: https://github.com/mblumtritt/invariable
96
30
  licenses:
97
31
  - BSD-3-Clause
98
32
  metadata:
99
33
  source_code_uri: https://github.com/mblumtritt/invariable
100
34
  bug_tracker_uri: https://github.com/mblumtritt/invariable/issues
101
- documentation_uri: https://rubydoc.info/github/mblumtritt/invariable
35
+ documentation_uri: https://rubydoc.info/gems/invariable
102
36
  post_install_message:
103
37
  rdoc_options: []
104
38
  require_paths:
@@ -114,12 +48,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
114
48
  - !ruby/object:Gem::Version
115
49
  version: '0'
116
50
  requirements: []
117
- rubygems_version: 3.3.3
51
+ rubygems_version: 3.3.7
118
52
  signing_key:
119
53
  specification_version: 4
120
54
  summary: The Invariable data class for Ruby.
121
- test_files:
122
- - spec/helper.rb
123
- - spec/invariable_include_spec.rb
124
- - spec/invariable_new_spec.rb
125
- - spec/invariable_spec.rb
55
+ test_files: []
data/.gitignore DELETED
@@ -1,6 +0,0 @@
1
- tmp/
2
- pkg/
3
- local/
4
- doc/
5
- .yardoc/
6
- gems.locked
data/gems.rb DELETED
@@ -1,4 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source 'https://rubygems.org'
4
- gemspec
data/invariable.gemspec DELETED
@@ -1,35 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative './lib/invariable/version'
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = 'invariable'
7
- spec.version = Invariable::VERSION
8
- spec.required_ruby_version = '>= 2.7.0'
9
-
10
- spec.author = 'Mike Blumtritt'
11
- spec.summary = 'The Invariable data class for Ruby.'
12
- spec.description = <<~description
13
- An Invariable bundles a number of read-only attributes.
14
- It can be used like a Hash as well as an Array.
15
- It supports subclassing and pattern matching.
16
- description
17
-
18
- spec.homepage = 'https://github.com/mblumtritt/invariable'
19
- spec.license = 'BSD-3-Clause'
20
- spec.metadata.merge!(
21
- 'source_code_uri' => 'https://github.com/mblumtritt/invariable',
22
- 'bug_tracker_uri' => 'https://github.com/mblumtritt/invariable/issues',
23
- 'documentation_uri' => 'https://rubydoc.info/github/mblumtritt/invariable'
24
- )
25
-
26
- spec.add_development_dependency 'bundler'
27
- spec.add_development_dependency 'rake'
28
- spec.add_development_dependency 'rspec'
29
- spec.add_development_dependency 'yard'
30
-
31
- all_files = Dir.chdir(__dir__) { `git ls-files -z`.split(0.chr) }
32
- spec.test_files = all_files.grep(%r{^spec/})
33
- spec.files = all_files - spec.test_files
34
- spec.extra_rdoc_files = %w[README.md LICENSE]
35
- end
data/rakefile.rb DELETED
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'rake/clean'
4
- require 'bundler/gem_tasks'
5
- require 'rspec/core/rake_task'
6
- require 'yard'
7
-
8
- $stdout.sync = $stderr.sync = true
9
- CLOBBER << 'prj' << '.yardoc' << 'doc'
10
- task(:default) { exec('rake --tasks') }
11
- RSpec::Core::RakeTask.new { |task| task.ruby_opts = %w[-w] }
12
- YARD::Rake::YardocTask.new { |task| task.stats_options = %w[--list-undoc] }
@@ -1,90 +0,0 @@
1
- # frozen_string_literal: true
2
- #
3
- # Sample to use Invariables as complex options.
4
- # See the options used for Net::HTTP.start to refer the sample.
5
- #
6
-
7
- require_relative '../lib/invariable'
8
-
9
- #
10
- # HTTP Options
11
- # HTTPOptions#to_h is used to generate the options Hash provided to
12
- # Net::HTTP.start (see there).
13
- #
14
- class HTTPOptions
15
- include Invariable
16
-
17
- attributes :open_timeout,
18
- :read_timeout,
19
- :write_timeout,
20
- :continue_timeout,
21
- :keep_alive_timeout,
22
- :close_on_empty_response
23
-
24
- attribute proxy: Invariable.new(:from_env, :address, :port, :user, :pass)
25
-
26
- attribute ssl:
27
- Invariable.new(
28
- :ca_file,
29
- :ca_path,
30
- :cert,
31
- :cert_store,
32
- :ciphers,
33
- :extra_chain_cert,
34
- :key,
35
- :timeout,
36
- :version,
37
- :min_version,
38
- :max_version,
39
- :verify_callback,
40
- :verify_depth,
41
- :verify_mode,
42
- :verify_hostname
43
- )
44
-
45
- #
46
- # Superseded to add some magic and to flatten the Hash provided to eg.
47
- # Net::HTTP.start.
48
- #
49
- def to_h
50
- # the compact option allows to skip all values of nil and all empty
51
- # Invariable values
52
- result = super(compact: true)
53
-
54
- # flatten the SSL options:
55
- ssl = result.delete(:ssl)
56
- if ssl
57
- # prefix two options:
58
- ssl[:ssl_timeout] = ssl.delete(:timeout) if ssl.key?(:timeout)
59
- ssl[:ssl_version] = ssl.delete(:version) if ssl.key?(:version)
60
- result.merge!(ssl)
61
-
62
- # automagic :)
63
- result[:use_ssl] = true
64
- end
65
-
66
- # flatten the proxy options and prefix the keys
67
- proxy = result.delete(:proxy)
68
- if proxy
69
- result.merge!(proxy.transform_keys! { |key| "proxy_#{key}".to_sym })
70
- end
71
-
72
- result
73
- end
74
- end
75
-
76
- puts '- create a sample'
77
- sample =
78
- HTTPOptions.new(
79
- open_timeout: 2,
80
- read_timeout: 2,
81
- write_timeout: 2,
82
- ssl: {
83
- timeout: 2,
84
- min_version: :TLS1_2
85
- },
86
- proxy: {
87
- from_env: true
88
- }
89
- )
90
- p sample.to_h
data/samples/person.rb DELETED
@@ -1,49 +0,0 @@
1
- # frozen_string_literal: true
2
- #
3
- # This sample shows the different aspects of Invariable.
4
- #
5
-
6
- require_relative '../lib/invariable'
7
-
8
- #
9
- # Person is a sample class which is combined from primitives as well as an
10
- # anonymous Invariable class used for the address attribute.
11
- #
12
- class Person
13
- include Invariable
14
- attributes :name, :last_name, address: Invariable.new(:city, :zip, :street)
15
-
16
- def full_name
17
- "#{name} #{last_name}"
18
- end
19
-
20
- def to_s
21
- address.to_a.unshift(full_name).compact.join(', ')
22
- end
23
- end
24
-
25
- puts '- we can check the members of the class'
26
- p Person.members #=> [:name, :last_name, :address]
27
- p Person.member?(:last_name) #=> true
28
-
29
- puts '- create a person record'
30
- john = Person.new(name: 'John', last_name: 'Doe')
31
- puts john #=> "John Doe"
32
-
33
- puts '- we can check the members of the instance'
34
- p john.members #=> [:name, :last_name, :address]
35
- p john.member?(:last_name) #=> true
36
-
37
- puts '- the address members are nil'
38
- p john.address.city #=> nil
39
-
40
- puts '- converted to an compact Hash the address is skipped'
41
- p john.to_h(compact: true) #=> {:name=>"John", :last_name=>"Doe"}
42
-
43
- puts '- update the record with an address'
44
- john =
45
- john.update(address: { street: '123 Main St', city: 'Anytown', zip: '45678' })
46
-
47
- puts '- the city is assigned now'
48
- p john.dig(:address, :city) #=> "Anytown"
49
- puts john #=> John Doe, Anytown, 45678, 123 Main St
data/spec/helper.rb DELETED
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'rspec/core'
4
- require_relative '../lib/invariable'
5
-
6
- $stdout.sync = $stderr.sync = true
7
-
8
- RSpec.configure do |config|
9
- config.disable_monkey_patching!
10
- config.warnings = true
11
- config.order = :random
12
- end
@@ -1,97 +0,0 @@
1
- # frozen_string_literal: true
2
- require_relative 'helper'
3
-
4
- RSpec.describe 'include Invariable' do
5
- let(:invariable) do
6
- Class.new do
7
- include Invariable
8
- attribute :name
9
- attribute :last_name
10
- end
11
- end
12
-
13
- it 'defines all attributes' do
14
- expect(invariable.members).to eq %i[name last_name]
15
- end
16
-
17
- it 'initializes the attributes' do
18
- instance = invariable.new(name: 'John', last_name: 'Doe')
19
- expect(instance.name).to eq 'John'
20
- expect(instance.last_name).to eq 'Doe'
21
- end
22
-
23
- it 'initializes only given attributes' do
24
- instance = invariable.new(last_name: 'Doe')
25
- expect(instance.name).to be_nil
26
- expect(instance.last_name).to eq 'Doe'
27
- end
28
-
29
- it 'ignores unknown attributes' do
30
- expect {
31
- invariable.new(foo: 42, last_name: 'Doe', ignored: true)
32
- }.not_to raise_error
33
- end
34
-
35
- context 'when defining an already defined attribute' do
36
- it 'raises an exception' do
37
- expect do
38
- Class.new do
39
- include Invariable
40
- attribute :name
41
- attribute :name
42
- end
43
- end.to raise_error(NameError, 'attribute already defined - name')
44
- end
45
- end
46
-
47
- context 'when used in sub-classing' do
48
- let(:invariable) { Class.new(base_class) { attributes :street, :city } }
49
- let(:base_class) do
50
- Class.new do
51
- include Invariable
52
- attributes :name, :last_name
53
- end
54
- end
55
-
56
- it 'defines all attributes' do
57
- expect(invariable.members).to eq %i[name last_name street city]
58
- end
59
-
60
- it 'initializes the attributes' do
61
- instance =
62
- invariable.new(
63
- name: 'John',
64
- last_name: 'Doe',
65
- street: '123 Main St',
66
- city: 'Anytown'
67
- )
68
-
69
- expect(instance.name).to eq 'John'
70
- expect(instance.last_name).to eq 'Doe'
71
- expect(instance.street).to eq '123 Main St'
72
- expect(instance.city).to eq 'Anytown'
73
- end
74
-
75
- it 'initializes only given attributes' do
76
- instance = invariable.new(last_name: 'Doe', city: 'Anytown')
77
- expect(instance.name).to be_nil
78
- expect(instance.last_name).to eq 'Doe'
79
- expect(instance.street).to be_nil
80
- expect(instance.city).to eq 'Anytown'
81
- end
82
-
83
- it 'ignores unknown attributes' do
84
- expect {
85
- invariable.new(foo: 42, city: 'Anytown', ignored: true)
86
- }.not_to raise_error
87
- end
88
-
89
- context 'when defining an already defined attribute of the superclass' do
90
- it 'raises an exception' do
91
- expect do
92
- Class.new(base_class){ attribute :name }
93
- end.to raise_error(NameError, 'attribute already defined - name')
94
- end
95
- end
96
- end
97
- end
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
- require_relative 'helper'
3
-
4
- RSpec.describe 'Invariable.new' do
5
- context 'when only attribute names are given' do
6
- subject(:invariable) { Invariable.new(:name, :last_name) }
7
-
8
- it 'creates a new Class' do
9
- expect(invariable).to be_a Class
10
- end
11
-
12
- it 'is inherited from Object' do
13
- expect(invariable).to be < Object
14
- end
15
-
16
- it 'defines the attributes as instance methods' do
17
- expect(invariable).to be_public_method_defined :name
18
- expect(invariable).to be_public_method_defined :last_name
19
- end
20
- end
21
-
22
- context 'when a base class and attribute names are given' do
23
- subject(:invariable) { Invariable.new(foo_class, :name, :last_name) }
24
- let(:foo_class) { Class.new }
25
-
26
- it 'creates a new Class' do
27
- expect(invariable).to be_a Class
28
- end
29
-
30
- it 'is inherited from the given class' do
31
- expect(invariable).to be < foo_class
32
- end
33
-
34
- it 'defines the attributes as instance methods' do
35
- expect(invariable).to be_public_method_defined :name
36
- expect(invariable).to be_public_method_defined :last_name
37
- end
38
- end
39
-
40
- context 'when a block is given' do
41
- subject(:invariable) do
42
- Invariable.new(:name, :last_name) do
43
- def full_name
44
- "#{name} #{last_name}"
45
- end
46
- end
47
- end
48
-
49
- it 'allows to extend the new class' do
50
- expect(invariable).to be_public_method_defined :full_name
51
- end
52
- end
53
- end
@@ -1,291 +0,0 @@
1
- # frozen_string_literal: true
2
- require_relative 'helper'
3
-
4
- RSpec.describe Invariable do
5
- subject(:instance) do
6
- sample_class.new(
7
- name: 'John',
8
- last_name: 'Doe',
9
- address: {
10
- zip: '45678',
11
- city: 'Anytown',
12
- street: '123 Main St'
13
- }
14
- )
15
- end
16
-
17
- let(:sample_class) do
18
- Class.new do
19
- include Invariable
20
- attributes :name, :last_name
21
- attribute address: Invariable.new(:city, :zip, :street)
22
-
23
- def full_name
24
- "#{name} #{last_name}"
25
- end
26
- end
27
- end
28
-
29
- context 'attributes' do
30
- it 'allows to read the attributes by name' do
31
- expect(instance.name).to eq 'John'
32
- expect(instance.last_name).to eq 'Doe'
33
- expect(instance.address).to be_a Invariable
34
- end
35
-
36
- it 'provides information about its attributes' do
37
- expect(instance.members).to eq %i[name last_name address]
38
- end
39
-
40
- it 'can be checked whether an attribute is defined' do
41
- expect(instance.member?(:last_name)).to be true
42
- expect(instance.member?(:city)).to be false
43
- end
44
- end
45
-
46
- context 'Hash-like behavior' do
47
- it 'provides Hash-like attribute access' do
48
- expect(instance[:name]).to eq 'John'
49
- expect(instance[:last_name]).to eq 'Doe'
50
- expect(instance[:address][:city]).to eq 'Anytown'
51
- end
52
-
53
- context 'when the attribute name is unknown' do
54
- it 'raises a NameError' do
55
- expect { instance[:size_of_shoe] }.to raise_error(
56
- NameError,
57
- 'not member - size_of_shoe'
58
- )
59
- end
60
- end
61
-
62
- it 'can be converted into a Hash' do
63
- expect(instance.to_h).to eq(
64
- name: 'John',
65
- last_name: 'Doe',
66
- address: {
67
- zip: '45678',
68
- city: 'Anytown',
69
- street: '123 Main St'
70
- }
71
- )
72
- end
73
-
74
- it 'can be converted into a customized Hash' do
75
- converted = instance.to_h { |key, value| ["my_#{key}", value] }
76
- expect(converted.keys).to eq %w[my_name my_last_name my_address]
77
- end
78
-
79
- it 'allows to iterate all attribute name/value pairs' do
80
- expect { |b| instance.each_pair(&b) }.to yield_successive_args(
81
- [:name, 'John'],
82
- [:last_name, 'Doe'],
83
- [:address, instance.address]
84
- )
85
- end
86
-
87
- it 'provides an Enumerable for its attributes name/value pairs' do
88
- expect(instance.each_pair).to be_a(Enumerable)
89
- end
90
-
91
- it 'can be converted to a compact Hash' do
92
- john = sample_class.new(name: 'John')
93
- expect(john.to_h(compact: true)).to eq(name: 'John')
94
- end
95
- end
96
-
97
- context 'Array-like behavior' do
98
- it 'provides its attribute count' do
99
- expect(instance.size).to be 3
100
- end
101
-
102
- it 'provides Array-like attribute access' do
103
- expect(instance[0]).to eq 'John'
104
- expect(instance[1]).to eq 'Doe'
105
- expect(instance[2]).to be instance.address
106
- expect(instance[-1]).to be instance.address
107
- end
108
-
109
- context 'when the access index is out of bounds' do
110
- it 'raises a NameError' do
111
- expect { instance[3] }.to raise_error(IndexError, 'invalid offset - 3')
112
- end
113
- end
114
-
115
- it 'can be converted into an Array' do
116
- expect(instance.to_a).to eq ['John', 'Doe', instance.address]
117
- end
118
-
119
- it 'allows to iterate all attribute values' do
120
- expect { |b| instance.each(&b) }.to yield_successive_args(
121
- 'John',
122
- 'Doe',
123
- instance.address
124
- )
125
- end
126
-
127
- it 'provides an Enumerable for its attribute values' do
128
- expect(instance.each).to be_a(Enumerable)
129
- end
130
- end
131
-
132
- context 'comparing' do
133
- it 'can be compared to other objects' do
134
- other =
135
- sample_class.new(
136
- name: 'John',
137
- last_name: 'Doe',
138
- address: {
139
- zip: '45678',
140
- city: 'Anytown',
141
- street: '123 Main St'
142
- }
143
- )
144
- expect(instance == other).to be true
145
-
146
- other =
147
- sample_class.new(
148
- name: 'John',
149
- last_name: 'Doe',
150
- address: {
151
- zip: '45678',
152
- city: 'Anytown',
153
- street: '124 Main St' # difffers
154
- }
155
- )
156
- expect(instance == other).to be false
157
-
158
- other =
159
- double(
160
- :other,
161
- name: 'John',
162
- last_name: 'Doe',
163
- address:
164
- double(
165
- :other_addr,
166
- zip: '45678',
167
- city: 'Anytown',
168
- street: '123 Main St'
169
- )
170
- )
171
- expect(instance == other).to be true
172
- end
173
-
174
- it 'can be tested for equality' do
175
- other =
176
- sample_class.new(
177
- name: 'John',
178
- last_name: 'Doe',
179
- address: {
180
- zip: '45678',
181
- city: 'Anytown',
182
- street: '123 Main St'
183
- }
184
- )
185
- expect(instance.eql?(other)).to be true
186
-
187
- other =
188
- sample_class.new(
189
- name: 'John',
190
- last_name: 'Doe',
191
- address: {
192
- zip: '45679', # differs
193
- city: 'Anytown',
194
- street: '123 Main St'
195
- }
196
- )
197
- expect(instance.eql?(other)).to be false
198
-
199
- other = # class differs
200
- double(
201
- :other,
202
- name: 'John',
203
- last_name: 'Doe',
204
- address: {
205
- zip: '45678',
206
- city: 'Anytown',
207
- street: '123 Main St'
208
- }
209
- )
210
- expect(instance.eql?(other)).to be false
211
- end
212
- end
213
-
214
- context '#dig pattern' do
215
- let(:data) { { person: instance } }
216
-
217
- it 'can be used with attribute names' do
218
- expect(data.dig(:person, :last_name)).to eq 'Doe'
219
- expect(data.dig(:person, :zip)).to be_nil
220
- expect(data.dig(:person, :address, :city)).to eq 'Anytown'
221
- end
222
-
223
- it 'can be used with indices' do
224
- expect(data.dig(:person, 1)).to eq 'Doe'
225
- expect(data.dig(:person, -1, :zip)).to eq '45678'
226
- end
227
- end
228
-
229
- context 'pattern matching' do
230
- it 'can be used for named pattern matching' do
231
- result =
232
- case instance
233
- in name: 'Fred', last_name: 'Doe'
234
- :fred
235
- in name: 'John', last_name: 'New'
236
- :not_john
237
- in name: 'John', last_name: 'Doe', address: { city: 'NY' }
238
- :john_from_ny
239
- in name: 'John', last_name: 'Doe', address: { city: 'Anytown' }
240
- :john
241
- else
242
- nil
243
- end
244
-
245
- expect(result).to be :john
246
- end
247
-
248
- it 'can be used for indexed pattern matching' do
249
- result =
250
- case instance
251
- in 'Fred', 'Doe', *_
252
- :fred
253
- in 'John', 'New', *_
254
- :not_john
255
- in 'John', 'Doe', *_
256
- :john
257
- else
258
- nil
259
- end
260
-
261
- expect(result).to be :john
262
- end
263
- end
264
-
265
- it 'allows to create an updated version of itself' do
266
- result =
267
- instance.update(
268
- name: 'Fred',
269
- address: {
270
- zip: '45678',
271
- city: 'Anytown',
272
- street: '124 Main St'
273
- }
274
- )
275
- expect(result).to be_a sample_class
276
- expect(result.name).to eq 'Fred'
277
- expect(result.last_name).to eq 'Doe'
278
- expect(result.address.to_h).to eq(
279
- zip: '45678',
280
- city: 'Anytown',
281
- street: '124 Main St'
282
- )
283
- end
284
-
285
- it 'can be inspected' do
286
- expect(instance.inspect).to include(' name: "John", last_name: "Doe"')
287
- expect(instance.inspect).to include(
288
- 'city: "Anytown", zip: "45678", street: "123 Main St"'
289
- )
290
- end
291
- end