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.
@@ -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
- class NoMetaClass < RuntimeError
3
- end
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
- # = Object
8
+ # = Attic
6
9
  #
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.
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
- # 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 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
- # self.metaclass.metaclass
114
+ # Attic.construct(Object)
61
115
  #
62
- def metametaclass; self.metaclass.metaclass; end
63
-
64
- def metameta_eval &blk; metametaclass.instance_eval &blk; end
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 metameta_def name, &blk
67
- metameta_eval { define_method name, &blk }
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
- # = Attic
75
- #
76
- # A place to store instance variables.
77
- #
78
- module Attic
79
- VERSION = '0.5.3' unless defined?(VERSION)
80
-
81
- module InstanceMethods
82
- def attic_variables
83
- self.class.attic_variables
84
- end
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
- end
105
-
106
- def self.included(o)
107
- raise "You probably meant to 'extend Attic' in #{o}"
108
- end
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
- if method_defined? :instance_variables
132
- old_instance_variables = instance_method(:instance_variables)
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
- # A class method for defining variables to store in the attic.
149
- # * +junk+ is a list of variables names. Accessor methods are
150
- # created for each variable name in the list.
151
- #
152
- # Returns the list of attic variable names or if not junk was
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 *junk
165
- return metaclass if junk.empty?
166
- junk.each do |name|
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
- self.attic_variables << name
169
-
170
- unless method_defined? name
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
- unless method_defined? "#{name}="
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
- attic_vars
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
- a = self.metaclass.instance_variable_get("@attic_variables")
193
- a ||= self.metaclass.instance_variable_set("@attic_variables", [])
194
- a
195
- end
196
- 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
197
217
 
198
- def attic_variable?(n)
199
- attic_variables.member? n
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
- require 'attic'
2
-
1
+ require_relative "../lib/attic"
2
+
3
+ Attic.construct Symbol #, :name
4
+
3
5
  ## has list of no metaclass classes
4
- Object::NOMETACLASS
5
- #=> [Symbol, Fixnum]
6
-
7
- ## Symbol metaclass raises exception
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.metaclass
10
- rescue NoMetaClass
11
- :success
19
+ :any.attic.class
20
+ rescue NoSingletonError
21
+ :failed
12
22
  end
13
- #=> :success
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
- ## metaclass? method exists
32
+
33
+ ## attic? method exists
24
34
  Symbol.extend Attic
25
- :any.respond_to? :metaclass?
35
+ :any.respond_to? :attic?
26
36
  #=> true
27
-
28
- ## metaclass? method is false for a Symbol", false do
29
- :any.metaclass?
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" do
42
+ ## A Symbol's attic vars appear in `all_instance_variables` do
33
43
  Symbol.extend Attic
34
- Symbol.attic :name
44
+ Symbol.attic :_name
35
45
  a, b = :symbol1, :symbol2
36
- a.name = :roger
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" do
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", [:name] do
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