attic 0.6.pre.RC1 → 0.9.0.pre.rc2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/attic.rb CHANGED
@@ -1,157 +1,172 @@
1
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
2
+ # Attic: A special place to store instance variables.
10
3
 
11
- unless defined?(::Object::NOMETACLASS)
12
- # An Array of classes which do not have metaclasses.
13
- NOMETACLASS = [Symbol, Integer]
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
- def metaclass?
21
- !NOMETACLASS.member?(self.class)
22
- end
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)
104
+ attr_reader :all_attic_variables
23
105
 
24
- # A convenient method for getting the metaclass of the current object.
25
- # i.e.
106
+ # A convenince method at the class level for including
107
+ # ConstructMethods in the given object specifically.
26
108
  #
27
- # class << self; self; end;
109
+ # e.g.
28
110
  #
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 be stored in the class itself.
32
- def metaclass
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.
111
+ # Add Attic support to all objects available now and
112
+ # in the future:
113
+ #
114
+ # Attic.construct(Object)
72
115
  #
73
- # self.metaclass.metaclass
116
+ # which is equivalent to:
74
117
  #
75
- def metametaclass
76
- self.metaclass.metaclass
118
+ # class Object; include Attic::ClassMethods; end
119
+ #
120
+ def self.construct(obj)
121
+ obj.include Attic::ClassMethods
77
122
  end
78
123
 
79
- def metameta_eval &blk
80
- metametaclass.instance_eval blk
124
+ def self.included(obj)
125
+ raise Runtime, "Did you to `extend Attic`` in #{obj}"
81
126
  end
82
127
 
83
- def metameta_def name, &blk
84
- metameta_eval { define_method name, &blk }
85
- end
86
- end
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
87
132
 
133
+ # Add the instance methods for accessing attic variables
134
+ obj.send :include, Attic::InstanceMethods
88
135
 
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")
89
140
 
90
- # = Attic
91
- #
92
- # A place to store instance variables.
93
- #
94
- module Attic
95
- VERSION = '0.6-RC1' unless defined?(VERSION)
141
+ obj.attic.instance_variable_set("@attic_variables", [])
96
142
 
97
- module InstanceMethods
98
- def attic_variables
99
- self.class.attic_variables
100
- end
101
- alias_method :attic_vars, :attic_variables
102
- def attic_variable? n
103
- self.class.attic_variable? n
104
- end
105
- def attic_variable_set(n,v)
106
- attic_variables << n unless attic_variable? n
107
- # binding.pry
108
- metaclassfly.instance_variable_set("@___attic_#{n}", v)
143
+ def obj.inherited(klass)
144
+ super
145
+ attic_vars = self.attic_variables.clone
146
+ klass.attic.instance_variable_set("@attic_variables", attic_vars)
109
147
  end
110
- def attic_variable_get(n)
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)
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
134
154
  end
135
- if method_defined? :instance_variables
136
- old_instance_variables = instance_method(:instance_variables)
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
155
+ define_method :all_instance_variables do
156
+ _instance_variables_orig.bind(self).call
145
157
  end
146
158
  end
159
+
160
+ rescue TypeError => e
161
+ raise NoSingleton, obj, caller
147
162
  end
148
163
 
149
164
  # A class method for defining variables to store in the attic.
150
- # * +junk+ is a list of variables names. Accessor methods are
165
+ # * +names+ is a list of variables names. Accessor methods are
151
166
  # created for each variable name in the list.
152
167
  #
153
- # Returns the list of attic variable names or if not junk was
154
- # given, returns the metaclass.
168
+ # Returns the list of attic variable names or if no names were
169
+ # given, returns the attic.
155
170
  #
156
171
  # e.g.
157
172
  #
@@ -162,24 +177,28 @@ module Attic
162
177
  # * <tt>String#timestamp</tt> for getting the value
163
178
  # * <tt>String#timestamp</tt> for setting the value
164
179
  #
165
- def attic *junk
166
- return metaclass if junk.empty?
167
- junk.each do |name|
180
+ def attic(*names)
181
+ return all_attic_variables if names.empty?
182
+
183
+ names.each do |name|
168
184
  next if attic_variable? name
169
- self.attic_variables << name
170
185
 
171
- unless method_defined? name
186
+ self.all_attic_variables << name
187
+
188
+ unless method_defined?(name)
172
189
  define_method(name) do
173
190
  attic_variable_get name
174
191
  end
175
192
  end
176
- unless method_defined? "#{name}="
193
+
194
+ unless method_defined?("#{name}=")
177
195
  define_method("#{name}=") do |val|
178
196
  attic_variable_set name, val
179
197
  end
180
198
  end
181
199
  end
182
- attic_vars
200
+
201
+ all_attic_variables
183
202
  end
184
203
 
185
204
  # Returns an Array of attic variables for the current class.
@@ -189,18 +208,29 @@ module Attic
189
208
  # String.attic :timestamp
190
209
  # String.attic_variables # => [:timestamp]
191
210
  #
192
- def attic_variables
193
- a = self.metaclass.instance_variable_get("@attic_variables")
194
- a ||= self.metaclass.instance_variable_set("@attic_variables", [])
195
- a
196
- end
197
- alias_method :attic_vars, :attic_variables
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
198
217
 
199
- def attic_variable?(n)
200
- attic_variables.member? n
218
+ def attic_variable?(name)
219
+ attic_variables.member? name
201
220
  end
202
-
203
221
  end
204
222
 
205
223
  # - Module#instance_method returns an UnboundMethod
206
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
@@ -0,0 +1,17 @@
1
+ require 'attic'
2
+
3
+ ## has metaclass", 'Object' do
4
+ if Tryouts.sysinfo.ruby.to_s == "1.9.1"
5
+ Object.new.metaclass.superclass.to_s
6
+ else
7
+ 'Object'
8
+ end
9
+ #=> 'Object'
10
+
11
+ ## has metametaclass", '#<Class:Object>' do
12
+ if Tryouts.sysinfo.ruby.to_s >= "1.9.1"
13
+ Object.new.metaclass.superclass.to_s
14
+ else
15
+ '#<Class:Object>'
16
+ end
17
+ #=> 'Object'
@@ -0,0 +1,41 @@
1
+ require 'attic'
2
+
3
+ ## can extend Attic
4
+ class ::Worker
5
+ extend Attic
6
+ def kind() :true end
7
+ end
8
+ # 1.9 # 1.8
9
+ Worker.methods.member?(:attic) || Worker.methods.member?('attic')
10
+ #=> true
11
+
12
+ ## can't include Attic raises exception
13
+ begin
14
+ class ::Worker
15
+ include Attic
16
+ end
17
+ rescue => RuntimeError
18
+ :success
19
+ end
20
+ #=> :success
21
+
22
+ ## can define attic attribute
23
+ Worker.attic :size
24
+ w = Worker.new
25
+ #w.attic :size
26
+ p Worker.instance_methods(false)
27
+ p Worker.methods.sort
28
+ w.respond_to? :size
29
+ #=> true
30
+
31
+ ## can access attic attributes explicitly"
32
+ w = Worker.new
33
+ w.attic_variable_set :size, 2
34
+ w.attic_variable_get :size
35
+ #=> 2
36
+
37
+ ## won't define a method if on already exists
38
+ Worker.attic :kind
39
+ a = Worker.new
40
+ a.kind
41
+ #=> :true
@@ -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
@@ -0,0 +1,112 @@
1
+ # $ ruby tryouts/metaclasses.rb
2
+
3
+ class Object
4
+
5
+ # A convenient method for getting the metaclass of the current object.
6
+ # i.e.
7
+ #
8
+ # class << self; self; end;
9
+ #
10
+ def metaclass; class << self; self; end; end
11
+
12
+ # Execute a block +&blk+ within the metaclass of the current object.
13
+ def meta_eval &blk; metaclass.instance_eval &blk; end
14
+
15
+ # Add an instance method called +name+ to metaclass for the current object.
16
+ # This is useful because it will be available as a singleton method
17
+ # to all subclasses too.
18
+ def meta_def name, &blk
19
+ meta_eval { define_method name, &blk }
20
+ end
21
+
22
+ # Add a class method called +name+ for the current object's class. This
23
+ # isn't so special but it maintains consistency with meta_def.
24
+ def class_def name, &blk
25
+ class_eval { define_method name, &blk }
26
+ end
27
+
28
+
29
+ # A convenient method for getting the metaclass of the metaclass
30
+ # i.e.
31
+ #
32
+ # self.metaclass.metaclass
33
+ #
34
+ def metametaclass; self.metaclass.metaclass; end
35
+
36
+ def metameta_eval &blk; metametaclass.instance_eval &blk; end
37
+
38
+ def metameta_def name, &blk
39
+ metameta_eval { define_method name, &blk }
40
+ end
41
+
42
+ end
43
+
44
+ # Create an instance method
45
+ class NamedArray1
46
+ class_eval do
47
+ define_method(:name) do
48
+ :roger
49
+ end
50
+ end
51
+ end
52
+ p [1, NamedArray1.new.name]
53
+
54
+ # Create getter and setter instance methods
55
+ class NamedArray2
56
+ class_eval do
57
+ define_method(:name) do
58
+ instance_variable_get("@name")
59
+ end
60
+ define_method(:name=) do |val|
61
+ instance_variable_set("@name", val)
62
+ end
63
+ end
64
+ end
65
+ a = NamedArray2.new
66
+ a.name = :roger
67
+ p [2, a.name, a.instance_variables]
68
+
69
+ # Create getter and setter instance methods,
70
+ # store instance variable in metaclass
71
+ class NamedArray3
72
+ class_eval do
73
+ define_method(:name) do
74
+ metaclass.instance_variable_get("@name")
75
+ end
76
+ define_method(:name=) do |val|
77
+ metaclass.instance_variable_set("@name", val)
78
+ end
79
+ end
80
+ end
81
+ a = NamedArray3.new
82
+ a.name = :roger
83
+ p [3, a.name, a.instance_variables, a.metaclass.instance_variables]
84
+
85
+ # Create a module with the which puts the functionality
86
+ # in NamedArray3 into a class method.
87
+ module StorageArea
88
+ def store *junk
89
+ junk.each do |name|
90
+ class_eval do
91
+ define_method(name) do
92
+ metaclass.instance_variable_get("@#{name}")
93
+ end
94
+ define_method("#{name}=") do |val|
95
+ metaclass.instance_variable_set("@#{name}", val)
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ class NamedArray4
102
+ extend StorageArea
103
+ store :name
104
+ end
105
+ a = NamedArray4.new
106
+ a.name = :roger
107
+ p [4, a.name, a.instance_variables, a.metaclass.instance_variables]
108
+
109
+
110
+
111
+
112
+