attic 0.5.3 → 0.9.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 +7 -0
- data/.gitignore +18 -0
- data/.rubocop.yml +28 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +63 -0
- data/LICENSE.txt +7 -5
- data/README.md +149 -0
- data/Rakefile +6 -37
- data/attic.gemspec +22 -51
- 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 +187 -156
- data/try/30_nometaclass_tryouts.rb +43 -26
- data/try/X3_nosingleton.rb +44 -0
- metadata +85 -64
- data/CHANGES.txt +0 -48
- data/README.rdoc +0 -48
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5b8515c2812b3e4523d79d1fae98c348f041b4d6862d632e7a41ff4fb6555d7a
|
4
|
+
data.tar.gz: 29f3b9a07abb04e0d4611724a015ff8a0fcb704d7d8f3038d4ecd99ed6ae6768
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 37911f6e23435f00b28f5d75643d2e250ee760c9ddee7049b6296dc3f5eaf6a00a48a9a6d3c484e9e9fd24e3feb15b57afaf7cc33d1e9456be80a8383840a360
|
7
|
+
data.tar.gz: bf9811b8008f3d292c0be8087202785ada8fda996ba352194d8d77652fc997ef0c2d9fe324f710493459f6aa79939472569d11bd68a25b6e4c7c31021b8c9c05
|
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,63 @@
|
|
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.1)
|
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-23
|
50
|
+
ruby
|
51
|
+
|
52
|
+
DEPENDENCIES
|
53
|
+
byebug
|
54
|
+
pry
|
55
|
+
pry-doc
|
56
|
+
rubocop
|
57
|
+
tryouts (= 2.2.0.pre.RC1)
|
58
|
+
|
59
|
+
RUBY VERSION
|
60
|
+
ruby 3.1.4p223
|
61
|
+
|
62
|
+
BUNDLED WITH
|
63
|
+
2.5.7
|
data/LICENSE.txt
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2009-2024 delano
|
2
4
|
|
3
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
6
|
of this software and associated documentation files (the "Software"), to deal
|
@@ -7,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
9
|
copies of the Software, and to permit persons to whom the Software is
|
8
10
|
furnished to do so, subject to the following conditions:
|
9
11
|
|
10
|
-
The above copyright notice and this permission notice shall be included in
|
11
|
-
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
12
14
|
|
13
15
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
16
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
17
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
18
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
-
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
# Attic - v1.0-RC1 (2023-03-15)
|
2
|
+
|
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
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
require 'attic'
|
16
|
+
|
17
|
+
# Extend a class with Attic
|
18
|
+
class String
|
19
|
+
extend Attic
|
20
|
+
|
21
|
+
# Add an instance variable to the attic. All instances
|
22
|
+
# of this class will have this variable available.
|
23
|
+
attic :timestamp
|
24
|
+
end
|
25
|
+
|
26
|
+
# Instantiate a new String object
|
27
|
+
a = "A lovely example of a string"
|
28
|
+
|
29
|
+
# Set and get the timestamp
|
30
|
+
a.timestamp = "1990-11-18"
|
31
|
+
a.timestamp # => "1990-11-18"
|
32
|
+
|
33
|
+
# The timestamp is not visible in the public interface
|
34
|
+
a.instance_variables # => []
|
35
|
+
|
36
|
+
# But it is available in the attic
|
37
|
+
a.attic_variables # => [:timestamp]
|
38
|
+
|
39
|
+
# If you prefer getting your hands dirty, you can also
|
40
|
+
# interact with the attic at a lower level.
|
41
|
+
a.attic_variable_set :tags, [:a, :b, :c]
|
42
|
+
a.attic_variable_get :tags # => [:a, :b, :c]
|
43
|
+
|
44
|
+
# Looking at the attic again shows that the timestamp
|
45
|
+
# is still there too.
|
46
|
+
a.attic_variables # => [:timestamp, :tags]
|
47
|
+
```
|
48
|
+
|
49
|
+
|
50
|
+
### **Objects without singleton classes
|
51
|
+
|
52
|
+
Symbol, Integer, Float, TrueClass, FalseClass, NilClass, and Fixnum are all objects that do not have singleton classes. TrueClass, FalseClass, and NilClass are all singletons themselves. Fixnum is a singleton of Integer.
|
53
|
+
|
54
|
+
These objects do not have metaclasses so the attic is hidden in the object itself.
|
55
|
+
|
56
|
+
### Explore in irb
|
57
|
+
|
58
|
+
```shell
|
59
|
+
$ irb -r attic
|
60
|
+
```
|
61
|
+
|
62
|
+
---
|
63
|
+
|
64
|
+
## Installation
|
65
|
+
|
66
|
+
```shell
|
67
|
+
$ gem install attic
|
68
|
+
```
|
69
|
+
|
70
|
+
```shell
|
71
|
+
$ bundle install attic
|
72
|
+
```
|
73
|
+
|
74
|
+
or via download:
|
75
|
+
* [attic-latest.tar.gz](https://github.com/delano/attic/tarball/latest)
|
76
|
+
* [attic-latest.zip](https://github.com/delano/attic/zipball/latest)
|
77
|
+
|
78
|
+
|
79
|
+
## Proofs
|
80
|
+
|
81
|
+
Tested the following code in IRB for Ruby 2.6.8 and 3.0.2:
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
rquire 'pp'
|
85
|
+
|
86
|
+
test_values = [:sym, 1, 1.01, Symbol, Integer, Float, String, TrueClass, FalseClass, NilClass, '', true, false, nil]
|
87
|
+
|
88
|
+
results = test_values.map do |value|
|
89
|
+
{ value: value,
|
90
|
+
class: value.class,
|
91
|
+
attic: [value.attic?, value.attic? && value.attic.object_id] }
|
92
|
+
end
|
93
|
+
```
|
94
|
+
|
95
|
+
which produced the same results for both.
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
attic> RUBY_VERSION
|
99
|
+
=> "3.2.0"
|
100
|
+
|
101
|
+
pp results
|
102
|
+
[
|
103
|
+
{:value=>:sym, :class=>Symbol, :attic=>[false, false]},
|
104
|
+
{:value=>1, :class=>Integer, :attic=>[false, false]},
|
105
|
+
{:value=>1.01, :class=>Float, :attic=>[false, false]},
|
106
|
+
{:value=>Symbol, :class=>Class, :attic=>[true, 564680]},
|
107
|
+
{:value=>Integer, :class=>Class, :attic=>[true, 564700]},
|
108
|
+
{:value=>Float, :class=>Class, :attic=>[true, 564720]},
|
109
|
+
{:value=>String, :class=>Class, :attic=>[true, 564740]},
|
110
|
+
{:value=>TrueClass, :class=>Class, :attic=>[true, 564760]},
|
111
|
+
{:value=>FalseClass, :class=>Class, :attic=>[true, 564780]},
|
112
|
+
{:value=>NilClass, :class=>Class, :attic=>[true, 564800]},
|
113
|
+
{:value=>"", :class=>String, :attic=>[true, 602880]}
|
114
|
+
{:value=>true, :class=>TrueClass, :attic=>[true, 13840]},
|
115
|
+
{:value=>false, :class=>FalseClass, :attic=>[true, 13860]},
|
116
|
+
{:value=>nil, :class=>NilClass, :attic=>[true, 40]}
|
117
|
+
]
|
118
|
+
```
|
119
|
+
```ruby
|
120
|
+
attic> RUBY_VERSION
|
121
|
+
=> "2.6.8"
|
122
|
+
|
123
|
+
pp results
|
124
|
+
[
|
125
|
+
{:value=>:sym, :class=>Symbol, :attic=>[false, false]},
|
126
|
+
{:value=>1, :class=>Integer, :attic=>[false, false]},
|
127
|
+
{:value=>1.01, :class=>Float, :attic=>[false, false]},
|
128
|
+
{:value=>Symbol, :class=>Class, :attic=>[true, 2844115920]},
|
129
|
+
{:value=>Integer, :class=>Class, :attic=>[true, 2844089400]},
|
130
|
+
{:value=>Float, :class=>Class, :attic=>[true, 2844087700]},
|
131
|
+
{:value=>String, :class=>Class, :attic=>[true, 2844122580]},
|
132
|
+
{:value=>TrueClass, :class=>Class, :attic=>[true, 2844136260]},
|
133
|
+
{:value=>FalseClass, :class=>Class, :attic=>[true, 2844136000]},
|
134
|
+
{:value=>NilClass, :class=>Class, :attic=>[true, 2844139060]},
|
135
|
+
{:value=>"", :class=>String, :attic=>[true, 2845261220]},
|
136
|
+
{:value=>true, :class=>TrueClass, :attic=>[true, 2844136280]},
|
137
|
+
{:value=>false, :class=>FalseClass, :attic=>[true, 2844136020]},
|
138
|
+
{:value=>nil, :class=>NilClass, :attic=>[true, 2844139080]}
|
139
|
+
]
|
140
|
+
```
|
141
|
+
|
142
|
+
## Credits
|
143
|
+
|
144
|
+
* (@delano) Delano Mandelbaum
|
145
|
+
|
146
|
+
|
147
|
+
## License
|
148
|
+
|
149
|
+
MIT
|
data/Rakefile
CHANGED
@@ -1,25 +1,16 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'rake/clean'
|
3
|
-
require '
|
3
|
+
require 'rubygems/package_task'
|
4
4
|
require 'fileutils'
|
5
|
+
require 'rdoc/task'
|
5
6
|
include FileUtils
|
6
|
-
|
7
|
-
|
8
|
-
begin
|
9
|
-
require 'hanna/rdoctask'
|
10
|
-
rescue LoadError
|
11
|
-
require 'rake/rdoctask'
|
12
|
-
end
|
13
|
-
|
14
7
|
|
15
8
|
task :default => :package
|
16
|
-
|
9
|
+
|
17
10
|
# CONFIG =============================================================
|
18
11
|
|
19
12
|
# Change the following according to your needs
|
20
|
-
README = "README.
|
21
|
-
CHANGES = "CHANGES.txt"
|
22
|
-
LICENSE = "LICENSE.txt"
|
13
|
+
README = "README.md"
|
23
14
|
|
24
15
|
# Files and directories to be deleted when you run "rake clean"
|
25
16
|
CLEAN.include [ 'pkg', '*.gem', '.config']
|
@@ -30,7 +21,7 @@ load "#{name}.gemspec"
|
|
30
21
|
version = @spec.version
|
31
22
|
|
32
23
|
# That's it! The following defaults should allow you to get started
|
33
|
-
# on other things.
|
24
|
+
# on other things.
|
34
25
|
|
35
26
|
|
36
27
|
# TESTS/SPECS =========================================================
|
@@ -39,7 +30,7 @@ version = @spec.version
|
|
39
30
|
|
40
31
|
# INSTALL =============================================================
|
41
32
|
|
42
|
-
|
33
|
+
Gem::PackageTask.new(@spec) do |p|
|
43
34
|
p.need_tar = true if RUBY_PLATFORM !~ /mswin/
|
44
35
|
end
|
45
36
|
|
@@ -52,25 +43,6 @@ task :uninstall => [ :clean ] do
|
|
52
43
|
end
|
53
44
|
|
54
45
|
|
55
|
-
# RUBYFORGE RELEASE / PUBLISH TASKS ==================================
|
56
|
-
|
57
|
-
if @spec.rubyforge_project
|
58
|
-
desc 'Publish website to rubyforge'
|
59
|
-
task 'publish:rdoc' => 'doc/index.html' do
|
60
|
-
sh "scp -rp doc/* rubyforge.org:/var/www/gforge-projects/#{name}/"
|
61
|
-
end
|
62
|
-
|
63
|
-
desc 'Public release to rubyforge'
|
64
|
-
task 'publish:gem' => [:package] do |t|
|
65
|
-
sh <<-end
|
66
|
-
rubyforge add_release -o Any -a #{CHANGES} -f -n #{README} #{name} #{name} #{@spec.version} pkg/#{name}-#{@spec.version}.gem &&
|
67
|
-
rubyforge add_file -o Any -a #{CHANGES} -f -n #{README} #{name} #{name} #{@spec.version} pkg/#{name}-#{@spec.version}.tgz
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
|
73
|
-
|
74
46
|
# RUBY DOCS TASK ==================================
|
75
47
|
|
76
48
|
Rake::RDocTask.new do |t|
|
@@ -78,10 +50,7 @@ Rake::RDocTask.new do |t|
|
|
78
50
|
t.title = @spec.summary
|
79
51
|
t.options << '--line-numbers' << '-A cattr_accessor=object'
|
80
52
|
t.options << '--charset' << 'utf-8'
|
81
|
-
t.rdoc_files.include(LICENSE)
|
82
53
|
t.rdoc_files.include(README)
|
83
|
-
t.rdoc_files.include(CHANGES)
|
84
|
-
#t.rdoc_files.include('bin/*')
|
85
54
|
t.rdoc_files.include('lib/**/*.rb')
|
86
55
|
end
|
87
56
|
|
data/attic.gemspec
CHANGED
@@ -1,52 +1,23 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
s.
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
# = EXECUTABLES =
|
12
|
-
# The list of executables in your project (if any). Don't include the path,
|
13
|
-
# just the base filename.
|
14
|
-
s.executables = %w[]
|
15
|
-
|
16
|
-
# Directories to extract rdocs from
|
17
|
-
s.require_paths = %w[lib]
|
18
|
-
|
19
|
-
# Specific files to include rdocs from
|
20
|
-
s.extra_rdoc_files = %w[README.rdoc LICENSE.txt CHANGES.txt]
|
21
|
-
|
22
|
-
# Update --main to reflect the default page to display
|
23
|
-
s.rdoc_options = ["--line-numbers", "--title", s.summary, "--main", "README.rdoc"]
|
24
|
-
|
25
|
-
# = MANIFEST =
|
26
|
-
# The complete list of files to be included in the release. When GitHub packages your gem,
|
27
|
-
# it doesn't allow you to run any command that accesses the filesystem. You will get an
|
28
|
-
# error. You can ask your VCS for the list of versioned files:
|
29
|
-
# git ls-files
|
30
|
-
# svn list -R
|
31
|
-
s.files = %w(
|
32
|
-
CHANGES.txt
|
33
|
-
LICENSE.txt
|
34
|
-
README.rdoc
|
35
|
-
Rakefile
|
36
|
-
attic.gemspec
|
37
|
-
lib/attic.rb
|
38
|
-
try/01_mixins_tryouts.rb
|
39
|
-
try/10_attic_tryouts.rb
|
40
|
-
try/20_accessing_tryouts.rb
|
41
|
-
try/25_string_tryouts.rb
|
42
|
-
try/30_nometaclass_tryouts.rb
|
43
|
-
try/40_explicit_accessor_tryouts.rb
|
44
|
-
try/X1_metaclasses.rb
|
45
|
-
try/X2_extending.rb
|
46
|
-
)
|
47
|
-
|
48
|
-
s.has_rdoc = true
|
49
|
-
s.rubygems_version = '1.3.0'
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "attic"
|
3
|
+
s.version = "0.9.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"
|
50
10
|
|
51
|
-
|
52
|
-
|
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"
|
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 have to
|
25
|
+
# keep trying to access its singleton class.
|
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, Fixnum].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
|