metaruby 1.0.0.rc1
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 +7 -0
- data/.gemtest +0 -0
- data/History.txt +1 -0
- data/Manifest.txt +40 -0
- data/README.md +318 -0
- data/Rakefile +39 -0
- data/lib/metaruby/class.rb +120 -0
- data/lib/metaruby/dsls/doc.rb +78 -0
- data/lib/metaruby/dsls/find_through_method_missing.rb +76 -0
- data/lib/metaruby/dsls.rb +2 -0
- data/lib/metaruby/gui/exception_view.rb +124 -0
- data/lib/metaruby/gui/html/button.rb +65 -0
- data/lib/metaruby/gui/html/collection.rb +103 -0
- data/lib/metaruby/gui/html/exception_view.css +8 -0
- data/lib/metaruby/gui/html/jquery.min.js +154 -0
- data/lib/metaruby/gui/html/jquery.selectfilter.js +65 -0
- data/lib/metaruby/gui/html/jquery.tagcloud.min.js +8 -0
- data/lib/metaruby/gui/html/jquery.tinysort.min.js +8 -0
- data/lib/metaruby/gui/html/list.rhtml +24 -0
- data/lib/metaruby/gui/html/page.css +55 -0
- data/lib/metaruby/gui/html/page.rb +283 -0
- data/lib/metaruby/gui/html/page.rhtml +13 -0
- data/lib/metaruby/gui/html/page_body.rhtml +17 -0
- data/lib/metaruby/gui/html/rock-website.css +694 -0
- data/lib/metaruby/gui/html.rb +16 -0
- data/lib/metaruby/gui/model_browser.rb +262 -0
- data/lib/metaruby/gui/model_selector.rb +266 -0
- data/lib/metaruby/gui/rendering_manager.rb +112 -0
- data/lib/metaruby/gui/ruby_constants_item_model.rb +253 -0
- data/lib/metaruby/gui.rb +9 -0
- data/lib/metaruby/inherited_attribute.rb +482 -0
- data/lib/metaruby/module.rb +158 -0
- data/lib/metaruby/registration.rb +157 -0
- data/lib/metaruby/test.rb +79 -0
- data/lib/metaruby.rb +17 -0
- data/manifest.xml +12 -0
- data/test/suite.rb +15 -0
- data/test/test_attributes.rb +323 -0
- data/test/test_class.rb +68 -0
- data/test/test_dsls.rb +49 -0
- data/test/test_module.rb +105 -0
- data/test/test_registration.rb +182 -0
- metadata +160 -0
@@ -0,0 +1,482 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'utilrb/module/dsl_attribute'
|
3
|
+
module MetaRuby
|
4
|
+
module Attributes
|
5
|
+
InheritedAttribute = Struct.new :single_value, :name, :accessor_name, :init
|
6
|
+
|
7
|
+
# The set of inherited attributes defined on this object
|
8
|
+
# @return [Array<InheritedAttribute>]
|
9
|
+
attribute(:inherited_attributes) { Array.new }
|
10
|
+
|
11
|
+
# Tests for the existence of an inherited attribute by its name
|
12
|
+
#
|
13
|
+
# @param [String] name the attribute name
|
14
|
+
# @return [Boolean] true if there is an attribute defined with the given
|
15
|
+
# name
|
16
|
+
def inherited_attribute_defined?(name)
|
17
|
+
inherited_attributes.any? { |ih| ih.name == name }
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns the inherited attribute definition that matches the given name
|
21
|
+
#
|
22
|
+
# @param [String] name the attribute name
|
23
|
+
# @return [InheritedAttribute] the attribute definition
|
24
|
+
# @raise [ArgumentError] if no attribute with that name exists
|
25
|
+
def inherited_attribute_by_name(name)
|
26
|
+
if attr = inherited_attributes.find { |ih| ih.name == name }
|
27
|
+
return attr
|
28
|
+
else raise ArgumentError, "#{self} has no inherited attribute called #{name}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def included(mod)
|
33
|
+
mod.extend Attributes
|
34
|
+
end
|
35
|
+
|
36
|
+
# Defines an attribute that holds at most a single value
|
37
|
+
#
|
38
|
+
# @param [String] name the attribute name
|
39
|
+
# @return [InheritedAttribute] the attribute definition
|
40
|
+
# @raise [ArgumentError] if no attribute with that name exists
|
41
|
+
def inherited_single_value_attribute(name, &default_value)
|
42
|
+
dsl_attribute_name = "__dsl_attribute__#{name}"
|
43
|
+
ivar = "@#{dsl_attribute_name}"
|
44
|
+
dsl_attribute(dsl_attribute_name)
|
45
|
+
if default_value
|
46
|
+
define_method("#{dsl_attribute_name}_get_default") { default_value }
|
47
|
+
end
|
48
|
+
|
49
|
+
promotion_method = "promote_#{name}"
|
50
|
+
if method_defined?(promotion_method)
|
51
|
+
define_single_value_with_promotion("#{dsl_attribute_name}_get", promotion_method, ivar)
|
52
|
+
else
|
53
|
+
define_single_value_without_promotion("#{dsl_attribute_name}_get", ivar)
|
54
|
+
end
|
55
|
+
define_method(name) do |*args|
|
56
|
+
if args.empty? # Getter call
|
57
|
+
send("#{dsl_attribute_name}_get")
|
58
|
+
else # Setter call, delegate to the dsl_attribute implementation
|
59
|
+
send(dsl_attribute_name, *args)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
nil
|
63
|
+
end
|
64
|
+
|
65
|
+
# Helper method for {#inherited_single_value_attribute} in case there
|
66
|
+
# are no promotion method(s) defined
|
67
|
+
def define_single_value_without_promotion(method_name, ivar)
|
68
|
+
class_eval <<-EOF, __FILE__, __LINE__+1
|
69
|
+
def #{method_name}
|
70
|
+
ancestors = self.ancestors
|
71
|
+
if ancestors.first != self
|
72
|
+
ancestors.unshift self
|
73
|
+
end
|
74
|
+
|
75
|
+
has_value = false
|
76
|
+
for klass in ancestors
|
77
|
+
if klass.instance_variable_defined?(:#{ivar})
|
78
|
+
has_value = true
|
79
|
+
value = klass.instance_variable_get(:#{ivar})
|
80
|
+
break
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
if !has_value && respond_to?(:#{method_name}_default)
|
85
|
+
# Look for default
|
86
|
+
has_value = true
|
87
|
+
value = send(:#{method_name}_default).call
|
88
|
+
base = nil
|
89
|
+
for klass in ancestors
|
90
|
+
if !klass.respond_to?(:#{method_name}_default)
|
91
|
+
break
|
92
|
+
end
|
93
|
+
base = klass
|
94
|
+
end
|
95
|
+
base.instance_variable_set :#{ivar}, value
|
96
|
+
end
|
97
|
+
value
|
98
|
+
end
|
99
|
+
EOF
|
100
|
+
end
|
101
|
+
|
102
|
+
# Helper method for {#inherited_single_value_attribute} in case there is
|
103
|
+
# a promotion method defined
|
104
|
+
def define_single_value_with_promotion(method_name, promotion_method_name, ivar)
|
105
|
+
class_eval <<-EOF, __FILE__, __LINE__+1
|
106
|
+
def #{method_name}
|
107
|
+
ancestors = self.ancestors
|
108
|
+
if ancestors.first != self
|
109
|
+
ancestors.unshift self
|
110
|
+
end
|
111
|
+
|
112
|
+
promotions = []
|
113
|
+
for klass in ancestors
|
114
|
+
if klass.instance_variable_defined?(:#{ivar})
|
115
|
+
has_value = true
|
116
|
+
value = klass.instance_variable_get(:#{ivar})
|
117
|
+
break
|
118
|
+
end
|
119
|
+
promotions.unshift(klass) if klass.respond_to?("#{promotion_method_name}")
|
120
|
+
end
|
121
|
+
if !has_value && respond_to?(:#{method_name}_default)
|
122
|
+
# Look for default
|
123
|
+
has_value = true
|
124
|
+
value = send(:#{method_name}_default).call
|
125
|
+
base = nil
|
126
|
+
promotions.clear
|
127
|
+
for klass in ancestors
|
128
|
+
if !klass.respond_to?(:#{method_name}_default)
|
129
|
+
break
|
130
|
+
end
|
131
|
+
base = klass
|
132
|
+
promotions.unshift(klass) if klass.respond_to?(:#{promotion_method_name})
|
133
|
+
end
|
134
|
+
promotions.shift
|
135
|
+
base.instance_variable_set :#{ivar}, value
|
136
|
+
end
|
137
|
+
|
138
|
+
if has_value
|
139
|
+
promotions.inject(value) { |v, k| k.#{promotion_method_name}(v) }
|
140
|
+
end
|
141
|
+
end
|
142
|
+
EOF
|
143
|
+
end
|
144
|
+
|
145
|
+
# Defines an attribute that holds a set of values, and defines the
|
146
|
+
# relevant methods and accessors to allow accessing it in a way that
|
147
|
+
# makes sense when embedded in a model hierarchy
|
148
|
+
#
|
149
|
+
# More specifically, it defines a <tt>each_#{name}(&iterator)</tt>
|
150
|
+
# instance method and a <tt>each_#{name}(&iterator)</tt>
|
151
|
+
# class method which iterates (in order) on
|
152
|
+
# - the instance #{name} attribute
|
153
|
+
# - the singleton class #{name} attribute
|
154
|
+
# - the class #{name} attribute
|
155
|
+
# - the superclass #{name} attribute
|
156
|
+
# - the superclass' superclass #{name} attribute
|
157
|
+
# ...
|
158
|
+
#
|
159
|
+
# This method can be used on modules, in which case the module is used as if
|
160
|
+
# it was part of the inheritance hierarchy.
|
161
|
+
#
|
162
|
+
# The +name+ option defines the enumeration method name (+value+ will
|
163
|
+
# define a +each_value+ method). +attribute_name+ defines the attribute
|
164
|
+
# name. +init+ is a block called to initialize the attribute.
|
165
|
+
# Valid options in +options+ are:
|
166
|
+
# map::
|
167
|
+
# If true, the attribute should respond to +[]+. In that case, the
|
168
|
+
# enumeration method is each_value(key = nil, uniq = false) If +key+ is
|
169
|
+
# given, we iterate on the values given by <tt>attribute[key]</tt>. If
|
170
|
+
# +uniq+ is true, the enumeration will yield at most one value for each
|
171
|
+
# +key+ found (so, if both +key+ and +uniq+ are given, the enumeration
|
172
|
+
# yields at most one value). See the examples below
|
173
|
+
# enum_with:: the enumeration method of the enumerable, if it is not +each+
|
174
|
+
#
|
175
|
+
# === Example
|
176
|
+
# Let's define some classes and look at the ancestor chain
|
177
|
+
#
|
178
|
+
# class A; end
|
179
|
+
# module M; end
|
180
|
+
# class B < A; include M end
|
181
|
+
# A.ancestors # => [A, Object, Kernel]
|
182
|
+
# B.ancestors # => [B, M, A, Object, Kernel]
|
183
|
+
#
|
184
|
+
# ==== Attributes for which 'map' is not set
|
185
|
+
#
|
186
|
+
# class A
|
187
|
+
# class << self
|
188
|
+
# inherited_attribute("value", "values") do
|
189
|
+
# Array.new
|
190
|
+
# end
|
191
|
+
# end
|
192
|
+
# end
|
193
|
+
# module M
|
194
|
+
# class << self
|
195
|
+
# extend MetaRuby::Attributes
|
196
|
+
# inherited_attribute("mod") do
|
197
|
+
# Array.new
|
198
|
+
# end
|
199
|
+
# end
|
200
|
+
# end
|
201
|
+
#
|
202
|
+
# A.values << 1 # => [1]
|
203
|
+
# B.values << 2 # => [2]
|
204
|
+
# M.mod << 1 # => [1]
|
205
|
+
# b = B.new
|
206
|
+
# class << b
|
207
|
+
# self.values << 3 # => [3]
|
208
|
+
# self.mod << 4 # => [4]
|
209
|
+
# end
|
210
|
+
# M.mod << 2 # => [1, 2]
|
211
|
+
#
|
212
|
+
# A.enum_for(:each_value).to_a # => [1]
|
213
|
+
# B.enum_for(:each_value).to_a # => [2, 1]
|
214
|
+
# b.singleton_class.enum_for(:each_value).to_a # => [3, 2, 1]
|
215
|
+
# b.singleton_class.enum_for(:each_mod).to_a # => [4, 1, 2]
|
216
|
+
#
|
217
|
+
# ==== Attributes for which 'map' is set
|
218
|
+
#
|
219
|
+
# class A
|
220
|
+
# class << self
|
221
|
+
# inherited_attribute("mapped", "map", :map => true) do
|
222
|
+
# Hash.new { |h, k| h[k] = Array.new }
|
223
|
+
# end
|
224
|
+
# end
|
225
|
+
# end
|
226
|
+
#
|
227
|
+
# A.map['name'] = 'A' # => "A"
|
228
|
+
# A.map['universe'] = 42
|
229
|
+
# B.map['name'] = 'B' # => "B"
|
230
|
+
# B.map['half_of_it'] = 21
|
231
|
+
#
|
232
|
+
# Let's see what happens if we don't specify the key option.
|
233
|
+
# A.enum_for(:each_mapped).to_a # => [["name", "A"], ["universe", 42]]
|
234
|
+
# If the +uniq+ option is set (the default), we see only B's value for 'name'
|
235
|
+
# B.enum_for(:each_mapped).to_a # => [["half_of_it", 21], ["name", "B"], ["universe", 42]]
|
236
|
+
# If the +uniq+ option is not set, we see both values for 'name'. Note that
|
237
|
+
# since 'map' is a Hash, the order of keys in one class is not guaranteed.
|
238
|
+
# Nonetheless, we have the guarantee that values from B appear before
|
239
|
+
# those from A
|
240
|
+
# B.enum_for(:each_mapped, nil, false).to_a # => [["half_of_it", 21], ["name", "B"], ["name", "A"], ["universe", 42]]
|
241
|
+
#
|
242
|
+
#
|
243
|
+
# Now, let's see how 'key' behaves
|
244
|
+
# A.enum_for(:each_mapped, 'name').to_a # => ["A"]
|
245
|
+
# B.enum_for(:each_mapped, 'name').to_a # => ["B"]
|
246
|
+
# B.enum_for(:each_mapped, 'name', false).to_a # => ["B", "A"]
|
247
|
+
#
|
248
|
+
def inherited_attribute(name, attribute_name = name, options = Hash.new, &init) # :nodoc:
|
249
|
+
# Set up the attribute accessor
|
250
|
+
attribute(attribute_name, &init)
|
251
|
+
class_eval { private "#{attribute_name}=" }
|
252
|
+
|
253
|
+
promote = method_defined?("promote_#{name}")
|
254
|
+
options[:enum_with] ||= :each
|
255
|
+
|
256
|
+
class_eval <<-EOF, __FILE__, __LINE__+1
|
257
|
+
def all_#{name}; each_#{name}.to_a end
|
258
|
+
def self_#{name}; @#{attribute_name} end
|
259
|
+
EOF
|
260
|
+
|
261
|
+
if options[:map]
|
262
|
+
class_eval <<-EOF, __FILE__, __LINE__+1
|
263
|
+
def find_#{name}(key)
|
264
|
+
raise ArgumentError, "nil cannot be used as a key in find_#{name}" if !key
|
265
|
+
each_#{name}(key, true) do |value|
|
266
|
+
return value
|
267
|
+
end
|
268
|
+
nil
|
269
|
+
end
|
270
|
+
def has_#{name}?(key)
|
271
|
+
ancestors = self.ancestors
|
272
|
+
if ancestors.first != self
|
273
|
+
ancestors.unshift self
|
274
|
+
end
|
275
|
+
for klass in ancestors
|
276
|
+
if klass.instance_variable_defined?(:@#{attribute_name})
|
277
|
+
return true if klass.#{attribute_name}.has_key?(key)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
false
|
281
|
+
end
|
282
|
+
EOF
|
283
|
+
end
|
284
|
+
|
285
|
+
class_eval <<-EOF, __FILE__, __LINE__+1
|
286
|
+
def clear_#{attribute_name}
|
287
|
+
#{attribute_name}.clear
|
288
|
+
for klass in ancestors
|
289
|
+
if klass.instance_variable_defined?(:@#{attribute_name})
|
290
|
+
klass.#{attribute_name}.clear
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
EOF
|
295
|
+
|
296
|
+
if !promote
|
297
|
+
if options[:map]
|
298
|
+
class_eval(*Attributes.map_without_promotion(name, attribute_name, options))
|
299
|
+
else
|
300
|
+
class_eval(*Attributes.nomap_without_promotion(name, attribute_name, options))
|
301
|
+
end
|
302
|
+
else
|
303
|
+
if options[:map]
|
304
|
+
class_eval(*Attributes.map_with_promotion(name, attribute_name, options))
|
305
|
+
else
|
306
|
+
class_eval(*Attributes.nomap_with_promotion(name, attribute_name, options))
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
# Helper class that defines the iteration method for inherited_attribute
|
312
|
+
# when :map is set and there is not promotion method
|
313
|
+
def self.map_without_promotion(name, attribute_name, options)
|
314
|
+
code, file, line =<<-EOF, __FILE__, __LINE__+1
|
315
|
+
def each_#{name}(key = nil, uniq = true)
|
316
|
+
if !block_given?
|
317
|
+
return enum_for(:each_#{name}, key, uniq)
|
318
|
+
end
|
319
|
+
|
320
|
+
ancestors = self.ancestors
|
321
|
+
if ancestors.first != self
|
322
|
+
ancestors.unshift self
|
323
|
+
end
|
324
|
+
if key
|
325
|
+
for klass in ancestors
|
326
|
+
if klass.instance_variable_defined?(:@#{attribute_name})
|
327
|
+
if klass.#{attribute_name}.has_key?(key)
|
328
|
+
yield(klass.#{attribute_name}[key])
|
329
|
+
return self if uniq
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
333
|
+
elsif !uniq
|
334
|
+
for klass in ancestors
|
335
|
+
if klass.instance_variable_defined?(:@#{attribute_name})
|
336
|
+
klass.#{attribute_name}.#{options[:enum_with]} do |el|
|
337
|
+
yield(el)
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
341
|
+
else
|
342
|
+
seen = Set.new
|
343
|
+
for klass in ancestors
|
344
|
+
if klass.instance_variable_defined?(:@#{attribute_name})
|
345
|
+
klass.#{attribute_name}.#{options[:enum_with]} do |el|
|
346
|
+
unless seen.include?(el.first)
|
347
|
+
seen << el.first
|
348
|
+
yield(el)
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
end
|
355
|
+
self
|
356
|
+
end
|
357
|
+
EOF
|
358
|
+
return code, file, line
|
359
|
+
end
|
360
|
+
|
361
|
+
# Helper class that defines the iteration method for inherited_attribute
|
362
|
+
# when :map is not set and there is no promotion method
|
363
|
+
def self.nomap_without_promotion(name, attribute_name, options)
|
364
|
+
code, file, line =<<-EOF, __FILE__, __LINE__+1
|
365
|
+
def each_#{name}
|
366
|
+
if !block_given?
|
367
|
+
return enum_for(:each_#{name})
|
368
|
+
end
|
369
|
+
|
370
|
+
ancestors = self.ancestors
|
371
|
+
if ancestors.first != self
|
372
|
+
ancestors.unshift self
|
373
|
+
end
|
374
|
+
for klass in ancestors
|
375
|
+
if klass.instance_variable_defined?(:@#{attribute_name})
|
376
|
+
klass.#{attribute_name}.#{options[:enum_with]} { |el| yield(el) }
|
377
|
+
end
|
378
|
+
end
|
379
|
+
self
|
380
|
+
end
|
381
|
+
EOF
|
382
|
+
return code, file, line
|
383
|
+
end
|
384
|
+
|
385
|
+
# Helper class that defines the iteration method for inherited_attribute
|
386
|
+
# when :map is set and there is a promotion method
|
387
|
+
def self.map_with_promotion(name, attribute_name, options)
|
388
|
+
code, file, line =<<-EOF, __FILE__, __LINE__+1
|
389
|
+
def each_#{name}(key = nil, uniq = true)
|
390
|
+
if !block_given?
|
391
|
+
return enum_for(:each_#{name}, key, uniq)
|
392
|
+
end
|
393
|
+
|
394
|
+
ancestors = self.ancestors
|
395
|
+
if ancestors.first != self
|
396
|
+
ancestors.unshift self
|
397
|
+
end
|
398
|
+
if key
|
399
|
+
promotions = []
|
400
|
+
for klass in ancestors
|
401
|
+
if klass.instance_variable_defined?(:@#{attribute_name})
|
402
|
+
if klass.#{attribute_name}.has_key?(key)
|
403
|
+
value = klass.#{attribute_name}[key]
|
404
|
+
for p in promotions
|
405
|
+
value = p.promote_#{name}(key, value)
|
406
|
+
end
|
407
|
+
yield(value)
|
408
|
+
return self if uniq
|
409
|
+
end
|
410
|
+
end
|
411
|
+
promotions.unshift(klass) if klass.respond_to?("promote_#{name}")
|
412
|
+
end
|
413
|
+
elsif !uniq
|
414
|
+
promotions = []
|
415
|
+
for klass in ancestors
|
416
|
+
if klass.instance_variable_defined?(:@#{attribute_name})
|
417
|
+
klass.#{attribute_name}.#{options[:enum_with]} do |k, v|
|
418
|
+
for p in promotions
|
419
|
+
v = p.promote_#{name}(k, v)
|
420
|
+
end
|
421
|
+
yield(k, v)
|
422
|
+
end
|
423
|
+
end
|
424
|
+
promotions.unshift(klass) if klass.respond_to?("promote_#{name}")
|
425
|
+
end
|
426
|
+
else
|
427
|
+
seen = Set.new
|
428
|
+
promotions = []
|
429
|
+
for klass in ancestors
|
430
|
+
if klass.instance_variable_defined?(:@#{attribute_name})
|
431
|
+
klass.#{attribute_name}.#{options[:enum_with]} do |k, v|
|
432
|
+
unless seen.include?(k)
|
433
|
+
for p in promotions
|
434
|
+
v = p.promote_#{name}(k, v)
|
435
|
+
end
|
436
|
+
seen << k
|
437
|
+
yield(k, v)
|
438
|
+
end
|
439
|
+
end
|
440
|
+
end
|
441
|
+
promotions.unshift(klass) if klass.respond_to?("promote_#{name}")
|
442
|
+
end
|
443
|
+
end
|
444
|
+
self
|
445
|
+
end
|
446
|
+
EOF
|
447
|
+
return code, file, line
|
448
|
+
end
|
449
|
+
|
450
|
+
# Helper class that defines the iteration method for inherited_attribute
|
451
|
+
# when :map is not set and there is a promotion method
|
452
|
+
def self.nomap_with_promotion(name, attribute_name, options)
|
453
|
+
code, file, line =<<-EOF, __FILE__, __LINE__+1
|
454
|
+
def each_#{name}
|
455
|
+
if !block_given?
|
456
|
+
return enum_for(:each_#{name})
|
457
|
+
end
|
458
|
+
|
459
|
+
ancestors = self.ancestors
|
460
|
+
if ancestors.first != self
|
461
|
+
ancestors.unshift self
|
462
|
+
end
|
463
|
+
promotions = []
|
464
|
+
for klass in ancestors
|
465
|
+
if klass.instance_variable_defined?(:@#{attribute_name})
|
466
|
+
klass.#{attribute_name}.#{options[:enum_with]} do |value|
|
467
|
+
for p in promotions
|
468
|
+
value = p.promote_#{name}(value)
|
469
|
+
end
|
470
|
+
yield(value)
|
471
|
+
end
|
472
|
+
end
|
473
|
+
promotions.unshift(klass) if klass.respond_to?("promote_#{name}")
|
474
|
+
end
|
475
|
+
self
|
476
|
+
end
|
477
|
+
EOF
|
478
|
+
return code, file, line
|
479
|
+
end
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'utilrb/value_set'
|
2
|
+
require 'utilrb/module/const_defined_here_p'
|
3
|
+
module MetaRuby
|
4
|
+
# Extend in modules that are used as models
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# module MyBaseModel
|
8
|
+
# extend MetaRuby::ModelAsModule
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# Alternatively, one can create a module to describe the metamodel for our
|
12
|
+
# base model and then include it in the actual root model
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# module MyBaseMetamodel
|
16
|
+
# include MetaRuby::ModelAsModule
|
17
|
+
# end
|
18
|
+
# module MyBaseModel
|
19
|
+
# extend MyBaseMetamodel
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
module ModelAsModule
|
23
|
+
include Attributes
|
24
|
+
include Registration
|
25
|
+
extend Attributes
|
26
|
+
|
27
|
+
# @return [String] set or get the documentation text for this model
|
28
|
+
inherited_single_value_attribute :doc
|
29
|
+
|
30
|
+
def self.validate_constant_name(name)
|
31
|
+
if name !~ /^[A-Z]\w+$/
|
32
|
+
raise ArgumentError, "#{name} is not a valid model name"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Common method that can be used to create and register a
|
37
|
+
# submodel-as-a-module on a provided namespace
|
38
|
+
#
|
39
|
+
# It is usually used to create specific DSL-like methods that allow to
|
40
|
+
# create these models
|
41
|
+
def self.create_and_register_submodel(namespace, name, base_model, *args, &block)
|
42
|
+
ModelAsModule.validate_constant_name(name)
|
43
|
+
|
44
|
+
if namespace.const_defined_here?(name)
|
45
|
+
model = namespace.const_get(name)
|
46
|
+
base_model.setup_submodel(model, *args, &block)
|
47
|
+
else
|
48
|
+
namespace.const_set(name, model = base_model.new_submodel(*args, &block))
|
49
|
+
model.permanent_model = if !namespace.respond_to?(:permanent_model?)
|
50
|
+
Registration.accessible_by_name?(namespace)
|
51
|
+
else namespace.permanent_model?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
model
|
56
|
+
end
|
57
|
+
|
58
|
+
# The call trace at the point of definition. It is usually used to
|
59
|
+
# report to the user in which file this model got defined
|
60
|
+
#
|
61
|
+
# @return [Array[(String,Integer,Symbol)]] a list of (file,line,method)
|
62
|
+
# tuples as returned by #call_stack (from the facet gem)
|
63
|
+
attr_accessor :definition_location
|
64
|
+
|
65
|
+
# Set of models that this model provides
|
66
|
+
attribute(:parent_models) { Set.new }
|
67
|
+
|
68
|
+
# True if this model is a root model
|
69
|
+
attr_predicate :root?, true
|
70
|
+
|
71
|
+
# Sets a name on this model
|
72
|
+
#
|
73
|
+
# Only use this on 'anonymous models', i.e. on models that are not
|
74
|
+
# meant to be assigned on a Ruby constant
|
75
|
+
#
|
76
|
+
# @return [String] the assigned name
|
77
|
+
def name=(name)
|
78
|
+
def self.name
|
79
|
+
if @name then @name
|
80
|
+
else super
|
81
|
+
end
|
82
|
+
end
|
83
|
+
@name = name
|
84
|
+
end
|
85
|
+
|
86
|
+
# Set the root model. See {root_model}
|
87
|
+
attr_accessor :supermodel
|
88
|
+
|
89
|
+
# Creates a new DataServiceModel that is a submodel of +self+
|
90
|
+
#
|
91
|
+
# @param [Hash] options the option hash
|
92
|
+
# @option options [String] :name the submodel name. Use this option
|
93
|
+
# only for "anonymous" models, i.e. models that won't be
|
94
|
+
# registered on a Ruby constant
|
95
|
+
# @option options [Class] :type (self.class) the type of the submodel
|
96
|
+
#
|
97
|
+
def new_submodel(options = Hash.new, &block)
|
98
|
+
options, submodel_options = Kernel.filter_options options,
|
99
|
+
:name => nil, :type => self.class
|
100
|
+
|
101
|
+
model = options[:type].new
|
102
|
+
model.extend ModelAsModule
|
103
|
+
if options[:name]
|
104
|
+
model.name = options[:name].dup
|
105
|
+
end
|
106
|
+
model.definition_location = call_stack
|
107
|
+
setup_submodel(model, submodel_options, &block)
|
108
|
+
model
|
109
|
+
end
|
110
|
+
|
111
|
+
# Called when a new submodel has been created, on the newly created
|
112
|
+
# submodel
|
113
|
+
def setup_submodel(submodel, options = Hash.new, &block)
|
114
|
+
submodel.provides self
|
115
|
+
|
116
|
+
if block_given?
|
117
|
+
submodel.apply_block(&block)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# In the case of model-as-modules, we always deregister (regardless of
|
122
|
+
# the fact that +self+ is permanent or not). The reason for this is that
|
123
|
+
# the model-as-module hierarchy is much more dynamic than
|
124
|
+
# model-as-class. Who provides what can be changed after a #clear_model
|
125
|
+
# call.
|
126
|
+
def clear_model
|
127
|
+
super
|
128
|
+
if supermodel
|
129
|
+
supermodel.deregister_submodels([self])
|
130
|
+
end
|
131
|
+
@supermodel = nil
|
132
|
+
parent_models.clear
|
133
|
+
end
|
134
|
+
|
135
|
+
# Called to apply a model definition block on this model
|
136
|
+
#
|
137
|
+
# The definition class-eval's it
|
138
|
+
#
|
139
|
+
# @return [void]
|
140
|
+
def apply_block(&block)
|
141
|
+
class_eval(&block)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Declares that this model also provides this other given model
|
145
|
+
def provides(model)
|
146
|
+
include model
|
147
|
+
if model.root?
|
148
|
+
self.supermodel = model
|
149
|
+
else
|
150
|
+
self.supermodel = model.supermodel
|
151
|
+
end
|
152
|
+
self.supermodel.register_submodel(self)
|
153
|
+
self.parent_models |= model.parent_models
|
154
|
+
self.parent_models << model
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|