attic 0.6.pre.RC1 → 1.0.0.pre.RC2
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/.gitignore +18 -0
- data/.rubocop.yml +28 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +62 -0
- data/LICENSE.txt +21 -0
- data/README.md +108 -16
- data/attic.gemspec +22 -19
- data/lib/attic/class_methods.rb +66 -0
- data/lib/attic/core_ext.rb +70 -0
- data/lib/attic/errors.rb +52 -0
- data/lib/attic/instance_methods.rb +36 -0
- data/lib/attic.rb +172 -141
- data/try/01_mixins_tryouts.rb +65 -0
- data/try/10_attic_tryouts.rb +51 -0
- data/try/20_accessing_tryouts.rb +29 -0
- data/try/25_string_tryouts.rb +32 -0
- data/try/30_nometaclass_tryouts.rb +68 -0
- data/try/40_explicit_accessor_tryouts.rb +17 -0
- metadata +69 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 65eaa51d7ee74a1e97bef40a3cdb0c6cb8c78ca29e0cd8f8bb55e0cba641f664
|
4
|
+
data.tar.gz: fbc75d2a1063522b54edfc6d608a2f4599ac2b34c39a92aa400b138138f19cd8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2a54f4d47b2aa70d9a8d6ee30578d5c06360e9e5c2ffd3c5dfa196086b40122dfee3d84b079cbabd9a24076262b8d8b4521d50a649fe111c3f8f3b80d20945da
|
7
|
+
data.tar.gz: b06ab0a4a824c1536046f4d36ee0a42ce0e5315e214ef398955cf45f7046c56937d93333102044a1a4607fbe4780dd176d0377569126320e81f623c220527c16
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
AllCops:
|
2
|
+
NewCops: enable
|
3
|
+
TargetRubyVersion: 3.1, 3.2
|
4
|
+
EnabledByDefault: false
|
5
|
+
|
6
|
+
Style/StringLiterals:
|
7
|
+
Enabled: true
|
8
|
+
EnforcedStyle: double_quotes
|
9
|
+
|
10
|
+
Style/StringLiteralsInInterpolation:
|
11
|
+
Enabled: true
|
12
|
+
EnforcedStyle: double_quotes
|
13
|
+
|
14
|
+
Layout/LineLength:
|
15
|
+
Enabled: true
|
16
|
+
Max: 120
|
17
|
+
|
18
|
+
Metrics/CyclomaticComplexity:
|
19
|
+
Enabled: false
|
20
|
+
|
21
|
+
Layout/HashAlignment:
|
22
|
+
Enabled: false
|
23
|
+
|
24
|
+
Lint/Void:
|
25
|
+
Enabled: false
|
26
|
+
|
27
|
+
Metrics/MethodLength:
|
28
|
+
Enabled: false
|
data/Gemfile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
ruby '>= 3.1'
|
6
|
+
|
7
|
+
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
8
|
+
|
9
|
+
# bundle install --without production
|
10
|
+
# bundle install --with development
|
11
|
+
# bundle install --without development
|
12
|
+
group "development" do
|
13
|
+
gem "byebug"
|
14
|
+
gem "pry"
|
15
|
+
gem "pry-doc"
|
16
|
+
gem "rubocop"
|
17
|
+
gem "tryouts", '2.2.0.pre.RC1'
|
18
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
ast (2.4.2)
|
5
|
+
byebug (11.1.3)
|
6
|
+
coderay (1.1.3)
|
7
|
+
drydock (0.6.9)
|
8
|
+
json (2.7.2)
|
9
|
+
language_server-protocol (3.17.0.3)
|
10
|
+
method_source (1.0.0)
|
11
|
+
parallel (1.24.0)
|
12
|
+
parser (3.3.0.5)
|
13
|
+
ast (~> 2.4.1)
|
14
|
+
racc
|
15
|
+
pry (0.14.2)
|
16
|
+
coderay (~> 1.1)
|
17
|
+
method_source (~> 1.0)
|
18
|
+
pry-doc (1.5.0)
|
19
|
+
pry (~> 0.11)
|
20
|
+
yard (~> 0.9.11)
|
21
|
+
racc (1.7.3)
|
22
|
+
rainbow (3.1.1)
|
23
|
+
regexp_parser (2.9.0)
|
24
|
+
rexml (3.2.6)
|
25
|
+
rubocop (1.62.1)
|
26
|
+
json (~> 2.3)
|
27
|
+
language_server-protocol (>= 3.17.0)
|
28
|
+
parallel (~> 1.10)
|
29
|
+
parser (>= 3.3.0.2)
|
30
|
+
rainbow (>= 2.2.2, < 4.0)
|
31
|
+
regexp_parser (>= 1.8, < 3.0)
|
32
|
+
rexml (>= 3.2.5, < 4.0)
|
33
|
+
rubocop-ast (>= 1.31.1, < 2.0)
|
34
|
+
ruby-progressbar (~> 1.7)
|
35
|
+
unicode-display_width (>= 2.4.0, < 3.0)
|
36
|
+
rubocop-ast (1.31.2)
|
37
|
+
parser (>= 3.3.0.4)
|
38
|
+
ruby-progressbar (1.13.0)
|
39
|
+
storable (0.10.pre.RC1)
|
40
|
+
sysinfo (0.9.0.pre.RC1)
|
41
|
+
drydock (< 1.0)
|
42
|
+
storable (= 0.10.pre.RC1)
|
43
|
+
tryouts (2.2.0.pre.RC1)
|
44
|
+
sysinfo (= 0.9.0.pre.RC1)
|
45
|
+
unicode-display_width (2.5.0)
|
46
|
+
yard (0.9.36)
|
47
|
+
|
48
|
+
PLATFORMS
|
49
|
+
arm64-darwin-22
|
50
|
+
|
51
|
+
DEPENDENCIES
|
52
|
+
byebug
|
53
|
+
pry
|
54
|
+
pry-doc
|
55
|
+
rubocop
|
56
|
+
tryouts (= 2.2.0.pre.RC1)
|
57
|
+
|
58
|
+
RUBY VERSION
|
59
|
+
ruby 3.2.0p0
|
60
|
+
|
61
|
+
BUNDLED WITH
|
62
|
+
2.4.12
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2009-2024 delano
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
CHANGED
@@ -1,54 +1,146 @@
|
|
1
|
-
# Attic -
|
1
|
+
# Attic - v1.0-RC1 (2024-04-01)
|
2
2
|
|
3
|
-
A place to hide private instance variables in your Ruby objects.
|
3
|
+
A place to hide private instance variables in your Ruby objects: in the attic.
|
4
|
+
|
5
|
+
## Description
|
6
|
+
|
7
|
+
Attic is a Ruby library that provides a place to hide private instance variables in your Ruby objects: in the attic.
|
8
|
+
|
9
|
+
Like, _why though_? Well sometimes you want to hide thing from the public interface of your object. You could use the `@@` prefix to place them at the class level but then you can't use them on a per-instance basis without creating a new data structure to store them. Attic does this for you in a transparent way using the Ruby singleton classes that are already there**.
|
10
|
+
|
11
|
+
|
12
|
+
### Example
|
4
13
|
|
5
|
-
## Example
|
6
14
|
|
7
15
|
```ruby
|
8
16
|
require 'attic'
|
9
17
|
|
18
|
+
# Extend a class with Attic
|
10
19
|
class String
|
11
20
|
extend Attic
|
21
|
+
|
22
|
+
# Add an instance variable to the attic. All instances
|
23
|
+
# of this class will have this variable available.
|
12
24
|
attic :timestamp
|
13
25
|
end
|
14
26
|
|
15
|
-
a
|
16
|
-
a
|
27
|
+
# Instantiate a new String object
|
28
|
+
a = "A lovely example of a string"
|
29
|
+
|
30
|
+
# Set and get the timestamp
|
31
|
+
a.timestamp = "1990-11-18"
|
32
|
+
a.timestamp # => "1990-11-18"
|
33
|
+
|
34
|
+
# The timestamp is not visible in the public interface
|
17
35
|
a.instance_variables # => []
|
18
|
-
a.timestamp # 1980-11-18
|
19
36
|
|
37
|
+
# But it is available in the attic
|
20
38
|
a.attic_variables # => [:timestamp]
|
21
39
|
|
40
|
+
# If you prefer getting your hands dirty, you can also
|
41
|
+
# interact with the attic at a lower level.
|
22
42
|
a.attic_variable_set :tags, [:a, :b, :c]
|
23
|
-
a.attic_variable_get :tags # [:a, :b, :c]
|
43
|
+
a.attic_variable_get :tags # => [:a, :b, :c]
|
24
44
|
|
45
|
+
# Looking at the attic again shows that the timestamp
|
46
|
+
# is still there too.
|
25
47
|
a.attic_variables # => [:timestamp, :tags]
|
26
48
|
```
|
27
49
|
|
28
|
-
## Some objects have no metaclasses
|
29
50
|
|
30
|
-
|
51
|
+
### **Objects without singleton classes
|
31
52
|
|
53
|
+
Symbol, Integer, Float, TrueClass, FalseClass, NilClass, and Integer are all objects that do not have singleton classes. TrueClass, FalseClass, and NilClass are all singletons themselves. Integer is a singleton of Integer.
|
32
54
|
|
33
|
-
|
55
|
+
These objects do not have metaclasses so the attic is hidden in the object itself.
|
56
|
+
|
57
|
+
### Explore in irb
|
58
|
+
|
59
|
+
```shell
|
60
|
+
$ irb -r attic
|
61
|
+
```
|
62
|
+
|
63
|
+
---
|
34
64
|
|
35
|
-
|
65
|
+
## Installation
|
36
66
|
|
37
67
|
```shell
|
38
68
|
$ gem install attic
|
39
69
|
```
|
40
70
|
|
71
|
+
```shell
|
72
|
+
$ bundle install attic
|
73
|
+
```
|
74
|
+
|
41
75
|
or via download:
|
42
|
-
* attic-latest.tar.gz
|
43
|
-
* attic-latest.zip
|
76
|
+
* [attic-latest.tar.gz](https://github.com/delano/attic/tarball/latest)
|
77
|
+
* [attic-latest.zip](https://github.com/delano/attic/zipball/latest)
|
78
|
+
|
79
|
+
|
80
|
+
## Proofs
|
44
81
|
|
82
|
+
Tested the following code in IRB for Ruby 2.6.8 and 3.0.2:
|
45
83
|
|
46
|
-
|
84
|
+
```ruby
|
85
|
+
rquire 'pp'
|
86
|
+
|
87
|
+
test_values = [:sym, 1, 1.01, Symbol, Integer, Float, String, TrueClass, FalseClass, NilClass, '', true, false, nil]
|
47
88
|
|
48
|
-
|
89
|
+
results = test_values.map do |value|
|
90
|
+
{ value: value,
|
91
|
+
class: value.class,
|
92
|
+
attic: [value.attic?, value.attic? && value.attic.object_id] }
|
93
|
+
end
|
94
|
+
```
|
95
|
+
|
96
|
+
which produced the same results for both.
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
attic> RUBY_VERSION
|
100
|
+
=> "3.2.0"
|
101
|
+
|
102
|
+
pp results
|
103
|
+
[
|
104
|
+
{:value=>:sym, :class=>Symbol, :attic=>[false, false]},
|
105
|
+
{:value=>1, :class=>Integer, :attic=>[false, false]},
|
106
|
+
{:value=>1.01, :class=>Float, :attic=>[false, false]},
|
107
|
+
{:value=>Symbol, :class=>Class, :attic=>[true, 564680]},
|
108
|
+
{:value=>Integer, :class=>Class, :attic=>[true, 564700]},
|
109
|
+
{:value=>Float, :class=>Class, :attic=>[true, 564720]},
|
110
|
+
{:value=>String, :class=>Class, :attic=>[true, 564740]},
|
111
|
+
{:value=>TrueClass, :class=>Class, :attic=>[true, 564760]},
|
112
|
+
{:value=>FalseClass, :class=>Class, :attic=>[true, 564780]},
|
113
|
+
{:value=>NilClass, :class=>Class, :attic=>[true, 564800]},
|
114
|
+
{:value=>"", :class=>String, :attic=>[true, 602880]}
|
115
|
+
{:value=>true, :class=>TrueClass, :attic=>[true, 13840]},
|
116
|
+
{:value=>false, :class=>FalseClass, :attic=>[true, 13860]},
|
117
|
+
{:value=>nil, :class=>NilClass, :attic=>[true, 40]}
|
118
|
+
]
|
119
|
+
```
|
120
|
+
```ruby
|
121
|
+
attic> RUBY_VERSION
|
122
|
+
=> "2.6.8"
|
123
|
+
|
124
|
+
pp results
|
125
|
+
[
|
126
|
+
{:value=>:sym, :class=>Symbol, :attic=>[false, false]},
|
127
|
+
{:value=>1, :class=>Integer, :attic=>[false, false]},
|
128
|
+
{:value=>1.01, :class=>Float, :attic=>[false, false]},
|
129
|
+
{:value=>Symbol, :class=>Class, :attic=>[true, 2844115920]},
|
130
|
+
{:value=>Integer, :class=>Class, :attic=>[true, 2844089400]},
|
131
|
+
{:value=>Float, :class=>Class, :attic=>[true, 2844087700]},
|
132
|
+
{:value=>String, :class=>Class, :attic=>[true, 2844122580]},
|
133
|
+
{:value=>TrueClass, :class=>Class, :attic=>[true, 2844136260]},
|
134
|
+
{:value=>FalseClass, :class=>Class, :attic=>[true, 2844136000]},
|
135
|
+
{:value=>NilClass, :class=>Class, :attic=>[true, 2844139060]},
|
136
|
+
{:value=>"", :class=>String, :attic=>[true, 2845261220]},
|
137
|
+
{:value=>true, :class=>TrueClass, :attic=>[true, 2844136280]},
|
138
|
+
{:value=>false, :class=>FalseClass, :attic=>[true, 2844136020]},
|
139
|
+
{:value=>nil, :class=>NilClass, :attic=>[true, 2844139080]}
|
140
|
+
]
|
141
|
+
```
|
49
142
|
|
50
143
|
|
51
144
|
## License
|
52
145
|
|
53
146
|
MIT
|
54
|
-
|
data/attic.gemspec
CHANGED
@@ -1,20 +1,23 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
s.
|
10
|
-
|
11
|
-
s.
|
12
|
-
s.
|
13
|
-
s.
|
14
|
-
s.
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "attic"
|
3
|
+
s.version = "1.0.0-RC2"
|
4
|
+
s.summary = "When in doubt, store it in the attic"
|
5
|
+
s.description = "Attic: a place to hide metadata about the class or variable itself (e.g. SHA hash summaries)."
|
6
|
+
s.authors = ["Delano Mandelbaum"]
|
7
|
+
s.email = "gems@solutious.com"
|
8
|
+
s.homepage = "https://github.com/delano/attic"
|
9
|
+
s.license = "MIT"
|
10
|
+
|
11
|
+
s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
12
|
+
s.bindir = "exe"
|
13
|
+
s.executables = s.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
14
|
+
s.require_paths = ["lib"]
|
15
|
+
|
16
|
+
s.required_ruby_version = Gem::Requirement.new(">= 2.6.0")
|
17
|
+
|
18
|
+
s.add_dependency "rake", ">= 13.0.6"
|
19
|
+
|
20
|
+
# Add development dependencies
|
21
|
+
s.add_development_dependency "rspec", "~> 3.0"
|
22
|
+
s.add_development_dependency "rubocop", "~> 1.0"
|
20
23
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
#
|
2
|
+
# = Attic::ClassMethods
|
3
|
+
#
|
4
|
+
module Attic
|
5
|
+
# Adds a few methods for accessing the metaclass of an
|
6
|
+
# object. We do this with great caution since the Object
|
7
|
+
# class is as global as it gets in Ruby.
|
8
|
+
module ClassMethods
|
9
|
+
# A list of all the attic variables defined for this class.
|
10
|
+
attr_reader :attic_variables
|
11
|
+
|
12
|
+
# A quick way to check if the current object already has a
|
13
|
+
# dedicated singleton class. We want to know this because
|
14
|
+
# this is where our attic variables will be stored.
|
15
|
+
def attic?
|
16
|
+
return false if NoSingletonError.member? self
|
17
|
+
|
18
|
+
# NOTE: Calling this on an object for the first time lazily
|
19
|
+
# creates a singleton class for itself. Another way of doing
|
20
|
+
# the same thing is to attempt defining a singleton method
|
21
|
+
# for the object. In either case, objects that cannot have
|
22
|
+
# cannot have a dedicated singleton class (e.g. nil, true,
|
23
|
+
# false) will raise a TypeError. We rescue this and add the
|
24
|
+
# object to the NoSingletonError list so we don't keep
|
25
|
+
# trying to access its singleton class over and over.
|
26
|
+
!singleton_class.nil?
|
27
|
+
|
28
|
+
rescue TypeError
|
29
|
+
# Remember for next time.
|
30
|
+
NoSingletonError.add_member self
|
31
|
+
false
|
32
|
+
end
|
33
|
+
|
34
|
+
def attic(name=nil)
|
35
|
+
return singleton_class if name.nil?
|
36
|
+
|
37
|
+
name = name.normalize
|
38
|
+
|
39
|
+
self.attic_variables << name unless attic_variable? name
|
40
|
+
|
41
|
+
safe_name = "@_attic_#{name}"
|
42
|
+
instance_variable_set(safe_name, name)
|
43
|
+
end
|
44
|
+
|
45
|
+
def attic_variable?(name)
|
46
|
+
attic_variables.include? name.normalize
|
47
|
+
end
|
48
|
+
|
49
|
+
def attic_variable_set(name, val)
|
50
|
+
unless attic_variable? name
|
51
|
+
attic_variables << name.normalize
|
52
|
+
end
|
53
|
+
attic.instance_variable_set("@_attic_#{name}", val)
|
54
|
+
end
|
55
|
+
|
56
|
+
def attic_variable_get(name)
|
57
|
+
instance_variable_get("@_attic_#{name}")
|
58
|
+
end
|
59
|
+
|
60
|
+
protected
|
61
|
+
|
62
|
+
def normalize(name)
|
63
|
+
name.to_s.gsub(/\@[\?\!\=]$/, '_').to_sym
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
|
2
|
+
class NoMetaClass < RuntimeError
|
3
|
+
end
|
4
|
+
|
5
|
+
# = Object
|
6
|
+
#
|
7
|
+
# These methods are copied directly from _why's metaid.rb.
|
8
|
+
# See: http://whytheluckystiff.net/articles/seeingMetaclassesClearly.html
|
9
|
+
class Object
|
10
|
+
|
11
|
+
unless defined?(::Object::NOMETACLASS)
|
12
|
+
# An Array of classes which do not have metaclasses.
|
13
|
+
NOMETACLASS = [Symbol, Integer].freeze
|
14
|
+
end
|
15
|
+
|
16
|
+
def nometaclass?
|
17
|
+
NOMETACLASS.member?(self)
|
18
|
+
end
|
19
|
+
|
20
|
+
def metaclass?
|
21
|
+
!NOMETACLASS.member?(self.class)
|
22
|
+
end
|
23
|
+
|
24
|
+
# A convenient method for getting the metaclass of the current object.
|
25
|
+
# i.e.
|
26
|
+
#
|
27
|
+
# class << self; self; end;
|
28
|
+
#
|
29
|
+
# NOTE: Some Ruby class do not have meta classes (see: NOMETACLASS).
|
30
|
+
# For these classes, this method returns the class itself. That means
|
31
|
+
# the instance variables will stored in the class itself.
|
32
|
+
def metaclass
|
33
|
+
if !self.metaclass?
|
34
|
+
raise NoMetaClass, self
|
35
|
+
else
|
36
|
+
class << self; self; end;
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Execute a block +&blk+ within the metaclass of the current object.
|
41
|
+
def meta_eval &blk; metaclass.instance_eval &blk; end
|
42
|
+
|
43
|
+
# Add an instance method called +name+ to metaclass for the current object.
|
44
|
+
# This is useful because it will be available as a singleton method
|
45
|
+
# to all subclasses too.
|
46
|
+
def meta_def name, &blk
|
47
|
+
meta_eval { define_method name, &blk }
|
48
|
+
end
|
49
|
+
|
50
|
+
# Add a class method called +name+ for the current object's class. This
|
51
|
+
# isn't so special but it maintains consistency with meta_def.
|
52
|
+
def class_def name, &blk
|
53
|
+
class_eval { define_method name, &blk }
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
# A convenient method for getting the metaclass of the metaclass
|
58
|
+
# i.e.
|
59
|
+
#
|
60
|
+
# self.metaclass.metaclass
|
61
|
+
#
|
62
|
+
def metametaclass; self.metaclass.metaclass; end
|
63
|
+
|
64
|
+
def metameta_eval &blk; metametaclass.instance_eval &blk; end
|
65
|
+
|
66
|
+
def metameta_def name, &blk
|
67
|
+
metameta_eval { define_method name, &blk }
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
data/lib/attic/errors.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
|
2
|
+
# Attic: A special place to store instance variables.
|
3
|
+
|
4
|
+
class NoMetaClass < RuntimeError
|
5
|
+
end
|
6
|
+
|
7
|
+
# = NoSingletonError
|
8
|
+
#
|
9
|
+
# This error is raised when an attempt is made to access the
|
10
|
+
# attic of an object which does not have a singleton class.
|
11
|
+
#
|
12
|
+
# This is a TypeError because it is not an exceptional
|
13
|
+
# condition. It is simply a condition that is not supported
|
14
|
+
# by the Attic module.
|
15
|
+
#
|
16
|
+
# == Usage
|
17
|
+
#
|
18
|
+
# require 'attic'
|
19
|
+
# class MyClass
|
20
|
+
# include Attic
|
21
|
+
# attic :name, :age
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
class NoSingletonError < TypeError
|
25
|
+
unless defined?(MEMBERS)
|
26
|
+
# A Set of classes which do not have singleton classes
|
27
|
+
# (i.e. meta classes). This is used to prevent an exception
|
28
|
+
# the first time an attic is accessed. It's populated
|
29
|
+
# dynamically at start time by simply checking whether
|
30
|
+
# the object has a singleton. This only needs to be
|
31
|
+
# done once per class.
|
32
|
+
#
|
33
|
+
# We use a set here to avoid having to deal with duplicate
|
34
|
+
# values. Realistically there are only a few classes that
|
35
|
+
# do not have singleton classes. We could hard code them
|
36
|
+
# here which is not lost on us.
|
37
|
+
#
|
38
|
+
MEMBERS = Set.new
|
39
|
+
end
|
40
|
+
|
41
|
+
# Check if the given object is a member of the NoSingleton
|
42
|
+
# members list. This checks for the object itself and all
|
43
|
+
# of its ancestors. See the docs for `Enumerable#===` for
|
44
|
+
# more details.
|
45
|
+
def self.member?(obj)
|
46
|
+
MEMBERS === obj
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.add_member(obj)
|
50
|
+
MEMBERS.merge [self]
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
#
|
2
|
+
|
3
|
+
# = Attic::InstanceMethods
|
4
|
+
#
|
5
|
+
module Attic
|
6
|
+
# Adds a few methods for object instances to access the
|
7
|
+
# attic variables of their class.
|
8
|
+
module InstanceMethods
|
9
|
+
|
10
|
+
def attic
|
11
|
+
raise NoSingleton, self, caller unless attic?
|
12
|
+
|
13
|
+
singleton_class
|
14
|
+
|
15
|
+
rescue TypeError
|
16
|
+
NoSingleton.add_member self
|
17
|
+
end
|
18
|
+
|
19
|
+
def attic_variables
|
20
|
+
self.class.attic_variables
|
21
|
+
end
|
22
|
+
|
23
|
+
def attic_variable?(name)
|
24
|
+
self.class.attic_variable? name
|
25
|
+
end
|
26
|
+
|
27
|
+
def attic_variable_set(name, val)
|
28
|
+
attic_variables << name unless attic_variable? name
|
29
|
+
attic.instance_variable_set("@___attic_#{name}", val)
|
30
|
+
end
|
31
|
+
|
32
|
+
def attic_variable_get(name)
|
33
|
+
attic.instance_variable_get("@___attic_#{name}")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/attic.rb
CHANGED
@@ -1,157 +1,174 @@
|
|
1
1
|
|
2
|
-
|
3
|
-
end
|
4
|
-
|
5
|
-
# = Object
|
6
|
-
#
|
7
|
-
# These methods are copied directly from _why's metaid.rb.
|
8
|
-
# See: http://whytheluckystiff.net/articles/seeingMetaclassesClearly.html
|
9
|
-
class Object
|
2
|
+
# Attic: A special place to store instance variables.
|
10
3
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
end
|
15
|
-
|
16
|
-
def nometaclass?
|
17
|
-
NOMETACLASS.member?(self)
|
18
|
-
end
|
4
|
+
require_relative "attic/class_methods"
|
5
|
+
require_relative "attic/errors"
|
6
|
+
require_relative "attic/instance_methods"
|
19
7
|
|
20
|
-
|
21
|
-
|
22
|
-
|
8
|
+
# = Attic
|
9
|
+
#
|
10
|
+
# == Usage:
|
11
|
+
#
|
12
|
+
# require 'attic'
|
13
|
+
# class MyClass
|
14
|
+
# include Attic
|
15
|
+
# attic :name, :age
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# obj = MyClass.new
|
19
|
+
# obj.attic.nickname = 'My Classic Automobile'
|
20
|
+
# obj.attic.secret_age = 27
|
21
|
+
#
|
22
|
+
# obj.nickname #=> 'My Classic Automobile'
|
23
|
+
# obj.secret_age #=> 27
|
24
|
+
# obj.to_h #=> {}
|
25
|
+
#
|
26
|
+
# OR
|
27
|
+
#
|
28
|
+
# require 'attic'
|
29
|
+
# Attic.construct MyClass, :nickname, :secret_age
|
30
|
+
#
|
31
|
+
#
|
32
|
+
# == Description:
|
33
|
+
#
|
34
|
+
# Attic is a module that allows you to store instance variables
|
35
|
+
# in a dedicated singleton class. This is useful for storing
|
36
|
+
# instance variables that you don't want to be available to
|
37
|
+
# the public interface of your class. e.g. you want to store
|
38
|
+
# a value for the running instance but want to prevent it
|
39
|
+
# from being serialized.
|
40
|
+
#
|
41
|
+
# == Why?
|
42
|
+
#
|
43
|
+
# == Important Notes:
|
44
|
+
#
|
45
|
+
# Some objects just straight up are not capable of contructing
|
46
|
+
# an attic. `Symbols`, `Integers`, and `Floats` specifically do not
|
47
|
+
# have a dedicated singleton classes. These are what ruby
|
48
|
+
# internals refer to as "immediate values". They're special
|
49
|
+
# in that they are not objects in the traditional sense.
|
50
|
+
# They're just values (they're not even instances of a
|
51
|
+
# class 😮💨).
|
52
|
+
#
|
53
|
+
# When you call attic on an immediate value you get an error.
|
54
|
+
#
|
55
|
+
# :sym.attic #=> raises NoSingleton error
|
56
|
+
# 1.attic #=> Ditto
|
57
|
+
# 1.0.1.attic #=> Ditto again
|
58
|
+
#
|
59
|
+
#
|
60
|
+
# The other objects that do not have singleton classes are
|
61
|
+
# `true`, `false`, and `nil`. Calling attic on these don't
|
62
|
+
# raise an error but they simply return their class. This
|
63
|
+
# is because they are all instances of their same singleton
|
64
|
+
# class.
|
65
|
+
#
|
66
|
+
# true.attic #=> TrueClass
|
67
|
+
# false.attic #=> FalseClass
|
68
|
+
# nil.attic #=> NilClass
|
69
|
+
#
|
70
|
+
# Note: this means that every instance of nil
|
71
|
+
# returns the exact same singleton class. This is different
|
72
|
+
# from the behaviour of all other objects.
|
73
|
+
#
|
74
|
+
# nil.attic.object_id #=> 800
|
75
|
+
# nil.attic.object_id #=> 800
|
76
|
+
#
|
77
|
+
#
|
78
|
+
# NilClass, TrueClass, and FalseClass on the otherhand each
|
79
|
+
# have their own singleton class. Calling attic on these
|
80
|
+
# returns the singleton class for each of them. But again
|
81
|
+
# a singleton class for each of them but again they all
|
82
|
+
#
|
83
|
+
# TrueClass.attic #=> #<Class:TrueClass>
|
84
|
+
# FalseClass.attic #=> #<Class:FalseClass>
|
85
|
+
# NilClass.attic #=> #<Class:NilClass>
|
86
|
+
#
|
87
|
+
#
|
88
|
+
# For comparison, here's what happens with a String (each
|
89
|
+
# time attic is called on a new string you get a new
|
90
|
+
# singleton)
|
91
|
+
#
|
92
|
+
# "".attic #=> #<Class:#<String:0x0001234>>
|
93
|
+
# "".attic #=> #<Class:#<String:0x0005678>>
|
94
|
+
# "".attic.object_id #=> 1234
|
95
|
+
# "".attic.object_id #=> 5678
|
96
|
+
#
|
97
|
+
# nil.attic #=> NilClass
|
98
|
+
# nil.attic #=> NilClass
|
99
|
+
# nil.attic.object_id #=> 800
|
100
|
+
# nil.attic.object_id #=> 800
|
101
|
+
#
|
102
|
+
module Attic
|
103
|
+
VERSION = '0.9.0-RC1'.freeze unless defined?(VERSION)
|
23
104
|
|
24
|
-
# A
|
25
|
-
#
|
105
|
+
# A convenince method at the class level for including
|
106
|
+
# ConstructMethods in the given object specifically.
|
26
107
|
#
|
27
|
-
#
|
108
|
+
# e.g.
|
28
109
|
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
|
33
|
-
if self.metaclass?
|
34
|
-
class << self
|
35
|
-
self
|
36
|
-
end
|
37
|
-
else
|
38
|
-
self
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
def metaclassfly
|
43
|
-
location = self.class
|
44
|
-
attr_name = "@@_attic_#{self.object_id}"
|
45
|
-
unless location.class_variable_defined? attr_name
|
46
|
-
location.class_variable_set attr_name, Class.new
|
47
|
-
end
|
48
|
-
location.class_variable_get attr_name
|
49
|
-
end
|
50
|
-
|
51
|
-
# Execute a block +&blk+ within the metaclass of the current object.
|
52
|
-
def meta_eval &blk
|
53
|
-
metaclass.instance_eval blk
|
54
|
-
end
|
55
|
-
|
56
|
-
# Add an instance method called +name+ to metaclass for the current object.
|
57
|
-
# This is useful because it will be available as a singleton method
|
58
|
-
# to all subclasses too.
|
59
|
-
def meta_def name, &blk
|
60
|
-
meta_eval { define_method name, &blk }
|
61
|
-
end
|
62
|
-
|
63
|
-
# Add a class method called +name+ for the current object's class. This
|
64
|
-
# isn't so special but it maintains consistency with meta_def.
|
65
|
-
def class_def name, &blk
|
66
|
-
class_eval { define_method name, &blk }
|
67
|
-
end
|
68
|
-
|
69
|
-
|
70
|
-
# A convenient method for getting the metaclass of the metaclass
|
71
|
-
# i.e.
|
110
|
+
# Add Attic support to all objects available now and
|
111
|
+
# in the future:
|
112
|
+
#
|
113
|
+
# Attic.construct(Object)
|
72
114
|
#
|
73
|
-
#
|
115
|
+
# which is equivalent to:
|
74
116
|
#
|
75
|
-
|
76
|
-
|
117
|
+
# class Object; include Attic::ClassMethods; end
|
118
|
+
#
|
119
|
+
def self.construct(obj)
|
120
|
+
obj.include Attic::ClassMethods
|
77
121
|
end
|
78
122
|
|
79
|
-
|
80
|
-
|
123
|
+
# Friendly exception to say we're not to be included
|
124
|
+
#
|
125
|
+
def self.included(obj)
|
126
|
+
raise RuntimeError, "Did you mean to `extend Attic`` in #{obj}"
|
81
127
|
end
|
82
128
|
|
83
|
-
def
|
84
|
-
|
85
|
-
|
86
|
-
|
129
|
+
def self.extended(obj)
|
130
|
+
# If the class has already been extended, we don't need
|
131
|
+
# to add the class methods again.
|
132
|
+
return if obj.ancestors.member? self
|
87
133
|
|
134
|
+
# Add the instance methods for accessing attic variables
|
135
|
+
obj.send :include, Attic::InstanceMethods
|
88
136
|
|
137
|
+
# If the object doesn't have a dedicated singleton class
|
138
|
+
# an exception will be raised so it can be caught and
|
139
|
+
# handled appropriately.
|
140
|
+
obj.attic.instance_variable_defined?("@attic_variables")
|
89
141
|
|
90
|
-
|
91
|
-
#
|
92
|
-
# A place to store instance variables.
|
93
|
-
#
|
94
|
-
module Attic
|
95
|
-
VERSION = '0.6-RC1' unless defined?(VERSION)
|
142
|
+
obj.attic.instance_variable_set("@attic_variables", [])
|
96
143
|
|
97
|
-
|
98
|
-
|
99
|
-
self.
|
100
|
-
|
101
|
-
alias_method :attic_vars, :attic_variables
|
102
|
-
def attic_variable? n
|
103
|
-
self.class.attic_variable? n
|
144
|
+
def obj.inherited(klass)
|
145
|
+
super
|
146
|
+
attic_vars = self.attic_variables.clone
|
147
|
+
klass.attic.instance_variable_set("@attic_variables", attic_vars)
|
104
148
|
end
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
metaclassfly.instance_variable_get("@___attic_#{n}")
|
112
|
-
end
|
113
|
-
def get_binding
|
114
|
-
binding
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
def self.included(o)
|
119
|
-
raise "You probably meant to 'extend Attic' in #{o}"
|
120
|
-
end
|
121
|
-
|
122
|
-
def self.extended(o)
|
123
|
-
# This class has already been extended.
|
124
|
-
return if o.ancestors.member? Attic::InstanceMethods
|
125
|
-
|
126
|
-
# Add the instance methods for accessing attic variables
|
127
|
-
o.send :include, Attic::InstanceMethods
|
128
|
-
|
129
|
-
o.metaclass.instance_variable_set("@attic_variables", [])
|
130
|
-
o.class_eval do
|
131
|
-
def self.inherited(o2)
|
132
|
-
attic_vars = self.attic_variables.clone
|
133
|
-
o2.metaclass.instance_variable_set("@attic_variables", attic_vars)
|
149
|
+
if method_defined? :instance_variables
|
150
|
+
instance_variables_orig = instance_method(:instance_variables)
|
151
|
+
define_method :instance_variables do
|
152
|
+
ret = _instance_variables_orig.bind(self).call.clone
|
153
|
+
ret.reject! { |v| v.to_s =~ /^@___?attic/ } # match 2 or 3 underscores
|
154
|
+
ret
|
134
155
|
end
|
135
|
-
|
136
|
-
|
137
|
-
define_method :instance_variables do
|
138
|
-
ret = old_instance_variables.bind(self).call.clone
|
139
|
-
ret.reject! { |v| v.to_s =~ /^@___?attic/ } # match 2 or 3 underscores
|
140
|
-
ret
|
141
|
-
end
|
142
|
-
define_method :all_instance_variables do
|
143
|
-
old_instance_variables.bind(self).call
|
144
|
-
end
|
156
|
+
define_method :all_instance_variables do
|
157
|
+
_instance_variables_orig.bind(self).call
|
145
158
|
end
|
146
159
|
end
|
160
|
+
|
161
|
+
rescue TypeError => e
|
162
|
+
raise NoSingleton, obj, caller
|
147
163
|
end
|
148
164
|
|
149
165
|
# A class method for defining variables to store in the attic.
|
150
|
-
# * +
|
166
|
+
# * +names+ is a list of variables names. Accessor methods are
|
151
167
|
# created for each variable name in the list.
|
152
168
|
#
|
153
|
-
# Returns
|
154
|
-
# given
|
169
|
+
# Returns an Array of all attic variables for the current
|
170
|
+
# class unless no arguments are given in which case it
|
171
|
+
# returns its singleton.
|
155
172
|
#
|
156
173
|
# e.g.
|
157
174
|
#
|
@@ -162,24 +179,28 @@ module Attic
|
|
162
179
|
# * <tt>String#timestamp</tt> for getting the value
|
163
180
|
# * <tt>String#timestamp</tt> for setting the value
|
164
181
|
#
|
165
|
-
def attic
|
166
|
-
return
|
167
|
-
|
182
|
+
def attic(*names)
|
183
|
+
return singleton_class if names.empty?
|
184
|
+
|
185
|
+
names.each do |name|
|
168
186
|
next if attic_variable? name
|
187
|
+
|
169
188
|
self.attic_variables << name
|
170
189
|
|
171
|
-
unless method_defined?
|
190
|
+
unless method_defined?(name)
|
172
191
|
define_method(name) do
|
173
192
|
attic_variable_get name
|
174
193
|
end
|
175
194
|
end
|
176
|
-
|
195
|
+
|
196
|
+
unless method_defined?("#{name}=")
|
177
197
|
define_method("#{name}=") do |val|
|
178
198
|
attic_variable_set name, val
|
179
199
|
end
|
180
200
|
end
|
181
201
|
end
|
182
|
-
|
202
|
+
|
203
|
+
attic_variables # only after defining new attic vars
|
183
204
|
end
|
184
205
|
|
185
206
|
# Returns an Array of attic variables for the current class.
|
@@ -190,17 +211,27 @@ module Attic
|
|
190
211
|
# String.attic_variables # => [:timestamp]
|
191
212
|
#
|
192
213
|
def attic_variables
|
193
|
-
a =
|
194
|
-
a ||=
|
214
|
+
a = attic.instance_variable_get('@attic_variables')
|
215
|
+
a ||= attic.instance_variable_set('@attic_variables', [])
|
195
216
|
a
|
196
217
|
end
|
197
|
-
alias_method :attic_vars, :attic_variables
|
198
218
|
|
199
|
-
def attic_variable?(
|
200
|
-
attic_variables.member?
|
219
|
+
def attic_variable?(name)
|
220
|
+
attic_variables.member? name
|
201
221
|
end
|
202
|
-
|
203
222
|
end
|
204
223
|
|
205
224
|
# - Module#instance_method returns an UnboundMethod
|
206
225
|
# - http://ruby-doc.org/core/classes/Module.html#M001659
|
226
|
+
|
227
|
+
# Add some candy when we're in irb
|
228
|
+
if defined?(IRB)
|
229
|
+
require 'irb/completion'
|
230
|
+
IRB.conf[:PROMPT][:ATTIC] = {
|
231
|
+
PROMPT_I: "attic> ",
|
232
|
+
PROMPT_S: "attic%l> ",
|
233
|
+
PROMPT_C: "attic* ",
|
234
|
+
RETURN: "=> %s\n\n"
|
235
|
+
}
|
236
|
+
IRB.conf[:PROMPT_MODE] = :ATTIC
|
237
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require_relative '../lib/attic'
|
2
|
+
|
3
|
+
#
|
4
|
+
# Tests for the Object mixins that Attic relies on.
|
5
|
+
#
|
6
|
+
|
7
|
+
## Has a valid NoMetaClass exception class
|
8
|
+
NoMetaClass < RuntimeError
|
9
|
+
#=> true
|
10
|
+
|
11
|
+
## Has a pre-populated array of built-ins without a metaclass
|
12
|
+
begin
|
13
|
+
Object::NOMETACLASS
|
14
|
+
rescue NameError => e
|
15
|
+
e.class
|
16
|
+
end
|
17
|
+
#=> NameError
|
18
|
+
|
19
|
+
## Has Object#metaclass method
|
20
|
+
Object.new.respond_to? :metaclass
|
21
|
+
#=> false
|
22
|
+
|
23
|
+
## Has Object#singleton_class method
|
24
|
+
Object.new.respond_to? :singleton_class
|
25
|
+
#=> true
|
26
|
+
|
27
|
+
## Object#singleton_class is a class
|
28
|
+
Object.new.singleton_class.class
|
29
|
+
#=> Class
|
30
|
+
|
31
|
+
## Object#singleton_class is a class
|
32
|
+
Object.new.singleton_class.object_id.class
|
33
|
+
#=> Integer
|
34
|
+
|
35
|
+
## Object#singleton_class is a class
|
36
|
+
a = Object.new
|
37
|
+
b = Object.new
|
38
|
+
a.singleton_class.object_id == b.singleton_class.object_id
|
39
|
+
#=> false
|
40
|
+
|
41
|
+
## Object#singleton_class is an Object class
|
42
|
+
Object.new.singleton_class.superclass
|
43
|
+
#=> Object
|
44
|
+
|
45
|
+
## Object#singleton_class is equivalent to `class << self; self; end;`
|
46
|
+
a = Object.new
|
47
|
+
a.singleton_class == (class << a; self; end)
|
48
|
+
#=> true
|
49
|
+
|
50
|
+
## Integer doesn't have a singleton_class
|
51
|
+
Integer.singleton_class?
|
52
|
+
#=> false
|
53
|
+
|
54
|
+
## Symbol doesn't have a singleton_class
|
55
|
+
Symbol.singleton_class?
|
56
|
+
#=> false
|
57
|
+
|
58
|
+
## Object has a singleton_class
|
59
|
+
Object.singleton_class?
|
60
|
+
#=> false
|
61
|
+
|
62
|
+
## Object#singleton_class is equivalent to Object#singleton_class
|
63
|
+
a = Object.new
|
64
|
+
a.singleton_class == a.singleton_class
|
65
|
+
#=> true
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require_relative '../lib/attic'
|
2
|
+
|
3
|
+
#
|
4
|
+
# Tests for the Attic module
|
5
|
+
#
|
6
|
+
|
7
|
+
## A class we define can extend Attic
|
8
|
+
class ::ExampleClass
|
9
|
+
extend Attic
|
10
|
+
def kind() :unlikely_value end
|
11
|
+
end
|
12
|
+
ExampleClass.methods.member?(:attic)
|
13
|
+
#=> true
|
14
|
+
|
15
|
+
## Trying to include Attic raises an exception
|
16
|
+
begin
|
17
|
+
class ::ExampleClass
|
18
|
+
include Attic
|
19
|
+
end
|
20
|
+
rescue => e
|
21
|
+
e.class
|
22
|
+
end
|
23
|
+
#=> RuntimeError
|
24
|
+
|
25
|
+
## Can define attic variables at class level
|
26
|
+
ExampleClass.attic :size
|
27
|
+
w = ExampleClass.new
|
28
|
+
w.respond_to? :size
|
29
|
+
#=> true
|
30
|
+
|
31
|
+
## Accessing attic vars at the instance level fails
|
32
|
+
begin
|
33
|
+
w = ExampleClass.new
|
34
|
+
w.attic :size, 2
|
35
|
+
rescue => e
|
36
|
+
e.class
|
37
|
+
end
|
38
|
+
#=> NoMethodError
|
39
|
+
|
40
|
+
## Can access attic vars the long way though
|
41
|
+
w = ExampleClass.new
|
42
|
+
w.attic_variable_set :size, 2
|
43
|
+
w.attic_variable_get :size
|
44
|
+
#=> 2
|
45
|
+
|
46
|
+
## Won't clobber an existing method with the same name
|
47
|
+
## NOTE: But also won't tell you it didn't define the method
|
48
|
+
ExampleClass.attic :kind
|
49
|
+
a = ExampleClass.new
|
50
|
+
a.kind
|
51
|
+
#=> :unlikely_value
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'attic'
|
2
|
+
class ::Worker
|
3
|
+
extend Attic
|
4
|
+
attic :size
|
5
|
+
end
|
6
|
+
|
7
|
+
|
8
|
+
## save an instance variable the long way
|
9
|
+
w = Worker.new
|
10
|
+
w.metametaclass.instance_variable_set '@mattress', 'S&F'
|
11
|
+
w.metametaclass.instance_variable_get '@mattress'
|
12
|
+
#=> 'S&F'
|
13
|
+
|
14
|
+
## save an instance variable the short way
|
15
|
+
w = Worker.new
|
16
|
+
w.size = :california_king
|
17
|
+
w.size
|
18
|
+
#=> :california_king
|
19
|
+
|
20
|
+
## new instances don't cross streams
|
21
|
+
w = Worker.new
|
22
|
+
w.size
|
23
|
+
#=> nil
|
24
|
+
|
25
|
+
## instance variables are hidden
|
26
|
+
w = Worker.new
|
27
|
+
w.metametaclass.instance_variable_set '@mattress', 'S&F'
|
28
|
+
w.instance_variables
|
29
|
+
## []
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'attic'
|
2
|
+
|
3
|
+
## String can extend Attic
|
4
|
+
String.extend Attic
|
5
|
+
String.respond_to? :attic
|
6
|
+
#=> true
|
7
|
+
|
8
|
+
## save an instance variable the long way
|
9
|
+
s = ""
|
10
|
+
s.metametaclass.instance_variable_set '@mattress', 'S&F'
|
11
|
+
s.metametaclass.instance_variable_get '@mattress'
|
12
|
+
#=> 'S&F'
|
13
|
+
|
14
|
+
## can create attributes
|
15
|
+
String.attic :goodies
|
16
|
+
#=> [:goodies]
|
17
|
+
|
18
|
+
## save an instance variable the short way
|
19
|
+
s = ""
|
20
|
+
s.goodies = :california_king
|
21
|
+
p s.instance_variables
|
22
|
+
p s.attic_vars
|
23
|
+
s.goodies
|
24
|
+
#=> :california_king
|
25
|
+
|
26
|
+
## String instances don't cross streams
|
27
|
+
String.extend Attic
|
28
|
+
String.attic :name
|
29
|
+
a = "any"
|
30
|
+
a.name = :roger
|
31
|
+
a.name == "".name
|
32
|
+
#=> false
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require_relative "../lib/attic"
|
2
|
+
|
3
|
+
Attic.construct Symbol #, :name
|
4
|
+
|
5
|
+
## has list of no metaclass classes
|
6
|
+
NoSingletonError::MEMBERS
|
7
|
+
#=> [Symbol, Integer]
|
8
|
+
|
9
|
+
# ## Symbol metaclass does not raise an exception
|
10
|
+
# begin
|
11
|
+
# :any.attic.class
|
12
|
+
# rescue NoSingletonError
|
13
|
+
# :failed
|
14
|
+
# end
|
15
|
+
# #=> :failed
|
16
|
+
|
17
|
+
## Accessing Symbol metaclass raises an exception
|
18
|
+
begin
|
19
|
+
:any.attic.class
|
20
|
+
rescue NoSingletonError
|
21
|
+
:failed
|
22
|
+
end
|
23
|
+
#=> :failed
|
24
|
+
|
25
|
+
## Symbol instances don't cross streams
|
26
|
+
Symbol.extend Attic
|
27
|
+
Symbol.attic :name
|
28
|
+
a, b = :symbol1, :symbol2
|
29
|
+
a.name = :roger
|
30
|
+
[a.name, b.name]
|
31
|
+
#=> [:roger, nil]
|
32
|
+
|
33
|
+
## attic? method exists
|
34
|
+
Symbol.extend Attic
|
35
|
+
:any.respond_to? :attic?
|
36
|
+
#=> true
|
37
|
+
|
38
|
+
## attic? method is false for a Symbol", false do
|
39
|
+
:any.attic?
|
40
|
+
#=> false
|
41
|
+
|
42
|
+
## A Symbol's attic vars appear in `all_instance_variables` do
|
43
|
+
Symbol.extend Attic
|
44
|
+
Symbol.attic :_name
|
45
|
+
a, b = :symbol1, :symbol2
|
46
|
+
a._name = :roger
|
47
|
+
a.all_instance_variables
|
48
|
+
#=> [:@___attic_name]
|
49
|
+
|
50
|
+
## An Integer's attic vars appear in `all_instance_variables` do
|
51
|
+
Integer.extend Attic
|
52
|
+
Integer.attic :_name
|
53
|
+
a, b = 1, 2
|
54
|
+
a._name = :roger
|
55
|
+
a.all_instance_variables
|
56
|
+
#=> [:@___attic_name]
|
57
|
+
|
58
|
+
## A Symbol's attic vars do not appear in `instance_variables` do
|
59
|
+
Symbol.extend Attic
|
60
|
+
Symbol.attic :name
|
61
|
+
a, b = :symbol1, :symbol2
|
62
|
+
a.name = :roger
|
63
|
+
a.instance_variables
|
64
|
+
#=> []
|
65
|
+
|
66
|
+
## knows attic variables, [:name] do
|
67
|
+
Symbol.attic_variables
|
68
|
+
#=> [:name]
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'attic'
|
2
|
+
|
3
|
+
class ::Worker
|
4
|
+
extend Attic
|
5
|
+
end
|
6
|
+
|
7
|
+
## can set value", 100 do
|
8
|
+
a = Worker.new
|
9
|
+
a.attic_variable_set :space, 100
|
10
|
+
a.attic_variable_get :space
|
11
|
+
#=> 100
|
12
|
+
|
13
|
+
## doesn't create accessor methods", false do
|
14
|
+
a = Worker.new
|
15
|
+
a.attic_variable_set :space, 100
|
16
|
+
a.respond_to? :space
|
17
|
+
#=> false
|
metadata
CHANGED
@@ -1,53 +1,104 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: attic
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0.pre.RC2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Delano Mandelbaum
|
8
|
-
autorequire:
|
9
|
-
bindir:
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
12
|
-
dependencies:
|
11
|
+
date: 2024-04-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 13.0.6
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 13.0.6
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rubocop
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.0'
|
13
55
|
description: 'Attic: a place to hide metadata about the class or variable itself (e.g.
|
14
56
|
SHA hash summaries).'
|
15
57
|
email: gems@solutious.com
|
16
58
|
executables: []
|
17
59
|
extensions: []
|
18
|
-
extra_rdoc_files:
|
19
|
-
- README.md
|
60
|
+
extra_rdoc_files: []
|
20
61
|
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- ".rubocop.yml"
|
64
|
+
- Gemfile
|
65
|
+
- Gemfile.lock
|
66
|
+
- LICENSE.txt
|
21
67
|
- README.md
|
22
68
|
- Rakefile
|
23
69
|
- attic.gemspec
|
24
70
|
- lib/attic.rb
|
25
|
-
|
71
|
+
- lib/attic/class_methods.rb
|
72
|
+
- lib/attic/core_ext.rb
|
73
|
+
- lib/attic/errors.rb
|
74
|
+
- lib/attic/instance_methods.rb
|
75
|
+
- try/01_mixins_tryouts.rb
|
76
|
+
- try/10_attic_tryouts.rb
|
77
|
+
- try/20_accessing_tryouts.rb
|
78
|
+
- try/25_string_tryouts.rb
|
79
|
+
- try/30_nometaclass_tryouts.rb
|
80
|
+
- try/40_explicit_accessor_tryouts.rb
|
81
|
+
homepage: https://github.com/delano/attic
|
26
82
|
licenses:
|
27
83
|
- MIT
|
28
84
|
metadata: {}
|
29
|
-
post_install_message:
|
30
|
-
rdoc_options:
|
31
|
-
- "--line-numbers"
|
32
|
-
- "--title"
|
33
|
-
- When in doubt, store it in the attic
|
34
|
-
- "--main"
|
35
|
-
- README.md
|
85
|
+
post_install_message:
|
86
|
+
rdoc_options: []
|
36
87
|
require_paths:
|
37
88
|
- lib
|
38
89
|
required_ruby_version: !ruby/object:Gem::Requirement
|
39
90
|
requirements:
|
40
91
|
- - ">="
|
41
92
|
- !ruby/object:Gem::Version
|
42
|
-
version:
|
93
|
+
version: 2.6.0
|
43
94
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
44
95
|
requirements:
|
45
96
|
- - ">"
|
46
97
|
- !ruby/object:Gem::Version
|
47
98
|
version: 1.3.1
|
48
99
|
requirements: []
|
49
|
-
rubygems_version: 3.
|
50
|
-
signing_key:
|
100
|
+
rubygems_version: 3.4.12
|
101
|
+
signing_key:
|
51
102
|
specification_version: 4
|
52
103
|
summary: When in doubt, store it in the attic
|
53
104
|
test_files: []
|