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
@@ -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,159 +1,175 @@
|
|
1
1
|
|
2
|
-
|
3
|
-
|
2
|
+
# Attic: A special place to store instance variables.
|
3
|
+
|
4
|
+
require_relative "attic/class_methods"
|
5
|
+
require_relative "attic/errors"
|
6
|
+
require_relative "attic/instance_methods"
|
4
7
|
|
5
|
-
# =
|
8
|
+
# = Attic
|
6
9
|
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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)
|
104
|
+
attr_reader :all_attic_variables
|
105
|
+
|
106
|
+
# A convenince method at the class level for including
|
107
|
+
# ConstructMethods in the given object specifically.
|
26
108
|
#
|
27
|
-
#
|
109
|
+
# e.g.
|
28
110
|
#
|
29
|
-
#
|
30
|
-
#
|
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.
|
111
|
+
# Add Attic support to all objects available now and
|
112
|
+
# in the future:
|
59
113
|
#
|
60
|
-
#
|
114
|
+
# Attic.construct(Object)
|
61
115
|
#
|
62
|
-
|
63
|
-
|
64
|
-
|
116
|
+
# which is equivalent to:
|
117
|
+
#
|
118
|
+
# class Object; include Attic::ClassMethods; end
|
119
|
+
#
|
120
|
+
def self.construct(obj)
|
121
|
+
obj.include Attic::ClassMethods
|
122
|
+
end
|
65
123
|
|
66
|
-
def
|
67
|
-
|
124
|
+
def self.included(obj)
|
125
|
+
raise Runtime, "Did you to `extend Attic`` in #{obj}"
|
68
126
|
end
|
69
|
-
|
70
|
-
end
|
71
127
|
|
128
|
+
def self.extended(obj)
|
129
|
+
# If the class has already been extended, we don't need
|
130
|
+
# to add the class methods again.
|
131
|
+
return if obj.ancestors.member? self
|
72
132
|
|
133
|
+
# Add the instance methods for accessing attic variables
|
134
|
+
obj.send :include, Attic::InstanceMethods
|
73
135
|
|
74
|
-
#
|
75
|
-
#
|
76
|
-
#
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
self.
|
84
|
-
|
85
|
-
alias_method :attic_vars, :attic_variables
|
86
|
-
def attic_variable? n
|
87
|
-
self.class.attic_variable? n
|
88
|
-
end
|
89
|
-
def attic_variable_set(n,v)
|
90
|
-
attic_variables << n unless attic_variable? n
|
91
|
-
if metaclass?
|
92
|
-
metaclass.instance_variable_set("@#{n}", v)
|
93
|
-
else
|
94
|
-
instance_variable_set("@___attic_#{n}", v)
|
95
|
-
end
|
96
|
-
end
|
97
|
-
def attic_variable_get(n)
|
98
|
-
if metaclass?
|
99
|
-
metaclass.instance_variable_get("@#{n}")
|
100
|
-
else
|
101
|
-
instance_variable_get("@___attic_#{n}")
|
102
|
-
end
|
136
|
+
# If the object doesn't have a dedicated singleton class
|
137
|
+
# an exception will be raised so it can be caught and
|
138
|
+
# handled appropriately.
|
139
|
+
obj.attic.instance_variable_defined?("@attic_variables")
|
140
|
+
|
141
|
+
obj.attic.instance_variable_set("@attic_variables", [])
|
142
|
+
|
143
|
+
def obj.inherited(klass)
|
144
|
+
super
|
145
|
+
attic_vars = self.attic_variables.clone
|
146
|
+
klass.attic.instance_variable_set("@attic_variables", attic_vars)
|
103
147
|
end
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
def self.extended(o)
|
111
|
-
# This class has already been extended.
|
112
|
-
return if o.ancestors.member? Attic::InstanceMethods
|
113
|
-
|
114
|
-
|
115
|
-
## NOTE: This is just a reminder for a more descerning way to
|
116
|
-
## include the meta methods, instead of using a global mixin.
|
117
|
-
##o.class_eval do
|
118
|
-
## include ObjectHelpers
|
119
|
-
##end
|
120
|
-
# Create an instance method that returns the attic variables.
|
121
|
-
o.send :include, Attic::InstanceMethods
|
122
|
-
#p [:extend, self, o]
|
123
|
-
|
124
|
-
o.metaclass.instance_variable_set("@attic_variables", [])
|
125
|
-
o.class_eval do
|
126
|
-
def self.inherited(o2)
|
127
|
-
#p [:inherit, self, o2]
|
128
|
-
attic_vars = self.attic_variables.clone
|
129
|
-
o2.metaclass.instance_variable_set("@attic_variables", attic_vars)
|
148
|
+
if method_defined? :instance_variables
|
149
|
+
instance_variables_orig = instance_method(:instance_variables)
|
150
|
+
define_method :instance_variables do
|
151
|
+
ret = _instance_variables_orig.bind(self).call.clone
|
152
|
+
ret.reject! { |v| v.to_s =~ /^@___?attic/ } # match 2 or 3 underscores
|
153
|
+
ret
|
130
154
|
end
|
131
|
-
|
132
|
-
|
133
|
-
define_method :instance_variables do
|
134
|
-
ret = old_instance_variables.bind(self).call.clone
|
135
|
-
ret.reject! { |v| v.to_s =~ /^@___?attic/ } # match 2 or 3 underscores
|
136
|
-
ret
|
137
|
-
end
|
138
|
-
define_method :all_instance_variables do
|
139
|
-
old_instance_variables.bind(self).call
|
140
|
-
end
|
155
|
+
define_method :all_instance_variables do
|
156
|
+
_instance_variables_orig.bind(self).call
|
141
157
|
end
|
142
158
|
end
|
143
|
-
|
144
|
-
|
159
|
+
|
160
|
+
rescue TypeError => e
|
161
|
+
raise NoSingleton, obj, caller
|
145
162
|
end
|
146
|
-
|
147
|
-
|
148
|
-
#
|
149
|
-
#
|
150
|
-
#
|
151
|
-
#
|
152
|
-
#
|
153
|
-
# given, returns the metaclass.
|
163
|
+
|
164
|
+
# A class method for defining variables to store in the attic.
|
165
|
+
# * +names+ is a list of variables names. Accessor methods are
|
166
|
+
# created for each variable name in the list.
|
167
|
+
#
|
168
|
+
# Returns the list of attic variable names or if no names were
|
169
|
+
# given, returns the attic.
|
154
170
|
#
|
155
171
|
# e.g.
|
156
|
-
#
|
172
|
+
#
|
157
173
|
# String.extend Attic
|
158
174
|
# String.attic :timestamp
|
159
175
|
#
|
@@ -161,45 +177,60 @@ module Attic
|
|
161
177
|
# * <tt>String#timestamp</tt> for getting the value
|
162
178
|
# * <tt>String#timestamp</tt> for setting the value
|
163
179
|
#
|
164
|
-
def attic
|
165
|
-
return
|
166
|
-
|
180
|
+
def attic(*names)
|
181
|
+
return all_attic_variables if names.empty?
|
182
|
+
|
183
|
+
names.each do |name|
|
167
184
|
next if attic_variable? name
|
168
|
-
|
169
|
-
|
170
|
-
|
185
|
+
|
186
|
+
self.all_attic_variables << name
|
187
|
+
|
188
|
+
unless method_defined?(name)
|
171
189
|
define_method(name) do
|
172
190
|
attic_variable_get name
|
173
191
|
end
|
174
192
|
end
|
175
|
-
|
193
|
+
|
194
|
+
unless method_defined?("#{name}=")
|
176
195
|
define_method("#{name}=") do |val|
|
177
196
|
attic_variable_set name, val
|
178
197
|
end
|
179
198
|
end
|
180
199
|
end
|
181
|
-
|
200
|
+
|
201
|
+
all_attic_variables
|
182
202
|
end
|
183
|
-
|
184
|
-
# Returns an Array of attic variables for the current class.
|
203
|
+
|
204
|
+
# Returns an Array of attic variables for the current class.
|
185
205
|
# e.g.
|
186
206
|
#
|
187
207
|
# String.extend Attic
|
188
208
|
# String.attic :timestamp
|
189
209
|
# String.attic_variables # => [:timestamp]
|
190
210
|
#
|
191
|
-
def attic_variables
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
end
|
196
|
-
|
211
|
+
# def attic_variables
|
212
|
+
# a = attic.instance_variable_get('@attic_variables')
|
213
|
+
# a ||= attic.instance_variable_set('@attic_variables', [])
|
214
|
+
# a
|
215
|
+
# end
|
216
|
+
# alias attic_vars attic_variables
|
197
217
|
|
198
|
-
def attic_variable?(
|
199
|
-
attic_variables.member?
|
218
|
+
def attic_variable?(name)
|
219
|
+
attic_variables.member? name
|
200
220
|
end
|
201
|
-
|
202
221
|
end
|
203
222
|
|
204
223
|
# - Module#instance_method returns an UnboundMethod
|
205
224
|
# - http://ruby-doc.org/core/classes/Module.html#M001659
|
225
|
+
|
226
|
+
# Add some candy when we're in irb
|
227
|
+
if defined?(IRB)
|
228
|
+
require 'irb/completion'
|
229
|
+
IRB.conf[:PROMPT][:ATTIC] = {
|
230
|
+
PROMPT_I: "attic> ",
|
231
|
+
PROMPT_S: "attic%l> ",
|
232
|
+
PROMPT_C: "attic* ",
|
233
|
+
RETURN: "=> %s\n\n"
|
234
|
+
}
|
235
|
+
IRB.conf[:PROMPT_MODE] = :ATTIC
|
236
|
+
end
|
@@ -1,17 +1,27 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require_relative "../lib/attic"
|
2
|
+
|
3
|
+
Attic.construct Symbol #, :name
|
4
|
+
|
3
5
|
## has list of no metaclass classes
|
4
|
-
|
5
|
-
#=> [Symbol,
|
6
|
-
|
7
|
-
## Symbol metaclass
|
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
|
8
18
|
begin
|
9
|
-
:any.
|
10
|
-
rescue
|
11
|
-
:
|
19
|
+
:any.attic.class
|
20
|
+
rescue NoSingletonError
|
21
|
+
:failed
|
12
22
|
end
|
13
|
-
#=> :
|
14
|
-
|
23
|
+
#=> :failed
|
24
|
+
|
15
25
|
## Symbol instances don't cross streams
|
16
26
|
Symbol.extend Attic
|
17
27
|
Symbol.attic :name
|
@@ -19,33 +29,40 @@ a, b = :symbol1, :symbol2
|
|
19
29
|
a.name = :roger
|
20
30
|
[a.name, b.name]
|
21
31
|
#=> [:roger, nil]
|
22
|
-
|
23
|
-
##
|
32
|
+
|
33
|
+
## attic? method exists
|
24
34
|
Symbol.extend Attic
|
25
|
-
:any.respond_to? :
|
35
|
+
:any.respond_to? :attic?
|
26
36
|
#=> true
|
27
|
-
|
28
|
-
##
|
29
|
-
:any.
|
37
|
+
|
38
|
+
## attic? method is false for a Symbol", false do
|
39
|
+
:any.attic?
|
30
40
|
#=> false
|
31
41
|
|
32
|
-
## A Symbol's attic vars appear in all_instance_variables
|
42
|
+
## A Symbol's attic vars appear in `all_instance_variables` do
|
33
43
|
Symbol.extend Attic
|
34
|
-
Symbol.attic :
|
44
|
+
Symbol.attic :_name
|
35
45
|
a, b = :symbol1, :symbol2
|
36
|
-
a.
|
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
|
37
55
|
a.all_instance_variables
|
38
56
|
#=> [:@___attic_name]
|
39
|
-
|
40
57
|
|
41
|
-
## A Symbol's attic vars do not appear in instance_variables
|
58
|
+
## A Symbol's attic vars do not appear in `instance_variables` do
|
42
59
|
Symbol.extend Attic
|
43
60
|
Symbol.attic :name
|
44
61
|
a, b = :symbol1, :symbol2
|
45
62
|
a.name = :roger
|
46
63
|
a.instance_variables
|
47
|
-
#=> []
|
48
|
-
|
49
|
-
## knows attic variables
|
64
|
+
#=> []
|
65
|
+
|
66
|
+
## knows attic variables, [:name] do
|
50
67
|
Symbol.attic_variables
|
51
|
-
#=> [:name]
|
68
|
+
#=> [:name]
|
@@ -0,0 +1,44 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
# "".has_singleton_class? # => true
|
4
|
+
# :"".has_singleton_class? # => false
|
5
|
+
# 1.has_singleton_class? # =>
|
6
|
+
# nil.has_singleton_class? # => false
|
7
|
+
# NilClass.has_singleton_class? # => true
|
8
|
+
|
9
|
+
# members = ["", :food, 1, 1.00000001, nil, NilClass, true, TrueClass]
|
10
|
+
# members2 = members.clone
|
11
|
+
|
12
|
+
# members.each_with_index do |member, idx|
|
13
|
+
# puts "member: #{member.inspect}"
|
14
|
+
# member2 = members2[idx]
|
15
|
+
|
16
|
+
# member.has_a_dedicated_singleton_class? # => false
|
17
|
+
# member2.has_a_dedicated_singleton_class? # => false
|
18
|
+
# member.bestow_a_singleton_class!
|
19
|
+
# member.has_a_dedicated_singleton_class? # => true
|
20
|
+
# member2.has_a_dedicated_singleton_class? # => false
|
21
|
+
# member2.bestow_a_singleton_class!
|
22
|
+
# member2.has_a_dedicated_singleton_class? # => true
|
23
|
+
|
24
|
+
# member.singleton_class.object_id # => 600
|
25
|
+
# member2.singleton_class.object_id # => 700
|
26
|
+
|
27
|
+
# member.has_method?(:foo) # => false
|
28
|
+
# member.foo # => NoMethodError
|
29
|
+
# member.add_to_singleton_class(:foo)
|
30
|
+
# member.has_method?(:foo) # => true
|
31
|
+
# member.foo = :bar
|
32
|
+
# member.foo # => :bar
|
33
|
+
# member.foo.object_id # => 601
|
34
|
+
|
35
|
+
# member2.has_method?(:foo) # => false
|
36
|
+
# member2.foo # => NoMethodError
|
37
|
+
# member2.add_to_singleton_class(:foo)
|
38
|
+
# member2.has_method?(:foo) # => true
|
39
|
+
# member2.foo = :bar
|
40
|
+
# member2.foo # => :bar
|
41
|
+
# member2.foo.object_id # => 701
|
42
|
+
|
43
|
+
# member2.foo.object_id == member.foo.object_id # => false
|
44
|
+
# end
|