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.
- checksums.yaml +4 -4
- data/.codeclimate.yml +7 -0
- data/.gitignore +33 -16
- data/.travis.yml +8 -4
- data/Gemfile +2 -3
- data/Gemfile.lock +66 -0
- data/HISTORY.md +33 -0
- data/LICENSE.txt +23 -0
- data/README.md +56 -25
- data/docs/instance.md +201 -0
- data/docs/module.md +34 -0
- data/lib/meta_instance.rb +32 -53
- data/lib/meta_instance/freeze_method.rb +13 -0
- data/lib/meta_instance/instance_method_define.rb +61 -0
- data/lib/meta_instance/module_extensions.rb +61 -0
- data/lib/meta_instance/proxy.rb +270 -0
- data/lib/meta_instance/version.rb +2 -2
- data/meta_instance.gemspec +4 -3
- data/spec/instance_spec.rb +123 -0
- data/spec/{meta_instance_spec.rb → meta_instance/instance_method_define_spec.rb} +22 -15
- data/spec/module_spec.rb +0 -0
- data/spec/spec_helper.rb +1 -3
- metadata +35 -11
- data/.rspec +0 -2
- data/LICENSE +0 -22
- data/Rakefile +0 -1
- data/spec/support/foo.rb +0 -5
data/docs/module.md
ADDED
@@ -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
|
+
|
data/lib/meta_instance.rb
CHANGED
@@ -1,60 +1,39 @@
|
|
1
|
-
#
|
2
|
-
#
|
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
|
-
|
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
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
-
|
52
|
-
#
|
53
|
-
#
|
54
|
-
|
55
|
-
|
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
|