redis-objects-legacy 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.travis.yml +16 -0
- data/ATOMICITY.rdoc +154 -0
- data/CHANGELOG.rdoc +362 -0
- data/Gemfile +4 -0
- data/LICENSE +202 -0
- data/README.md +600 -0
- data/Rakefile +14 -0
- data/lib/redis/base_object.rb +62 -0
- data/lib/redis/counter.rb +145 -0
- data/lib/redis/enumerable_object.rb +28 -0
- data/lib/redis/hash_key.rb +163 -0
- data/lib/redis/helpers/core_commands.rb +89 -0
- data/lib/redis/list.rb +160 -0
- data/lib/redis/lock.rb +89 -0
- data/lib/redis/objects/connection_pool_proxy.rb +31 -0
- data/lib/redis/objects/counters.rb +155 -0
- data/lib/redis/objects/hashes.rb +60 -0
- data/lib/redis/objects/lists.rb +58 -0
- data/lib/redis/objects/locks.rb +73 -0
- data/lib/redis/objects/sets.rb +58 -0
- data/lib/redis/objects/sorted_sets.rb +49 -0
- data/lib/redis/objects/values.rb +64 -0
- data/lib/redis/objects/version.rb +5 -0
- data/lib/redis/objects.rb +199 -0
- data/lib/redis/set.rb +182 -0
- data/lib/redis/sorted_set.rb +325 -0
- data/lib/redis/value.rb +65 -0
- data/lib/redis-objects-legacy.rb +1 -0
- data/spec/redis_autoload_objects_spec.rb +46 -0
- data/spec/redis_namespace_compat_spec.rb +24 -0
- data/spec/redis_objects_active_record_spec.rb +162 -0
- data/spec/redis_objects_conn_spec.rb +276 -0
- data/spec/redis_objects_custom_serializer.rb +198 -0
- data/spec/redis_objects_instance_spec.rb +1666 -0
- data/spec/redis_objects_model_spec.rb +1097 -0
- data/spec/spec_helper.rb +92 -0
- metadata +214 -0
@@ -0,0 +1,155 @@
|
|
1
|
+
# This is the class loader, for use as "include Redis::Objects::Counters"
|
2
|
+
# For the object itself, see "Redis::Counter"
|
3
|
+
require 'redis/counter'
|
4
|
+
class Redis
|
5
|
+
module Objects
|
6
|
+
class UndefinedCounter < StandardError; end #:nodoc:
|
7
|
+
class MissingID < StandardError; end #:nodoc:
|
8
|
+
|
9
|
+
module Counters
|
10
|
+
def self.included(klass)
|
11
|
+
klass.instance_variable_set('@initialized_counters', {})
|
12
|
+
klass.send :include, InstanceMethods
|
13
|
+
klass.extend ClassMethods
|
14
|
+
end
|
15
|
+
|
16
|
+
# Class methods that appear in your class when you include Redis::Objects.
|
17
|
+
module ClassMethods
|
18
|
+
attr_reader :initialized_counters
|
19
|
+
|
20
|
+
# Define a new counter. It will function like a regular instance
|
21
|
+
# method, so it can be used alongside ActiveRecord, DataMapper, etc.
|
22
|
+
def counter(name, options={})
|
23
|
+
options[:start] ||= 0
|
24
|
+
options[:type] ||= options[:start] == 0 ? :increment : :decrement
|
25
|
+
redis_objects[name.to_sym] = options.merge(:type => :counter)
|
26
|
+
ivar_name = :"@#{name}"
|
27
|
+
|
28
|
+
mod = Module.new do
|
29
|
+
define_method(name) do
|
30
|
+
instance_variable_get(ivar_name) or
|
31
|
+
instance_variable_set(ivar_name,
|
32
|
+
Redis::Counter.new(
|
33
|
+
redis_field_key(name), redis_field_redis(name), redis_options(name)
|
34
|
+
)
|
35
|
+
)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
if options[:global]
|
40
|
+
extend mod
|
41
|
+
|
42
|
+
# dispatch to class methods
|
43
|
+
define_method(name) do
|
44
|
+
self.class.public_send(name)
|
45
|
+
end
|
46
|
+
else
|
47
|
+
include mod
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Get the current value of the counter. It is more efficient
|
52
|
+
# to use the instance method if possible.
|
53
|
+
def get_counter(name, id=nil)
|
54
|
+
verify_counter_defined!(name, id)
|
55
|
+
initialize_counter!(name, id)
|
56
|
+
redis.get(redis_field_key(name, id)).to_i
|
57
|
+
end
|
58
|
+
|
59
|
+
# Increment a counter with the specified name and id. Accepts a block
|
60
|
+
# like the instance method. See Redis::Objects::Counter for details.
|
61
|
+
def increment_counter(name, id=nil, by=1, &block)
|
62
|
+
name = name.to_sym
|
63
|
+
return super(name, id) unless counter_defined?(name)
|
64
|
+
verify_counter_defined!(name, id)
|
65
|
+
initialize_counter!(name, id)
|
66
|
+
value = redis.incrby(redis_field_key(name, id), by).to_i
|
67
|
+
block_given? ? rewindable_block(:decrement_counter, name, id, by, value, &block) : value
|
68
|
+
end
|
69
|
+
|
70
|
+
# Decrement a counter with the specified name and id. Accepts a block
|
71
|
+
# like the instance method. See Redis::Objects::Counter for details.
|
72
|
+
def decrement_counter(name, id=nil, by=1, &block)
|
73
|
+
name = name.to_sym
|
74
|
+
return super(name, id) unless counter_defined?(name)
|
75
|
+
verify_counter_defined!(name, id)
|
76
|
+
initialize_counter!(name, id)
|
77
|
+
value = redis.decrby(redis_field_key(name, id), by).to_i
|
78
|
+
block_given? ? rewindable_block(:increment_counter, name, id, by, value, &block) : value
|
79
|
+
end
|
80
|
+
|
81
|
+
# Reset a counter to its starting value.
|
82
|
+
def reset_counter(name, id=nil, to=nil)
|
83
|
+
verify_counter_defined!(name, id)
|
84
|
+
to = redis_objects[name][:start] if to.nil?
|
85
|
+
redis.set(redis_field_key(name, id), to.to_i)
|
86
|
+
true
|
87
|
+
end
|
88
|
+
|
89
|
+
# Set a counter to its starting value and return the old value.
|
90
|
+
def getset_counter(name, id=nil, to=nil)
|
91
|
+
verify_counter_defined!(name, id)
|
92
|
+
to = redis_objects[name][:start] if to.nil?
|
93
|
+
redis.getset(redis_field_key(name, id), to.to_i).to_i
|
94
|
+
end
|
95
|
+
|
96
|
+
def counter_defined?(name) #:nodoc:
|
97
|
+
redis_objects && redis_objects.has_key?(name.to_sym)
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def verify_counter_defined!(name, id) #:nodoc:
|
103
|
+
raise NoMethodError, "Undefined counter :#{name} for class #{self.name}" unless counter_defined?(name)
|
104
|
+
if id.nil? and !redis_objects[name][:global]
|
105
|
+
raise Redis::Objects::MissingID, "Missing ID for non-global counter #{self.name}##{name}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def initialize_counter!(name, id) #:nodoc:
|
110
|
+
key = redis_field_key(name, id)
|
111
|
+
unless @initialized_counters[key]
|
112
|
+
redis.setnx(key, redis_objects[name][:start])
|
113
|
+
end
|
114
|
+
@initialized_counters[key] = true
|
115
|
+
end
|
116
|
+
|
117
|
+
# Implements increment/decrement blocks on a class level
|
118
|
+
def rewindable_block(rewind, name, id, by, value, &block) #:nodoc:
|
119
|
+
# Unfortunately this is almost exactly duplicated from Redis::Counter
|
120
|
+
raise ArgumentError, "Missing block to rewindable_block somehow" unless block_given?
|
121
|
+
ret = nil
|
122
|
+
begin
|
123
|
+
ret = yield value
|
124
|
+
rescue
|
125
|
+
send(rewind, name, id, by)
|
126
|
+
raise
|
127
|
+
end
|
128
|
+
send(rewind, name, id, by) if ret.nil?
|
129
|
+
ret
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Instance methods that appear in your class when you include Redis::Objects.
|
134
|
+
module InstanceMethods
|
135
|
+
# Increment a counter. Called mainly in the context of :counter_cache
|
136
|
+
def increment(name, by=1)
|
137
|
+
if self.class.counter_defined?(name)
|
138
|
+
send(name).increment(by)
|
139
|
+
else
|
140
|
+
super # ActiveRecord
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Decrement a counter. Called mainly in the context of :counter_cache
|
145
|
+
def decrement(name, by=1)
|
146
|
+
if self.class.counter_defined?(name)
|
147
|
+
send(name).decrement(by)
|
148
|
+
else
|
149
|
+
super # ActiveRecord
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# This is the class loader, for use as "include Redis::Objects::Hashes"
|
2
|
+
# For the object itself, see "Redis::Hash"
|
3
|
+
require 'redis/hash_key'
|
4
|
+
class Redis
|
5
|
+
module Objects
|
6
|
+
module Hashes
|
7
|
+
def self.included(klass)
|
8
|
+
klass.send :include, InstanceMethods
|
9
|
+
klass.extend ClassMethods
|
10
|
+
end
|
11
|
+
|
12
|
+
# Class methods that appear in your class when you include Redis::Objects.
|
13
|
+
module ClassMethods
|
14
|
+
# Define a new hash key. It will function like a regular instance
|
15
|
+
# method, so it can be used alongside ActiveRecord, DataMapper, etc.
|
16
|
+
def hash_key(name, options={})
|
17
|
+
redis_objects[name.to_sym] = options.merge(:type => :dict)
|
18
|
+
ivar_name = :"@#{name}"
|
19
|
+
|
20
|
+
mod = Module.new do
|
21
|
+
define_method(name) do
|
22
|
+
instance_variable_get(ivar_name) or
|
23
|
+
instance_variable_set(ivar_name,
|
24
|
+
Redis::HashKey.new(
|
25
|
+
redis_field_key(name), redis_field_redis(name), redis_options(name)
|
26
|
+
)
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
define_method(:"#{name}=") do |values|
|
31
|
+
hash_key = public_send(name)
|
32
|
+
|
33
|
+
redis.pipelined do
|
34
|
+
hash_key.clear
|
35
|
+
hash_key.bulk_set(values)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
if options[:global]
|
41
|
+
extend mod
|
42
|
+
|
43
|
+
# dispatch to class methods
|
44
|
+
define_method(name) do
|
45
|
+
self.class.public_send(name)
|
46
|
+
end
|
47
|
+
else
|
48
|
+
include mod
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Instance methods that appear in your class when you include Redis::Objects.
|
54
|
+
module InstanceMethods
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# This is the class loader, for use as "include Redis::Objects::Lists"
|
2
|
+
# For the object itself, see "Redis::List"
|
3
|
+
require 'redis/list'
|
4
|
+
class Redis
|
5
|
+
module Objects
|
6
|
+
module Lists
|
7
|
+
def self.included(klass)
|
8
|
+
klass.send :include, InstanceMethods
|
9
|
+
klass.extend ClassMethods
|
10
|
+
end
|
11
|
+
|
12
|
+
# Class methods that appear in your class when you include Redis::Objects.
|
13
|
+
module ClassMethods
|
14
|
+
# Define a new list. It will function like a regular instance
|
15
|
+
# method, so it can be used alongside ActiveRecord, DataMapper, etc.
|
16
|
+
def list(name, options={})
|
17
|
+
redis_objects[name.to_sym] = options.merge(:type => :list)
|
18
|
+
ivar_name = :"@#{name}"
|
19
|
+
|
20
|
+
mod = Module.new do
|
21
|
+
define_method(name) do
|
22
|
+
instance_variable_get(ivar_name) or
|
23
|
+
instance_variable_set(ivar_name,
|
24
|
+
Redis::List.new(
|
25
|
+
redis_field_key(name), redis_field_redis(name), redis_options(name)
|
26
|
+
)
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
define_method(:"#{name}=") do |values|
|
31
|
+
list = public_send(name)
|
32
|
+
|
33
|
+
redis.pipelined do
|
34
|
+
list.clear
|
35
|
+
list.push(*values)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
if options[:global]
|
41
|
+
extend mod
|
42
|
+
|
43
|
+
# dispatch to class methods
|
44
|
+
define_method(name) do
|
45
|
+
self.class.public_send(name)
|
46
|
+
end
|
47
|
+
else
|
48
|
+
include mod
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Instance methods that appear in your class when you include Redis::Objects.
|
54
|
+
module InstanceMethods
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# This is the class loader, for use as "include Redis::Objects::Locks"
|
2
|
+
# For the object itself, see "Redis::Lock"
|
3
|
+
require 'redis/lock'
|
4
|
+
class Redis
|
5
|
+
module Objects
|
6
|
+
class UndefinedLock < StandardError; end #:nodoc:
|
7
|
+
module Locks
|
8
|
+
def self.included(klass)
|
9
|
+
klass.send :include, InstanceMethods
|
10
|
+
klass.extend ClassMethods
|
11
|
+
end
|
12
|
+
|
13
|
+
# Class methods that appear in your class when you include Redis::Objects.
|
14
|
+
module ClassMethods
|
15
|
+
# Define a new lock. It will function like a model attribute,
|
16
|
+
# so it can be used alongside ActiveRecord/DataMapper, etc.
|
17
|
+
def lock(name, options={})
|
18
|
+
options[:timeout] ||= 5 # seconds
|
19
|
+
lock_name = "#{name}_lock"
|
20
|
+
redis_objects[lock_name.to_sym] = options.merge(:type => :lock)
|
21
|
+
ivar_name = :"@#{lock_name}"
|
22
|
+
|
23
|
+
mod = Module.new do
|
24
|
+
define_method(lock_name) do |&block|
|
25
|
+
instance_variable_get(ivar_name) or
|
26
|
+
instance_variable_set(ivar_name,
|
27
|
+
Redis::Lock.new(
|
28
|
+
redis_field_key(lock_name), redis_field_redis(lock_name), redis_objects[lock_name.to_sym]
|
29
|
+
)
|
30
|
+
)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
if options[:global]
|
35
|
+
extend mod
|
36
|
+
|
37
|
+
# dispatch to class methods
|
38
|
+
define_method(lock_name) do |&block|
|
39
|
+
self.class.public_send(lock_name, &block)
|
40
|
+
end
|
41
|
+
else
|
42
|
+
include mod
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Obtain a lock, and execute the block synchronously. Any other code
|
47
|
+
# (on any server) will spin waiting for the lock up to the :timeout
|
48
|
+
# that was specified when the lock was defined.
|
49
|
+
def obtain_lock(name, id, &block)
|
50
|
+
verify_lock_defined!(name)
|
51
|
+
raise ArgumentError, "Missing block to #{self.name}.obtain_lock" unless block_given?
|
52
|
+
lock_name = "#{name}_lock"
|
53
|
+
Redis::Lock.new(redis_field_key(lock_name, id), redis_field_redis(lock_name), redis_objects[lock_name.to_sym]).lock(&block)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Clear the lock. Use with care - usually only in an Admin page to clear
|
57
|
+
# stale locks (a stale lock should only happen if a server crashes.)
|
58
|
+
def clear_lock(name, id)
|
59
|
+
verify_lock_defined!(name)
|
60
|
+
redis.del(redis_field_key("#{name}_lock", id))
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def verify_lock_defined!(name)
|
66
|
+
unless redis_objects.has_key?("#{name}_lock".to_sym)
|
67
|
+
raise Redis::Objects::UndefinedLock, "Undefined lock :#{name} for class #{self.name}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# This is the class loader, for use as "include Redis::Objects::Sets"
|
2
|
+
# For the object itself, see "Redis::Set"
|
3
|
+
require 'redis/set'
|
4
|
+
class Redis
|
5
|
+
module Objects
|
6
|
+
module Sets
|
7
|
+
def self.included(klass)
|
8
|
+
klass.send :include, InstanceMethods
|
9
|
+
klass.extend ClassMethods
|
10
|
+
end
|
11
|
+
|
12
|
+
# Class methods that appear in your class when you include Redis::Objects.
|
13
|
+
module ClassMethods
|
14
|
+
# Define a new list. It will function like a regular instance
|
15
|
+
# method, so it can be used alongside ActiveRecord, DataMapper, etc.
|
16
|
+
def set(name, options={})
|
17
|
+
redis_objects[name.to_sym] = options.merge(:type => :set)
|
18
|
+
ivar_name = :"@#{name}"
|
19
|
+
|
20
|
+
mod = Module.new do
|
21
|
+
define_method(name) do
|
22
|
+
instance_variable_get(ivar_name) or
|
23
|
+
instance_variable_set(ivar_name,
|
24
|
+
Redis::Set.new(
|
25
|
+
redis_field_key(name), redis_field_redis(name), redis_options(name)
|
26
|
+
)
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
define_method(:"#{name}=") do |values|
|
31
|
+
set = public_send(name)
|
32
|
+
|
33
|
+
redis.pipelined do
|
34
|
+
set.clear
|
35
|
+
set.merge(*values)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
if options[:global]
|
41
|
+
extend mod
|
42
|
+
|
43
|
+
# dispatch to class methods
|
44
|
+
define_method(name) do
|
45
|
+
self.class.public_send(name)
|
46
|
+
end
|
47
|
+
else
|
48
|
+
include mod
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Instance methods that appear in your class when you include Redis::Objects.
|
54
|
+
module InstanceMethods
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# This is the class loader, for use as "include Redis::Objects::Sets"
|
2
|
+
# For the object itself, see "Redis::Set"
|
3
|
+
require 'redis/sorted_set'
|
4
|
+
class Redis
|
5
|
+
module Objects
|
6
|
+
module SortedSets
|
7
|
+
def self.included(klass)
|
8
|
+
klass.send :include, InstanceMethods
|
9
|
+
klass.extend ClassMethods
|
10
|
+
end
|
11
|
+
|
12
|
+
# Class methods that appear in your class when you include Redis::Objects.
|
13
|
+
module ClassMethods
|
14
|
+
# Define a new list. It will function like a regular instance
|
15
|
+
# method, so it can be used alongside ActiveRecord, DataMapper, etc.
|
16
|
+
def sorted_set(name, options={})
|
17
|
+
redis_objects[name.to_sym] = options.merge(:type => :sorted_set)
|
18
|
+
ivar_name = :"@#{name}"
|
19
|
+
|
20
|
+
mod = Module.new do
|
21
|
+
define_method(name) do
|
22
|
+
instance_variable_get(ivar_name) or
|
23
|
+
instance_variable_set(ivar_name,
|
24
|
+
Redis::SortedSet.new(
|
25
|
+
redis_field_key(name), redis_field_redis(name), redis_options(name)
|
26
|
+
)
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
if options[:global]
|
32
|
+
extend mod
|
33
|
+
|
34
|
+
# dispatch to class methods
|
35
|
+
define_method(name) do
|
36
|
+
self.class.public_send(name)
|
37
|
+
end
|
38
|
+
else
|
39
|
+
include mod
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Instance methods that appear in your class when you include Redis::Objects.
|
45
|
+
module InstanceMethods
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# This is the class loader, for use as "include Redis::Objects::Values"
|
2
|
+
# For the object itself, see "Redis::Value"
|
3
|
+
require 'redis/value'
|
4
|
+
class Redis
|
5
|
+
module Objects
|
6
|
+
module Values
|
7
|
+
def self.included(klass)
|
8
|
+
klass.send :include, InstanceMethods
|
9
|
+
klass.extend ClassMethods
|
10
|
+
end
|
11
|
+
|
12
|
+
# Class methods that appear in your class when you include Redis::Objects.
|
13
|
+
module ClassMethods
|
14
|
+
# Define a new simple value. It will function like a regular instance
|
15
|
+
# method, so it can be used alongside ActiveRecord, DataMapper, etc.
|
16
|
+
def value(name, options={})
|
17
|
+
redis_objects[name.to_sym] = options.merge(:type => :value)
|
18
|
+
ivar_name = :"@#{name}"
|
19
|
+
|
20
|
+
mod = Module.new do
|
21
|
+
define_method(name) do
|
22
|
+
instance_variable_get(ivar_name) or
|
23
|
+
instance_variable_set(ivar_name,
|
24
|
+
Redis::Value.new(
|
25
|
+
redis_field_key(name), redis_field_redis(name), redis_options(name)
|
26
|
+
)
|
27
|
+
)
|
28
|
+
end
|
29
|
+
define_method("#{name}=") do |value|
|
30
|
+
public_send(name).value = value
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
if options[:global]
|
35
|
+
extend mod
|
36
|
+
|
37
|
+
# dispatch to class methods
|
38
|
+
define_method(name) do
|
39
|
+
self.class.public_send(name)
|
40
|
+
end
|
41
|
+
define_method("#{name}=") do |value|
|
42
|
+
self.class.public_send("#{name}=", value)
|
43
|
+
end
|
44
|
+
else
|
45
|
+
include mod
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def mget(name, objects = [])
|
50
|
+
return [] if objects.nil? || objects.empty?
|
51
|
+
raise "Field name Error" if !redis_objects.keys.include?(name.to_sym)
|
52
|
+
|
53
|
+
keys = objects.map{ |obj| obj.redis_field_key name.to_sym }
|
54
|
+
|
55
|
+
self.redis.mget keys
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Instance methods that appear in your class when you include Redis::Objects.
|
60
|
+
module InstanceMethods
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|