meta_instance 1.0.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,34 @@
1
+ # Instance for Modules/Classes
2
+
3
+ Modules and Classes have additional features not shared by other
4
+ types of objects.
5
+
6
+ ## Instance#method_definition
7
+
8
+ um = String.instance.method_definition(:to_s)
9
+ um.class.assert == UnboundMethod
10
+
11
+ ## Instance#definition
12
+
13
+ The `#definition` method is just an alias for `#method_definition`.
14
+
15
+ um = String.instance.definition(:to_s)
16
+ um.class.assert == UnboundMethod
17
+
18
+ ## Instance#method_definitions
19
+
20
+ list = String.instance.method_definitions
21
+
22
+ Method definitions can be selected use support symbol selectors.
23
+
24
+ list = String.instance.method_definitions(:public)
25
+ list = String.instance.method_definitions(:protected)
26
+ list = String.instance.method_definitions(:private)
27
+ list = String.instance.method_definitions(:private, :protected)
28
+
29
+ ## Instance#definitions
30
+
31
+ The `#definitions` method is likewise an alias for `#method_definitions`.
32
+
33
+ list = String.instance.definitions
34
+
@@ -1,60 +1,39 @@
1
- # This is based on a few things from
2
- # http://reference.jumpingmonkey.org/programming_languages/ruby/ruby-metaprogramming.html
1
+ # Instance class is a delgator for any object which provides an elegant
2
+ # and protected interface to an object's state, i.e. its *instance*.
3
+ # Elgence is achieved by providing a single interface, the `instance`
4
+ # method. Protection is made possible by caching all the built-in
5
+ # Ruby methods used to interface with an object's internal state.
6
+ # This way they can not be overriden by some errant code or third
7
+ # party library.
8
+ #
9
+ # Examples
10
+ #
11
+ # class Friend
12
+ # attr_accessor :name, :age, :phone
13
+ # def initialize(name, age, phone)
14
+ # @name, @age, @phone = name, age, phone
15
+ # end
16
+ # end
17
+ #
18
+ # f1 = Friend.new("John", 30, "555-1212")
19
+ # f1.instance.get(:name) #=> "John"
20
+ # f1.instance.update(:name=>'Jerry')
21
+ # f1.instance.get(:name) #=> "Jerry"
3
22
  #
4
- # allows the adding of methods to instances,
5
- # but not the entire set of instances for a
6
- # particular class
7
- module MetaInstance
8
-
9
- # when a method is stubbed with snapshot data, we stare the
10
- # original method prixefixed with this:
11
- METHOD_BACKUP_KEY = "_mata_instance_backup_current_"
12
-
13
- # backs up and overrides a method.
14
- # but don't override if we already have overridden this method
15
- def instance_override(name, &block)
16
- unless respond_to?("#{METHOD_BACKUP_KEY}#{name}")
17
- backup_instance_method(name)
18
- end
19
- instance_define(name, &block)
20
- end
21
-
22
- # Adds methods to a singletonclass
23
- # define_singleton_method(name, &block) is the same as doing
24
- #
25
- # meta_eval {
26
- # define_method(name, &block)
27
- # }
28
- def instance_define(name, &block)
29
- define_singleton_method(name, &block)
30
- end
31
23
 
32
- # backs up a method in case we want to restore it later
33
- def backup_instance_method(name)
34
- meta_eval {
35
- alias_method "#{METHOD_BACKUP_KEY}#{name}", name
36
- }
37
- end
24
+ require 'active_support'
38
25
 
39
- # the original method becomes reaccessible
40
- def restore_instance_method(name)
41
- if respond_to?("#{METHOD_BACKUP_KEY}#{name}")
42
- meta_eval {
43
- alias_method name, "#{METHOD_BACKUP_KEY}#{name}"
44
- remove_method "#{METHOD_BACKUP_KEY}#{name}"
45
- }
46
- end
47
- end
26
+ require 'meta_instance/version'
27
+ require 'meta_instance/freeze_method'
28
+ require 'meta_instance/module_extensions'
29
+ require 'meta_instance/instance_method_define'
30
+ require 'meta_instance/proxy'
48
31
 
49
- private
50
32
 
51
- # evals a block inside of a singleton class, aka
52
- #
53
- # class << self
54
- # self
55
- # end
56
- def meta_eval(&block)
57
- singleton_class.instance_eval(&block)
33
+ class BasicObject
34
+ # Returns an instance of Instance for `self`, which allows convenient
35
+ # access to an object's internals.
36
+ def instance
37
+ ::MetaInstance::Proxy.instance(self)
58
38
  end
59
-
60
39
  end
@@ -0,0 +1,13 @@
1
+ module MetaInstance::FreezeMethod
2
+ extend ActiveSupport::Concern
3
+
4
+ # Store Object methods so they cannot be overriden by the delegate class.
5
+ METHODS = {}
6
+
7
+ module ClassMethods
8
+ def freeze_method(name)
9
+ METHODS[name.to_sym] = Module.instance_method(name)
10
+ end
11
+ end
12
+
13
+ end
@@ -0,0 +1,61 @@
1
+ # This is based on a few things from
2
+ # http://reference.jumpingmonkey.org/programming_languages/ruby/ruby-metaprogramming.html
3
+ #
4
+ # allows the adding of methods to instances,
5
+ # but not the entire set of instances for a
6
+ # particular class
7
+ module MetaInstance::InstanceMethodDefine
8
+ extend ActiveSupport::Concern
9
+
10
+ # when a method is stubbed with snapshot data, we stare the
11
+ # original method prixefixed with this:
12
+ METHOD_BACKUP_KEY = "_mata_instance_backup_current_"
13
+
14
+ # backs up and overrides a method.
15
+ # but don't override if we already have overridden this method
16
+ def instance_override(name, &block)
17
+ unless respond_to?("#{METHOD_BACKUP_KEY}#{name}")
18
+ backup_method(name)
19
+ end
20
+ define_method(name, &block)
21
+ end
22
+
23
+ # Adds methods to a singletonclass
24
+ # define_singleton_method(name, &block) is the same as doing
25
+ #
26
+ # meta_eval {
27
+ # define_method(name, &block)
28
+ # }
29
+ def define_method(name, &block)
30
+ define_singleton_method(name, &block)
31
+ end
32
+
33
+ # backs up a method in case we want to restore it later
34
+ def backup_method(name)
35
+ meta_eval {
36
+ alias_method "#{METHOD_BACKUP_KEY}#{name}", name
37
+ }
38
+ end
39
+
40
+ # the original method becomes reaccessible
41
+ def restore_method(name)
42
+ if respond_to?("#{METHOD_BACKUP_KEY}#{name}")
43
+ meta_eval {
44
+ alias_method name, "#{METHOD_BACKUP_KEY}#{name}"
45
+ remove_method "#{METHOD_BACKUP_KEY}#{name}"
46
+ }
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ # evals a block inside of a singleton class, aka
53
+ #
54
+ # class << self
55
+ # self
56
+ # end
57
+ def meta_eval(&block)
58
+ singleton_class.instance_eval(&block)
59
+ end
60
+
61
+ end
@@ -0,0 +1,61 @@
1
+ require_relative "freeze_method"
2
+
3
+ ##
4
+ # ModuleExtensions provides some additional methods for Module and Class
5
+ # objects.
6
+ #
7
+ # TODO: Are there any other module/class methods that need to be provided?
8
+ #
9
+ module MetaInstance::ModuleExtensions
10
+ extend ActiveSupport::Concern
11
+
12
+ include MetaInstance::FreezeMethod
13
+ # Store Object methods so they cannot be overriden by the delegate class.
14
+
15
+ included do
16
+ freeze_method :instance_method
17
+ freeze_method :instance_methods
18
+ freeze_method :public_instance_methods
19
+ freeze_method :protected_instance_methods
20
+ freeze_method :private_instance_methods
21
+ end
22
+
23
+ # List of method definitions in a module or class.
24
+ #
25
+ # selection - Any of `:public`, `:protected` or `:private` which
26
+ # is used to select specific subsets of methods.
27
+ #
28
+ # Returns [Array<Symbol>]
29
+ def method_definitions(*selection)
30
+ list = []
31
+
32
+ if selection.empty?
33
+ list.concat bind_call(:instance_methods)
34
+ end
35
+
36
+ selection.each do |s|
37
+ case s
38
+ when :public, :all
39
+ list.concat bind_call(:public_instance_methods)
40
+ when :protected, :all
41
+ list.concat bind_call(:protected_instance_methods)
42
+ when :private, :all
43
+ list.concat bind_call(:private_instance_methods)
44
+ end
45
+ end
46
+
47
+ return list
48
+ end
49
+
50
+ # Shorter alias for #method_definitions.
51
+ alias :definitions :method_definitions
52
+
53
+ # Get a first-class method definition object.
54
+ #
55
+ # Returns an unbound method object. [UnboundMethod]
56
+ def method_definition(name)
57
+ bind_call(:instance_method, name)
58
+ end
59
+
60
+ alias :definition :method_definition
61
+ end
@@ -0,0 +1,270 @@
1
+ # require 'forwardable'
2
+ require 'active_support/core_ext'
3
+
4
+ module MetaInstance
5
+ class Proxy
6
+ # extend Forwardable
7
+ include Enumerable
8
+ include FreezeMethod
9
+ include ModuleExtensions
10
+ include InstanceMethodDefine
11
+
12
+ freeze_method :object_id
13
+ freeze_method :class
14
+ freeze_method :instance_of?
15
+ freeze_method :method
16
+ freeze_method :methods
17
+ freeze_method :public_methods
18
+ freeze_method :protected_methods
19
+ freeze_method :private_methods
20
+ freeze_method :instance_eval
21
+ freeze_method :instance_exec
22
+ freeze_method :instance_variables
23
+ freeze_method :instance_variable_get
24
+ freeze_method :instance_variable_set
25
+ freeze_method :instance_variable_defined?
26
+ freeze_method :remove_instance_variable
27
+ freeze_method :send
28
+ freeze_method :is_a?
29
+ freeze_method :kind_of?
30
+
31
+ delegate :send, :class, :kind_of, :is_a?, :instance_variables, to: :@delegate
32
+
33
+
34
+
35
+ # Instance cache acts as a global cache for instances of Instance.
36
+ @cache = {}
37
+
38
+ # Instance is multiton. Use this method instead of #new to get a
39
+ # cached instance.
40
+ def self.instance(delegate)
41
+ @cache[delegate] ||= MetaInstance::Proxy.new(delegate)
42
+ end
43
+
44
+ # Initialize new Instance instance. If the delegate is a type of
45
+ # Module or Class then the instance will be extended with the
46
+ # {ModuleExtensions} mixin.
47
+ #
48
+ def initialize(delegate)
49
+ @delegate = delegate
50
+ extend ModuleExtensions if Module === delegate
51
+ end
52
+
53
+ # The delegated object.
54
+ def delegate
55
+ @delegate
56
+ end
57
+
58
+ # Iterate over instance variables.
59
+ def each
60
+ variables.each do |name|
61
+ yield(name[1..-1].to_sym, get(name))
62
+ end
63
+ end
64
+
65
+ # Number of instance variables.
66
+ def size
67
+ variables.size
68
+ end
69
+
70
+ # Get instance variables with values as a hash.
71
+ #
72
+ # Examples
73
+ #
74
+ # class X
75
+ # def initialize(a,b)
76
+ # @a, @b = a, b
77
+ # end
78
+ # end
79
+ #
80
+ # x = X.new(1,2)
81
+ #
82
+ # x.instance.to_h #=> { :a=>1, :b=>2 }
83
+ #
84
+ # Returns [Hash].
85
+ def to_h(at=false)
86
+ h = {}
87
+ if at
88
+ variables.each do |name|
89
+ h[name] = get(name)
90
+ end
91
+ else
92
+ each do |key, value|
93
+ h[key] = value
94
+ end
95
+ end
96
+ h
97
+ end
98
+
99
+ # Get instance variable's value. Will return `nil` if the
100
+ # variable does not exist.
101
+ #
102
+ # Returns the value of the instance variable.
103
+ def get(name)
104
+ name = atize(name)
105
+ delegate.instance_variable_get(name)
106
+ end
107
+ alias :[] :get
108
+
109
+ # Set instance variable.
110
+ #
111
+ # Returns the set value.
112
+ def set(name, value)
113
+ name = atize(name)
114
+ delegate.instance_variable_set(name, value)
115
+ end
116
+ alias :[]= :set
117
+
118
+ # Set an instance variable given a name and a value in an array pair.
119
+ #
120
+ # Example
121
+ #
122
+ # f = Friend.new
123
+ # f.instance << [:name, "John"]
124
+ # f.name #=> "John"
125
+ #
126
+ # Returns the set value.
127
+ def <<(pair)
128
+ name, value = *pair
129
+ name = atize(name)
130
+ set(name, value)
131
+ end
132
+
133
+ # Remove instance variable.
134
+ def remove(name)
135
+ name = atize(name)
136
+ delegate.remove_instance_variable(name)
137
+ end
138
+
139
+ # Set instance variables given a +hash+.
140
+ #
141
+ # instance.update('@a'=>1, '@b'=>2)
142
+ # @a #=> 1
143
+ # @b #=> 2
144
+ #
145
+ # Also, +@+ sign is not neccessary.
146
+ #
147
+ # instance.update(:a=>1, :b=>2)
148
+ # @a #=> 1
149
+ # @b #=> 2
150
+ #
151
+ # Returns nothing.
152
+ def update(hash)
153
+ hash.each do |pair|
154
+ self << pair
155
+ end
156
+ end
157
+
158
+ # A hold-over from the the old #instance_assign method.
159
+ alias_method :assign, :update
160
+
161
+ # Same as #instance_variables.
162
+ def variables
163
+ delegate.instance_variables
164
+ end
165
+
166
+
167
+
168
+ # Instance vairable names as symbols.
169
+ #
170
+ # Returns [Array<Symbols>].
171
+ def keys
172
+ variables.collect do |name|
173
+ name[1..-1].to_sym
174
+ end
175
+ end
176
+
177
+ alias_method :names, :keys
178
+
179
+ # Instance variable values.
180
+ #
181
+ # Returns [Array<Object>].
182
+ def values
183
+ variables.collect do |name|
184
+ get(name)
185
+ end
186
+ end
187
+
188
+ # Instance evaluation.
189
+ def eval(*a,&b)
190
+ delegate.instance_eval(*a, &b)
191
+ end
192
+
193
+ # Instance execution.
194
+ def exec(*a,&b)
195
+ delegate.instance_exec(*a, &b)
196
+ end
197
+
198
+ # Get method. Usage of this might seem strange because Ruby's own
199
+ # `instance_method` method is a misnomer. It should be something
200
+ # like `definition` or `method_definition`. In Ruby the acutal
201
+ # "instance" method is accessed via the unadorned `method` method.
202
+ #
203
+ # Returns [Method].
204
+ def method(name)
205
+ bind_call(:method, name)
206
+ end
207
+
208
+ # Returns list of method names.
209
+ #
210
+ # Returns [Array<Symbol>].
211
+ def methods(*selection)
212
+ list = []
213
+
214
+ if selection.empty?
215
+ list.concat @delegate.methods
216
+ end
217
+
218
+ selection.each do |s|
219
+ case s
220
+ when :public, :all
221
+ list.concat @delegate.public_methods
222
+ when :protected, :all
223
+ list.concat @delegate.protected_methods
224
+ when :private, :all
225
+ list.concat @bind_call.private_methods
226
+ end
227
+ end
228
+
229
+ return list
230
+ end
231
+
232
+ # Is the object an instance of a given class?
233
+ #
234
+ # Returns [Boolean]
235
+ def of?(a_class)
236
+ delegate.instance_of?(a_class)
237
+ end
238
+
239
+
240
+ # Is an instaance variable defined?
241
+ #
242
+ # Returns [Boolean]
243
+ def variable_defined?(name)
244
+ name = atize(name)
245
+ delegate.instance_variable_defined?(name)
246
+ end
247
+
248
+ # Get object's instance id.
249
+ #
250
+ # Returns [Integer]
251
+ def id
252
+ delegate.object_id
253
+ end
254
+
255
+ # Fallback to get the real class of the Instance delegate itself.
256
+ alias :object_class :class
257
+
258
+ private
259
+
260
+ def atize(name)
261
+ name.to_s !~ /^@/ ? "@#{name}" : name
262
+ end
263
+
264
+ # TODO: Are there any method we need specific to a Class vs a Module?
265
+ #module ClassExtensions
266
+ #
267
+ #end
268
+
269
+ end
270
+ end