arunthampi-supermodel 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README +23 -0
- data/Rakefile +48 -0
- data/VERSION +1 -0
- data/lib/supermodel.rb +9 -0
- data/lib/supermodel/base.rb +284 -0
- data/lib/supermodel/errors.rb +6 -0
- data/lib/supermodel/support.rb +2 -0
- data/lib/supermodel/support/extensions.rb +80 -0
- data/lib/supermodel/support/inflections.rb +52 -0
- data/lib/supermodel/support/inflector.rb +277 -0
- data/spec/base/from_json_spec.rb +70 -0
- data/spec/base/has_many_spec.rb +89 -0
- data/spec/base/has_one_spec.rb +80 -0
- data/spec/base/has_spec.rb +88 -0
- data/spec/base/initialize_spec.rb +64 -0
- data/spec/base/marshal_dump_spec.rb +64 -0
- data/spec/base/marshal_load_spec.rb +58 -0
- data/spec/base/module_spec.rb +15 -0
- data/spec/base/nested_class_spec.rb +16 -0
- data/spec/base/to_json_spec.rb +73 -0
- data/spec/spec_helper.rb +9 -0
- data/supermodel.gemspec +15 -0
- metadata +83 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 Arun Thampi
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
SuperModel aims to give sexy semantics to any model/library in Ruby that you might create. This includes has, has_many and has_one semantics which we generally associate with ActiveRecord.
|
2
|
+
|
3
|
+
This project draws its roots from the ActiveCouch project (http://www.github.com/arunthampi/activecouch), after which I saw myself re-using the same semantics in many other projects.
|
4
|
+
|
5
|
+
Hence, the SuperModel project was born!
|
6
|
+
|
7
|
+
The irony is that even though a SuperModel will be more bloated than a regular Ruby model, it will be better looking.
|
8
|
+
|
9
|
+
So with SuperModel, you can define a model such as this:
|
10
|
+
|
11
|
+
class Person < SuperModel::Base
|
12
|
+
has :name, :which_is => :text, :with_default_value => "McLovin"
|
13
|
+
end
|
14
|
+
|
15
|
+
Also supports JSON serialization, so you can do this:
|
16
|
+
|
17
|
+
p = Person.new(:name => 'McLovin').to_json # => {"name":"McLovin"}
|
18
|
+
|
19
|
+
Plans For Future
|
20
|
+
----------------
|
21
|
+
1. Serialization in any format: to_xml, to_yaml methods (and of course from_xml, from_yaml methods as well)
|
22
|
+
2. Callbacks: Define any callback for any event (This is stolen from ActiveRecord)
|
23
|
+
3. More Sexiness and Awesomeness
|
data/Rakefile
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
|
4
|
+
spec = Gem::Specification.new do |s|
|
5
|
+
s.platform = Gem::Platform::RUBY
|
6
|
+
s.summary = "Sexy Semantics for Any Ruby Model"
|
7
|
+
s.name = 'supermodel'
|
8
|
+
s.author = 'Arun Thampi'
|
9
|
+
s.email = "arun.thampi@gmail.com"
|
10
|
+
s.homepage = "http://www.github.com/arunthampi/supermodel"
|
11
|
+
s.version = '0.1.0'
|
12
|
+
s.files = FileList[ '[A-Z]*', 'lib/**/*.rb', 'spec/**/*.rb' ],
|
13
|
+
s.has_rdoc = true
|
14
|
+
s.require_path = "lib"
|
15
|
+
s.extra_rdoc_files = ["README"]
|
16
|
+
s.add_dependency 'json', '>=1.1.2'
|
17
|
+
end
|
18
|
+
|
19
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
20
|
+
pkg.need_zip = true
|
21
|
+
pkg.need_tar = true
|
22
|
+
end
|
23
|
+
|
24
|
+
task :lines do
|
25
|
+
lines, codelines, total_lines, total_codelines = 0, 0, 0, 0
|
26
|
+
|
27
|
+
for file_name in FileList["lib/supermodel/**/*.rb"]
|
28
|
+
next if file_name =~ /vendor/
|
29
|
+
f = File.open(file_name)
|
30
|
+
|
31
|
+
while line = f.gets
|
32
|
+
lines += 1
|
33
|
+
next if line =~ /^\s*$/
|
34
|
+
next if line =~ /^\s*#/
|
35
|
+
codelines += 1
|
36
|
+
end
|
37
|
+
puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}"
|
38
|
+
|
39
|
+
total_lines += lines
|
40
|
+
total_codelines += codelines
|
41
|
+
|
42
|
+
lines, codelines = 0, 0
|
43
|
+
end
|
44
|
+
|
45
|
+
puts "Total: Lines #{total_lines}, LOC #{total_codelines}"
|
46
|
+
end
|
47
|
+
|
48
|
+
task :default => [:package]
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/lib/supermodel.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
require 'supermodel/support'
|
8
|
+
require 'supermodel/errors'
|
9
|
+
require 'supermodel/base'
|
@@ -0,0 +1,284 @@
|
|
1
|
+
module SuperModel
|
2
|
+
class Base
|
3
|
+
SPECIAL_MEMBERS = %w(attributes has_many_associations has_one_associations)
|
4
|
+
TYPES = { :text => "", :number => 0, :decimal => 0.0, :boolean => true }
|
5
|
+
TYPES.default = ""
|
6
|
+
|
7
|
+
# Initializes an SuperModel::Base object. The constructor accepts both a hash, as well as
|
8
|
+
# a block to initialize attributes
|
9
|
+
#
|
10
|
+
# Examples:
|
11
|
+
# class Person < SuperModel::Base
|
12
|
+
# has :name
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# person1 = Person.new(:name => "McLovin")
|
16
|
+
# person1.name # => "McLovin"
|
17
|
+
#
|
18
|
+
# person2 = Person.new do |p|
|
19
|
+
# p.name = "Seth"
|
20
|
+
# end
|
21
|
+
# person2.name # => "Seth"
|
22
|
+
def initialize(params = {})
|
23
|
+
# Object instance variable
|
24
|
+
@attributes, @has_many_associations = {}, @has_one_associations = {}
|
25
|
+
# Initialize local variables from class instance variables
|
26
|
+
klass_atts = self.class.attributes
|
27
|
+
klass_has_many_assocs = self.class.has_many_associations
|
28
|
+
klass_has_one_assocs = self.class.has_one_associations
|
29
|
+
# Define getter methods for special members
|
30
|
+
SPECIAL_MEMBERS.each do |k|
|
31
|
+
self.instance_eval "def #{k}; @#{k}; end"
|
32
|
+
end
|
33
|
+
# First, initialize all the attributes
|
34
|
+
klass_atts.each_key do |property|
|
35
|
+
@attributes[property] = klass_atts[property]
|
36
|
+
self.instance_eval "def #{property}; attributes[:#{property}]; end"
|
37
|
+
self.instance_eval "def #{property}=(val); attributes[:#{property}] = val; end"
|
38
|
+
end
|
39
|
+
# Then, initialize all the has-many associations
|
40
|
+
klass_has_many_assocs.each do |k,v|
|
41
|
+
@has_many_associations[k] = v
|
42
|
+
self.instance_eval "def #{k}; @#{k} ||= []; end"
|
43
|
+
# If you have has_many :people, this will add a method called add_person
|
44
|
+
# to the object instantiated from the class
|
45
|
+
self.instance_eval "def add_#{k.singularize}(val); @#{k} = #{k} << val; end"
|
46
|
+
end
|
47
|
+
|
48
|
+
# Then, initialize all the has-many associations
|
49
|
+
klass_has_one_assocs.each do |k,v|
|
50
|
+
@has_one_associations[k] = v
|
51
|
+
self.instance_eval "def #{k}; @#{k}; end"
|
52
|
+
# If you have has_many :people, this will add a method called add_person
|
53
|
+
# to the object instantiated from the class
|
54
|
+
self.instance_eval "def #{k.singularize}=(val); @#{k} = val; end"
|
55
|
+
end
|
56
|
+
|
57
|
+
# Set any instance variables if any, which are present in the params hash
|
58
|
+
from_hash(params)
|
59
|
+
# Handle the block, which can also be used to initialize the object
|
60
|
+
yield self if block_given?
|
61
|
+
end
|
62
|
+
|
63
|
+
# Generates a JSON representation of an instance of a subclass of SuperModel::Base.
|
64
|
+
# Ignores attributes which have a nil value.
|
65
|
+
#
|
66
|
+
# Examples:
|
67
|
+
# class Person < SuperModel::Base
|
68
|
+
# has :name, :which_is => :text, :with_default_value => "McLovin"
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# person = Person.new
|
72
|
+
# person.to_json # {"name":"McLovin"}
|
73
|
+
#
|
74
|
+
# class AgedPerson < SuperModel::Base
|
75
|
+
# has :age, :which_is => :decimal, :with_default_value => 3.5
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
# aged_person = AgedPerson.new
|
79
|
+
# aged_person.id = 'abc-def'
|
80
|
+
# aged_person.to_json # {"age":3.5, "_id":"abc-def"}
|
81
|
+
def to_json
|
82
|
+
hash = {}
|
83
|
+
# First merge the attributes...
|
84
|
+
hash.merge!(attributes.reject{ |k,v| v.nil? })
|
85
|
+
# ...and then the associations, first has_many...
|
86
|
+
has_many_associations.each_key { |name| hash.merge!({ name => self.__send__(name.to_s) }) }
|
87
|
+
# ...and then has_one
|
88
|
+
has_one_associations.each_key { |name| hash.merge!({ name => self.__send__(name.to_s) }) }
|
89
|
+
# and by the Power of Grayskull, convert the hash to json
|
90
|
+
hash.to_json
|
91
|
+
end
|
92
|
+
|
93
|
+
def marshal_dump # :nodoc:
|
94
|
+
# Deflate using Zlib
|
95
|
+
self.to_json
|
96
|
+
end
|
97
|
+
|
98
|
+
def marshal_load(str) # :nodoc:
|
99
|
+
self.instance_eval do
|
100
|
+
# Inflate first, and then parse the JSON
|
101
|
+
hash = JSON.parse(str)
|
102
|
+
initialize(hash)
|
103
|
+
end
|
104
|
+
self
|
105
|
+
end
|
106
|
+
|
107
|
+
class << self # Class methods
|
108
|
+
# Defines an attribute for a subclass of SuperModel::Base. The parameters
|
109
|
+
# for this method include name, which is the name of the attribute as well as
|
110
|
+
# an options hash.
|
111
|
+
#
|
112
|
+
# The options hash can contain the key 'which_is' which can
|
113
|
+
# have possible values :text, :decimal, :number. It can also contain the key
|
114
|
+
# 'with_default_value' which can set a default value for each attribute defined
|
115
|
+
# in the subclass of SuperModel::Base
|
116
|
+
#
|
117
|
+
# Examples:
|
118
|
+
# class Person < SuperModel::Base
|
119
|
+
# has :name
|
120
|
+
# end
|
121
|
+
#
|
122
|
+
# person = Person.new
|
123
|
+
# p.name.methods.include?(:name) # true
|
124
|
+
# p.name.methods.include?(:name=) # false
|
125
|
+
#
|
126
|
+
# class AgedPerson < SuperModel::Base
|
127
|
+
# has :age, :which_is => :number, :with_default_value = 18
|
128
|
+
# end
|
129
|
+
#
|
130
|
+
# person = AgedPerson.new
|
131
|
+
# person.age # 18
|
132
|
+
def has(name, options = {})
|
133
|
+
unless name.is_a?(String) || name.is_a?(Symbol)
|
134
|
+
raise ArgumentError, "#{name} is neither a String nor a Symbol"
|
135
|
+
end
|
136
|
+
# Set the attributes value to options[:with_default_value]
|
137
|
+
# In the constructor, this will be used to initialize the value of
|
138
|
+
# the 'name' instance variable to the value in the hash
|
139
|
+
@attributes[name] = options[:with_default_value] || TYPES[:which_is]
|
140
|
+
end
|
141
|
+
|
142
|
+
# Defines an array of objects which are 'children' of this class. The has_many
|
143
|
+
# function guesses the class of the child, based on the name of the association,
|
144
|
+
# but can be over-ridden by the :class key in the options hash.
|
145
|
+
#
|
146
|
+
# Examples:
|
147
|
+
#
|
148
|
+
# class Person < SuperModel::Base
|
149
|
+
# has :name
|
150
|
+
# end
|
151
|
+
#
|
152
|
+
# class GrandPerson < SuperModel::Base
|
153
|
+
# has_many :people # which will create an empty array which can contain
|
154
|
+
# # Person objects
|
155
|
+
# end
|
156
|
+
def has_many(name, options = {})
|
157
|
+
unless name.is_a?(String) || name.is_a?(Symbol)
|
158
|
+
raise ArgumentError, "#{name} is neither a String nor a Symbol"
|
159
|
+
end
|
160
|
+
|
161
|
+
@has_many_associations[name] = get_klass(name, options)
|
162
|
+
end
|
163
|
+
|
164
|
+
# Defines a single object which is a 'child' of this class. The has_one
|
165
|
+
# function guesses the class of the child, based on the name of the association,
|
166
|
+
# but can be over-ridden by the :class key in the options hash.
|
167
|
+
#
|
168
|
+
# Examples:
|
169
|
+
#
|
170
|
+
# class Child < SuperModel::Base
|
171
|
+
# has :name
|
172
|
+
# end
|
173
|
+
#
|
174
|
+
# class GrandParent < SuperModel::Base
|
175
|
+
# has_one :child
|
176
|
+
# end
|
177
|
+
def has_one(name, options = {})
|
178
|
+
unless name.is_a?(String) || name.is_a?(Symbol)
|
179
|
+
raise ArgumentError, "#{name} is neither a String nor a Symbol"
|
180
|
+
end
|
181
|
+
|
182
|
+
@has_one_associations[name] = get_klass(name, options)
|
183
|
+
end
|
184
|
+
|
185
|
+
# Initializes an object of a subclass of SuperModel::Base based on a JSON
|
186
|
+
# representation of the object.
|
187
|
+
#
|
188
|
+
# Example:
|
189
|
+
# class Person < SuperModel::Base
|
190
|
+
# has :name
|
191
|
+
# end
|
192
|
+
#
|
193
|
+
# person = Person.from_json('{"name":"McLovin"}')
|
194
|
+
# person.name # "McLovin"
|
195
|
+
def from_json(json)
|
196
|
+
hash = JSON.parse(json)
|
197
|
+
# Create new based on parsed
|
198
|
+
self.new(hash)
|
199
|
+
end
|
200
|
+
|
201
|
+
# Defines an "attribute" method. A new (class) method will be created with the
|
202
|
+
# given name. If a value is specified, the new method will
|
203
|
+
# return that value (as a string). Otherwise, the given block
|
204
|
+
# will be used to compute the value of the method.
|
205
|
+
#
|
206
|
+
# The original method, if it exists, will be aliased, with the
|
207
|
+
# new name being
|
208
|
+
# prefixed with "original_". This allows the new method to
|
209
|
+
# access the original value.
|
210
|
+
#
|
211
|
+
# This method is stolen from ActiveRecord.
|
212
|
+
#
|
213
|
+
# Example:
|
214
|
+
#
|
215
|
+
# class Foo < SuperModel::Base
|
216
|
+
# define_attr_method :database_name, 'foo'
|
217
|
+
# # OR
|
218
|
+
# define_attr_method(:database_name) do
|
219
|
+
# original_database_name + '_legacy'
|
220
|
+
# end
|
221
|
+
# end
|
222
|
+
def define_attr_method(name, value = nil, &block)
|
223
|
+
metaclass.send(:alias_method, "original_#{name}", name)
|
224
|
+
if block_given?
|
225
|
+
meta_def name, &block
|
226
|
+
else
|
227
|
+
metaclass.class_eval "def #{name}; #{value.to_s.inspect}; end"
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def inherited(subklass)
|
232
|
+
subklass.instance_eval do
|
233
|
+
@attributes, @has_many_associations, @has_one_associations = {}, {}, {}
|
234
|
+
end
|
235
|
+
|
236
|
+
SPECIAL_MEMBERS.each do |k|
|
237
|
+
subklass.instance_eval "def #{k}; @#{k}; end"
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def base_class
|
242
|
+
class_of_supermodel_descendant(self)
|
243
|
+
end
|
244
|
+
|
245
|
+
private
|
246
|
+
# Generate a class from a name
|
247
|
+
def get_klass(name, options)
|
248
|
+
klass = options[:class]
|
249
|
+
!klass.nil? && klass.is_a?(Class) ? klass : name.to_s.classify.constantize
|
250
|
+
end
|
251
|
+
|
252
|
+
# Returns the class descending directly from SuperModel in the inheritance hierarchy.
|
253
|
+
def class_of_supermodel_descendant(klass)
|
254
|
+
if klass.superclass == Base
|
255
|
+
klass
|
256
|
+
elsif klass.superclass.nil?
|
257
|
+
raise SuperModelError, "#{name} doesn't belong in a hierarchy descending from SuperModel"
|
258
|
+
else
|
259
|
+
class_of_supermodel_descendant(klass.superclass)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end # End class methods
|
263
|
+
|
264
|
+
private
|
265
|
+
def from_hash(hash)
|
266
|
+
hash.each do |property, value|
|
267
|
+
property = property.to_sym rescue property
|
268
|
+
# This means a has_many association
|
269
|
+
if value.is_a?(Array) && !(child_klass = @has_many_associations[property]).nil?
|
270
|
+
value.each do |child|
|
271
|
+
child.is_a?(Hash) ? child_obj = child_klass.new(child) : child_obj = child
|
272
|
+
self.send "add_#{property.to_s.singularize}", child_obj
|
273
|
+
end
|
274
|
+
# This means a has_one association
|
275
|
+
elsif value.is_a?(Hash) && !(child_klass = @has_one_associations[property]).nil?
|
276
|
+
self.send "#{property.to_s.singularize}=", child_klass.new(value)
|
277
|
+
# This means this is a normal attribute
|
278
|
+
else
|
279
|
+
self.send("#{property}=", value) if respond_to?("#{property}=")
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end # End class Base
|
284
|
+
end # End module SuperModel
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module SuperModel
|
2
|
+
|
3
|
+
Symbol.class_eval do
|
4
|
+
def singularize; Inflector.singularize(self); end
|
5
|
+
end
|
6
|
+
|
7
|
+
String.class_eval do
|
8
|
+
require 'cgi'
|
9
|
+
def url_encode; CGI.escape("\"#{self.to_s}\""); end
|
10
|
+
# Delegate to Inflector
|
11
|
+
def singularize; Inflector.singularize(self); end
|
12
|
+
def demodulize; Inflector.demodulize(self); end
|
13
|
+
def pluralize; Inflector.pluralize(self); end
|
14
|
+
def underscore; Inflector.underscore(self); end
|
15
|
+
def classify; Inflector.classify(self); end
|
16
|
+
def constantize; Inflector.constantize(self); end
|
17
|
+
end
|
18
|
+
|
19
|
+
Hash.class_eval do
|
20
|
+
# Flatten on the array removes everything into *one* single array,
|
21
|
+
# so {}.to_a.flatten sometimes won't work nicely because a value might be an array
|
22
|
+
# So..introducing flatten for Hash, so that arrays which are values (to keys)
|
23
|
+
# are retained
|
24
|
+
def flatten
|
25
|
+
(0...self.size).inject([]) {|k,v| k << self.keys[v]; k << self.values[v]}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
Object.class_eval do
|
30
|
+
def get_class(name)
|
31
|
+
# From 'The Ruby Way Second Edition' by Hal Fulton
|
32
|
+
# This is to get nested class for e.g. A::B::C
|
33
|
+
name.split("::").inject(Object) {|x,y| x.const_get(y)}
|
34
|
+
end
|
35
|
+
|
36
|
+
# The singleton class.
|
37
|
+
def metaclass; class << self; self; end; end
|
38
|
+
def meta_eval &blk; metaclass.instance_eval &blk; end
|
39
|
+
|
40
|
+
# Adds methods to a metaclass.
|
41
|
+
def meta_def name, &blk
|
42
|
+
meta_eval { define_method name, &blk }
|
43
|
+
end
|
44
|
+
|
45
|
+
# Defines an instance method within a class.
|
46
|
+
def class_def name, &blk
|
47
|
+
class_eval { define_method name, &blk }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
Module.module_eval do
|
52
|
+
# Return the module which contains this one; if this is a root module, such as
|
53
|
+
# +::MyModule+, then Object is returned.
|
54
|
+
def parent
|
55
|
+
parent_name = name.split('::')[0..-2] * '::'
|
56
|
+
parent_name.empty? ? Object : Inflector.constantize(parent_name)
|
57
|
+
end
|
58
|
+
|
59
|
+
def alias_method_chain(target, feature)
|
60
|
+
# Strip out punctuation on predicates or bang methods since
|
61
|
+
# e.g. target?_without_feature is not a valid method name.
|
62
|
+
aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
|
63
|
+
yield(aliased_target, punctuation) if block_given?
|
64
|
+
|
65
|
+
with_method, without_method = "#{aliased_target}_with_#{feature}#{punctuation}", "#{aliased_target}_without_#{feature}#{punctuation}"
|
66
|
+
|
67
|
+
alias_method without_method, target
|
68
|
+
alias_method target, with_method
|
69
|
+
|
70
|
+
case
|
71
|
+
when public_method_defined?(without_method)
|
72
|
+
public target
|
73
|
+
when protected_method_defined?(without_method)
|
74
|
+
protected target
|
75
|
+
when private_method_defined?(without_method)
|
76
|
+
private target
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|