invariable 0.1.0 → 0.1.5

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 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