meta_instance 1.0.2 → 1.1.0

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,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