invariable 0.1.0 → 0.1.6
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 +4 -4
- data/.yardopts +1 -2
- data/README.md +4 -4
- data/lib/invariable/version.rb +1 -1
- data/lib/invariable.rb +25 -27
- metadata +6 -76
- data/.gitignore +0 -6
- data/gems.rb +0 -4
- data/invariable.gemspec +0 -35
- data/rakefile.rb +0 -12
- data/samples/http_options.rb +0 -90
- data/samples/person.rb +0 -49
- data/spec/helper.rb +0 -12
- data/spec/invariable_include_spec.rb +0 -97
- data/spec/invariable_new_spec.rb +0 -53
- data/spec/invariable_spec.rb +0 -291
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7383243bf91697ac7bf303aacee00e9d05e67ce84f44552c71ec6c1cb5f3f7cd
|
|
4
|
+
data.tar.gz: c76a032018adade6412f3bef21903bbed7ad9c55b1f7d4584ce01621ee31c2d7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f46f26ae8e84432bff462a31b686feb84a1d6eeeeb2d47251a6269d6a86bb9189ab766377e0976f80ac1390c898b1a6d68d2daf8d58c32bd7ccca28194f280d0
|
|
7
|
+
data.tar.gz: fa0fecd3fd9add3ea732ac6655c20be6e20acda2232c84d0d959397bddc89e050852a34f8d56822c34a5511712a0e2c14484006b321701947438f73bae15e7ae
|
data/.yardopts
CHANGED
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/
|
|
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](
|
|
37
|
+
For more samples see [the samples dir](./examples)
|
|
38
38
|
|
|
39
39
|
## Installation
|
|
40
40
|
|
|
41
|
-
Use [Bundler](http://gembundler.com/) to use
|
|
41
|
+
Use [Bundler](http://gembundler.com/) to use Invariiable in your own project:
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
Include in your `Gemfile`:
|
|
44
44
|
|
|
45
45
|
```ruby
|
|
46
46
|
gem 'invariable'
|
data/lib/invariable/version.rb
CHANGED
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 [
|
|
39
|
+
# @param defaults [{Symbol => Object, Class}] attribute names with default
|
|
40
40
|
# values
|
|
41
|
-
# @return [Array<
|
|
41
|
+
# @return [Array<Symbol>] names of defined attributes
|
|
42
42
|
#
|
|
43
43
|
# @!method member?(name)
|
|
44
|
-
# @return [Boolean]
|
|
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 [
|
|
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]
|
|
131
|
+
# @return [Boolean] whether the attribute values are equal
|
|
132
132
|
#
|
|
133
133
|
def ==(other)
|
|
134
134
|
@__attr__.each_pair do |k, v|
|
|
@@ -161,6 +161,11 @@ module Invariable
|
|
|
161
161
|
@__attr__.values[arg]
|
|
162
162
|
end
|
|
163
163
|
|
|
164
|
+
# @!visibility private
|
|
165
|
+
def deconstruct
|
|
166
|
+
@__attr__.values
|
|
167
|
+
end
|
|
168
|
+
|
|
164
169
|
# @!visibility private
|
|
165
170
|
def deconstruct_keys(...)
|
|
166
171
|
@__attr__.deconstruct_keys(...)
|
|
@@ -195,9 +200,7 @@ module Invariable
|
|
|
195
200
|
# @return [Enumerator]
|
|
196
201
|
#
|
|
197
202
|
def each(&block)
|
|
198
|
-
|
|
199
|
-
@__attr__.each_value(&block)
|
|
200
|
-
self
|
|
203
|
+
block ? @__attr__.each_value(&block) : to_enum(__method__)
|
|
201
204
|
end
|
|
202
205
|
|
|
203
206
|
#
|
|
@@ -214,16 +217,14 @@ module Invariable
|
|
|
214
217
|
# @return [Enumerator]
|
|
215
218
|
#
|
|
216
219
|
def each_pair(&block)
|
|
217
|
-
|
|
218
|
-
@__attr__.each_pair(&block)
|
|
219
|
-
self
|
|
220
|
+
block ? @__attr__.each_pair(&block) : to_enum(__method__)
|
|
220
221
|
end
|
|
221
222
|
|
|
222
223
|
#
|
|
223
224
|
# Compares its class and all attributes of itself with the class and
|
|
224
225
|
# attributes of a given other Object.
|
|
225
226
|
#
|
|
226
|
-
# @return [Boolean]
|
|
227
|
+
# @return [Boolean] whether the classes and each attribute value are equal
|
|
227
228
|
#
|
|
228
229
|
# @see ==
|
|
229
230
|
#
|
|
@@ -233,7 +234,7 @@ module Invariable
|
|
|
233
234
|
|
|
234
235
|
# @!visibility private
|
|
235
236
|
def hash
|
|
236
|
-
(
|
|
237
|
+
@__hash__ ||= (@__attr__.values << self.class).hash
|
|
237
238
|
end
|
|
238
239
|
|
|
239
240
|
#
|
|
@@ -246,7 +247,7 @@ module Invariable
|
|
|
246
247
|
alias to_s inspect
|
|
247
248
|
|
|
248
249
|
#
|
|
249
|
-
# @return [Boolean]
|
|
250
|
+
# @return [Boolean] whether the given name is a valid attribute name
|
|
250
251
|
#
|
|
251
252
|
def member?(name)
|
|
252
253
|
@__attr__.key?(name)
|
|
@@ -277,17 +278,12 @@ module Invariable
|
|
|
277
278
|
end
|
|
278
279
|
alias values to_a
|
|
279
280
|
|
|
280
|
-
# @!visibility private
|
|
281
|
-
def deconstruct
|
|
282
|
-
@__attr__.values
|
|
283
|
-
end
|
|
284
|
-
|
|
285
281
|
#
|
|
286
282
|
# @overload to_h
|
|
287
|
-
# @return [
|
|
283
|
+
# @return [{Symbol => Object}] names and values of all attributes
|
|
288
284
|
#
|
|
289
285
|
# @overload to_h(compact: true)
|
|
290
|
-
# @return [
|
|
286
|
+
# @return [{Symbol => Object}] names and values of all attributes which
|
|
291
287
|
# are not `nil` and which are not empty Invariable results
|
|
292
288
|
#
|
|
293
289
|
# @overload to_h(&block)
|
|
@@ -297,18 +293,18 @@ module Invariable
|
|
|
297
293
|
# @yieldparam [Object] value the attribute value
|
|
298
294
|
# @yieldreturn [Array<Symbol,Object>] the pair to be stored in the result
|
|
299
295
|
#
|
|
300
|
-
# @return [
|
|
296
|
+
# @return [{Object => Object}] pairs returned by the `block`
|
|
301
297
|
#
|
|
302
298
|
def to_h(compact: false, &block)
|
|
303
|
-
return to_compact_h if compact
|
|
304
299
|
return Hash[@__attr__.map(&block)] if block
|
|
300
|
+
return __to_compact_h if compact
|
|
305
301
|
@__attr__.transform_values { |v| v.is_a?(Invariable) ? v.to_h : v }
|
|
306
302
|
end
|
|
307
303
|
|
|
308
304
|
#
|
|
309
305
|
# Updates all given attributes.
|
|
310
306
|
#
|
|
311
|
-
# @return [Invariable] a new updated instance
|
|
307
|
+
# @return [Invariable] a new updated instance
|
|
312
308
|
def update(attributes)
|
|
313
309
|
opts = {}
|
|
314
310
|
@__attr__.each_pair do |k, v|
|
|
@@ -318,7 +314,7 @@ module Invariable
|
|
|
318
314
|
end
|
|
319
315
|
|
|
320
316
|
#
|
|
321
|
-
# @return [Array<Object>] Array whose elements are the
|
|
317
|
+
# @return [Array<Object>] Array whose elements are the attributes of self at
|
|
322
318
|
# the given Integer indexes
|
|
323
319
|
def values_at(...)
|
|
324
320
|
@__attr__.values.values_at(...)
|
|
@@ -326,7 +322,7 @@ module Invariable
|
|
|
326
322
|
|
|
327
323
|
private
|
|
328
324
|
|
|
329
|
-
def
|
|
325
|
+
def __to_compact_h
|
|
330
326
|
result = {}
|
|
331
327
|
@__attr__.each_pair do |key, value|
|
|
332
328
|
next if value.nil?
|
|
@@ -367,8 +363,10 @@ module Invariable
|
|
|
367
363
|
if method_defined?(name)
|
|
368
364
|
raise(NameError, "attribute already defined - #{name}", caller(4))
|
|
369
365
|
end
|
|
370
|
-
define_method(name) { @__attr__[name] }
|
|
371
366
|
@__attr__[name] = default.is_a?(Class) ? default : default.dup.freeze
|
|
367
|
+
# accessing "eval-ed" methods is faster than accessing methods defined
|
|
368
|
+
# via #define_method :/
|
|
369
|
+
class_eval("def #{name};@__attr__[:#{name}];end")
|
|
372
370
|
name
|
|
373
371
|
end
|
|
374
372
|
|
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.
|
|
4
|
+
version: 0.1.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mike Blumtritt
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
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-10-15 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/
|
|
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.
|
|
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
data/gems.rb
DELETED
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] }
|
data/samples/http_options.rb
DELETED
|
@@ -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
|
data/spec/invariable_new_spec.rb
DELETED
|
@@ -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
|
data/spec/invariable_spec.rb
DELETED
|
@@ -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
|