attic 0.5.3 → 0.9.0.pre.rc2

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,18 @@
1
+ .DS_Store
2
+ .bundle
3
+ .byebug*
4
+ .history
5
+ .devcontainer
6
+ .vscode
7
+ *.env
8
+ *.log
9
+ *.md
10
+ *.txt
11
+ !LICENSE.txt
12
+ .ruby-version
13
+ appendonlydir
14
+ Gemfile.lock
15
+ etc/config
16
+ log
17
+ tmp
18
+ vendor
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
- Copyright (c) 2009 Solutious Inc, Delano Mandelbaum
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
- all copies or substantial portions of the Software.
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
- THE SOFTWARE.
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 'rake/gempackagetask'
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.rdoc"
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
- Rake::GemPackageTask.new(@spec) do |p|
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
- @spec = Gem::Specification.new do |s|
2
- s.name = "attic"
3
- s.rubyforge_project = "attic"
4
- s.version = "0.5.3"
5
- s.summary = "A place to hide private instance variables in your Ruby objects."
6
- s.description = s.summary
7
- s.author = "Delano Mandelbaum"
8
- s.email = "delano@solutious.com"
9
- s.homepage = "http://github.com/delano/attic"
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
- end
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
@@ -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