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.
@@ -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