rgen 0.2.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.
- data/CHANGELOG +9 -0
- data/MIT-LICENSE +20 -0
- data/README +73 -0
- data/lib/ea/xmi_class_instantiator.rb +45 -0
- data/lib/ea/xmi_helper.rb +26 -0
- data/lib/ea/xmi_metamodel.rb +19 -0
- data/lib/ea/xmi_object_instantiator.rb +42 -0
- data/lib/ea/xmi_to_classmodel.rb +78 -0
- data/lib/ea/xmi_to_objectmodel.rb +89 -0
- data/lib/mmgen/metamodel_generator.rb +19 -0
- data/lib/mmgen/mm_ext/uml_classmodel_ext.rb +71 -0
- data/lib/mmgen/mmgen.rb +21 -0
- data/lib/mmgen/templates/uml_classmodel.tpl +63 -0
- data/lib/rgen/array_extensions.rb +23 -0
- data/lib/rgen/auto_class_creator.rb +56 -0
- data/lib/rgen/environment.rb +57 -0
- data/lib/rgen/metamodel_builder.rb +102 -0
- data/lib/rgen/metamodel_builder/build_helper.rb +29 -0
- data/lib/rgen/metamodel_builder/builder_extensions.rb +191 -0
- data/lib/rgen/metamodel_builder/builder_runtime.rb +67 -0
- data/lib/rgen/name_helper.rb +18 -0
- data/lib/rgen/template_language.rb +169 -0
- data/lib/rgen/template_language/directory_template_container.rb +51 -0
- data/lib/rgen/template_language/output_handler.rb +84 -0
- data/lib/rgen/template_language/template_container.rb +153 -0
- data/lib/rgen/template_language/template_helper.rb +26 -0
- data/lib/rgen/transformer.rb +316 -0
- data/lib/rgen/xml_instantiator/dependency_resolver.rb +23 -0
- data/lib/rgen/xml_instantiator/xml_instantiator.rb +78 -0
- data/lib/rgen/xml_instantiator/xml_parser.rb +39 -0
- data/lib/uml/objectmodel_instantiator.rb +53 -0
- data/lib/uml/uml_classmodel.rb +92 -0
- data/lib/uml/uml_objectmodel.rb +65 -0
- data/test/array_extensions_test.rb +54 -0
- data/test/environment_test.rb +47 -0
- data/test/metamodel_builder_test.rb +175 -0
- data/test/metamodel_generator_test.rb +45 -0
- data/test/metamodel_generator_test/TestModel.rb +40 -0
- data/test/metamodel_generator_test/expected_result.txt +40 -0
- data/test/output_handler_test.rb +40 -0
- data/test/rgen_test.rb +13 -0
- data/test/template_language_test.rb +46 -0
- data/test/template_language_test/expected_result.txt +10 -0
- data/test/template_language_test/templates/content/chapter.tpl +5 -0
- data/test/template_language_test/templates/index/c/cmod.tpl +1 -0
- data/test/template_language_test/templates/index/chapter.tpl +3 -0
- data/test/template_language_test/templates/root.tpl +22 -0
- data/test/template_language_test/testout.txt +10 -0
- data/test/transformer_test.rb +176 -0
- data/test/xmi_class_instantiator_test.rb +107 -0
- data/test/xmi_instantiator_test/testmodel.eap +0 -0
- data/test/xmi_instantiator_test/testmodel.xml +962 -0
- data/test/xmi_object_instantiator_test.rb +65 -0
- metadata +117 -0
data/lib/mmgen/mmgen.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'ea/xmi_instantiator'
|
2
|
+
require 'mmgen/metamodel_generator'
|
3
|
+
|
4
|
+
include XMIClassInstantiator
|
5
|
+
include MMGen::MetamodelGenerator
|
6
|
+
|
7
|
+
unless ARGV.length == 1
|
8
|
+
puts "Usage: mmgen.rb <xmi_class_model_file>"
|
9
|
+
exit
|
10
|
+
else
|
11
|
+
file_name = ARGV.shift
|
12
|
+
out_file = file_name.gsub(/\.\w+$/,'')
|
13
|
+
puts out_file
|
14
|
+
end
|
15
|
+
|
16
|
+
envUML = RGen::Environment.new
|
17
|
+
File.open(file_name) { |f|
|
18
|
+
instantiateUMLClassModel(envUML, f.read)
|
19
|
+
}
|
20
|
+
|
21
|
+
generateMetamodel(envUML, out_file)
|
@@ -0,0 +1,63 @@
|
|
1
|
+
|
2
|
+
<% define 'GenerateClassModel', :for => UMLPackage do |filename, modules| %>
|
3
|
+
<% file filename do %>
|
4
|
+
require 'rgen/metamodel_builder'
|
5
|
+
|
6
|
+
<% expand 'GeneratePackage', modules %>
|
7
|
+
<%nl%>
|
8
|
+
<% expand 'GenerateAssocs' %>
|
9
|
+
<% end %>
|
10
|
+
<% end %>
|
11
|
+
|
12
|
+
<% define 'GeneratePackage', :for => UMLPackage do |modules| %>
|
13
|
+
module <%= moduleName %><% iinc %>
|
14
|
+
<% for c in sortedClasses %><%nl%>
|
15
|
+
<% expand 'ModuleHeader', modules, :for => c %><%iinc%>
|
16
|
+
<% (c.superclasses.name & modules).each do |n| %>
|
17
|
+
include <%= n %>
|
18
|
+
<% end %>
|
19
|
+
<% if modules.include?(c.className) %>
|
20
|
+
extend RGen::MetamodelBuilder::BuilderExtensions
|
21
|
+
<% end %>
|
22
|
+
has_one 'name', String
|
23
|
+
<% for a in c.attributes %>
|
24
|
+
has_one '<%= a.name %>', <%= a.RubyType %>
|
25
|
+
<% end %><%idec%>
|
26
|
+
end
|
27
|
+
<% end %><%nl%>
|
28
|
+
<% for p in subpackages %>
|
29
|
+
<%nl%><% expand 'GeneratePackage', modules, :for => p %>
|
30
|
+
<% end %><%idec%>
|
31
|
+
end
|
32
|
+
<% end %>
|
33
|
+
|
34
|
+
<% define 'GenerateAssocs', :for => UMLPackage do %>
|
35
|
+
<% for a in allClasses.assocEnds>>:assoc %>
|
36
|
+
<% if a.endA.one? %>
|
37
|
+
<% if a.endB.one? %>
|
38
|
+
<%= a.endA.clazz.qualifiedName(this) %>.one_to_one '<%= a.endB.MName %>', <%= a.endB.clazz.qualifiedName(this) %>, '<%= a.endA.MName %>'
|
39
|
+
<% elsif a.endB.many? %>
|
40
|
+
<%= a.endA.clazz.qualifiedName(this) %>.one_to_many '<%= a.endB.MName %>', <%= a.endB.clazz.qualifiedName(this) %>, '<%= a.endA.MName %>'
|
41
|
+
<% end %>
|
42
|
+
<% elsif a.endA.many? %>
|
43
|
+
<% if a.endB.one? %>
|
44
|
+
<%= a.endB.clazz.qualifiedName(this) %>.one_to_many '<%= a.endA.MName %>', <%= a.endA.clazz.qualifiedName(this) %>, '<%= a.endB.MName %>'
|
45
|
+
<% elsif a.endB.many? %>
|
46
|
+
<%= a.endB.clazz.qualifiedName(this) %>.many_to_many '<%= a.endA.MName %>', <%= a.endA.clazz.qualifiedName(this) %>, '<%= a.endB.MName %>'
|
47
|
+
<% end %>
|
48
|
+
<% end %>
|
49
|
+
<% end %>
|
50
|
+
<% end %>
|
51
|
+
|
52
|
+
<% define 'ModuleHeader' do |modules| %>
|
53
|
+
<% if modules.include?(name) %>
|
54
|
+
module <%= className %>
|
55
|
+
<% else %>
|
56
|
+
class <%= className %> < <% nows %>
|
57
|
+
<% if rsc=RubySuperclass(modules) %><% nows %>
|
58
|
+
<%= rsc.className %>
|
59
|
+
<% else %><% nows %>
|
60
|
+
RGen::MetamodelBuilder::MMBase
|
61
|
+
<% end %>
|
62
|
+
<% end %>
|
63
|
+
<% end %>
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# RGen Framework
|
2
|
+
# (c) Martin Thiede, 2006
|
3
|
+
|
4
|
+
require 'rgen/metamodel_builder'
|
5
|
+
|
6
|
+
class Array
|
7
|
+
|
8
|
+
def >>(method)
|
9
|
+
compact.inject([]) { |r,e| r | ( (o=e.send(method)).is_a?(Array) ? o : [o] ) }
|
10
|
+
end
|
11
|
+
|
12
|
+
def method_missing(m, *args)
|
13
|
+
super unless size == 0 or compact.any?{|e| e.is_a? RGen::MetamodelBuilder::MMBase}
|
14
|
+
compact.inject([]) { |r,e|
|
15
|
+
if e.is_a? RGen::MetamodelBuilder::MMBase
|
16
|
+
r | ( (o=e.send(m)).is_a?(Array) ? o : [o] )
|
17
|
+
else
|
18
|
+
raise StandardError.new("Trying to call a method on an array element not a RGen MMBase")
|
19
|
+
end
|
20
|
+
}.compact
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# RGen Framework
|
2
|
+
# (c) Martin Thiede, 2006
|
3
|
+
|
4
|
+
require 'rgen/metamodel_builder'
|
5
|
+
|
6
|
+
module RGen
|
7
|
+
|
8
|
+
class Base
|
9
|
+
extend MetamodelBuilder
|
10
|
+
def initialize(env=nil)
|
11
|
+
env << self if env
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class AutoCreatedClass < Base
|
16
|
+
def method_missing(m,*args)
|
17
|
+
return super unless self.class.parent.accEnabled
|
18
|
+
if m.to_s =~ /(.*)=$/
|
19
|
+
self.class.has_one($1)
|
20
|
+
send(m,args[0])
|
21
|
+
elsif args.size == 0
|
22
|
+
self.class.has_many(m)
|
23
|
+
send(m)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# will be "extended" to the auto created class
|
29
|
+
module ParentAccess
|
30
|
+
def parent=(p)
|
31
|
+
@parent = p
|
32
|
+
end
|
33
|
+
def parent
|
34
|
+
@parent
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
module AutoClassCreator
|
39
|
+
attr_reader :accEnabled
|
40
|
+
def const_missing(className)
|
41
|
+
return super unless @accEnabled
|
42
|
+
module_eval("class "+className.to_s+" < RGen::AutoCreatedClass; end")
|
43
|
+
c = const_get(className)
|
44
|
+
c.extend(ParentAccess)
|
45
|
+
c.parent = self
|
46
|
+
c
|
47
|
+
end
|
48
|
+
def enableACC
|
49
|
+
@accEnabled = true
|
50
|
+
end
|
51
|
+
def disableACC
|
52
|
+
@accEnabled = false
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module RGen
|
2
|
+
|
3
|
+
# An Environment is used to hold model elements.
|
4
|
+
#
|
5
|
+
class Environment
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@elements = []
|
9
|
+
end
|
10
|
+
|
11
|
+
# Add a model element. Returns the environment so <code><<</code> can be chained.
|
12
|
+
#
|
13
|
+
def <<(el)
|
14
|
+
@elements << el
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
# Finds and returns model elements in the environment.
|
19
|
+
#
|
20
|
+
# The search description argument must be a hash specifying attribute/value pairs.
|
21
|
+
# Only model elements are returned which respond to the specified attribute methods
|
22
|
+
# and return the specified values as result of these attribute methods.
|
23
|
+
#
|
24
|
+
# As a special hash key :class can be used to look for model elements of a specific
|
25
|
+
# class. In this case an array of possible classes can optionally be given.
|
26
|
+
#
|
27
|
+
def find(desc)
|
28
|
+
result = []
|
29
|
+
classes = desc[:class] if desc[:class] and desc[:class].is_a?(Array)
|
30
|
+
classes = [ desc[:class] ] if !classes and desc[:class]
|
31
|
+
elements.each {|e|
|
32
|
+
failed = false
|
33
|
+
failed = true if classes and !classes.any?{ |c| e.is_a?(c) }
|
34
|
+
desc.each_pair { |k,v|
|
35
|
+
failed = true if k != :class and ( !e.respond_to?(k) or e.send(k) != v )
|
36
|
+
}
|
37
|
+
result << e unless failed
|
38
|
+
}
|
39
|
+
result
|
40
|
+
end
|
41
|
+
|
42
|
+
# Return the elements of the environment as an array
|
43
|
+
#
|
44
|
+
def elements
|
45
|
+
@elements.dup
|
46
|
+
end
|
47
|
+
|
48
|
+
# This method can be used to instantiate a class and automatically put it into
|
49
|
+
# the environment. The new instance is returned.
|
50
|
+
#
|
51
|
+
def new(clazz)
|
52
|
+
@elements << clazz.new
|
53
|
+
@elements[-1]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# RGen Framework
|
2
|
+
# (c) Martin Thiede, 2006
|
3
|
+
|
4
|
+
require 'rgen/metamodel_builder/builder_runtime'
|
5
|
+
require 'rgen/metamodel_builder/builder_extensions'
|
6
|
+
|
7
|
+
module RGen
|
8
|
+
|
9
|
+
# MetamodelBuilder can be used to create a metamodel, i.e. Ruby classes which
|
10
|
+
# act as metamodel elements.
|
11
|
+
#
|
12
|
+
# To create a new metamodel element, create a Ruby class which inherits from
|
13
|
+
# MetamodelBuilder::MMBase
|
14
|
+
#
|
15
|
+
# class Person < RGen::MetamodelBuilder::MMBase
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# This way a couple of class methods are made available to the new class.
|
19
|
+
# These methods can be used to:
|
20
|
+
# * add attributes to the class
|
21
|
+
# * add associations with other classes
|
22
|
+
#
|
23
|
+
# Here is an example:
|
24
|
+
#
|
25
|
+
# class Person < RGen::MetamodelBuilder::MMBase
|
26
|
+
# has_one 'name', String
|
27
|
+
# has_one 'age', Integer
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# class House < RGen::MetamodelBuilder::MMBase
|
31
|
+
# has_one 'address'
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# Person.many_to_many 'homes', House, 'inhabitants'
|
35
|
+
#
|
36
|
+
# See BuilderExtensions for details about the available class methods.
|
37
|
+
#
|
38
|
+
# =Attributes
|
39
|
+
#
|
40
|
+
# The example above creates two classes 'Person' and 'House'. Person has the attributes
|
41
|
+
# 'name' and 'age', House has the attribute 'address'. The attributes can be
|
42
|
+
# accessed on instances of the classes in the following way:
|
43
|
+
#
|
44
|
+
# p = Person.new
|
45
|
+
# p.name = "MyName"
|
46
|
+
# p.age = 22
|
47
|
+
# p.name # => "MyName"
|
48
|
+
# p.age # => 22
|
49
|
+
#
|
50
|
+
# Note that the class Person takes care of the type of its attributes. As
|
51
|
+
# declared above, a 'name' can only be a String, an 'age' must be an Integer.
|
52
|
+
# So the following would return an exception:
|
53
|
+
#
|
54
|
+
# p.name = :myName # => exception: can not put a Symbol where a String is expected
|
55
|
+
#
|
56
|
+
# If the type of an attribute should be left undefined, just leave away the
|
57
|
+
# second argument of 'has_one' as show at the attribute 'address' for House.
|
58
|
+
#
|
59
|
+
# =Associations
|
60
|
+
#
|
61
|
+
# As well as attributes show up as instance methods, associations bring their own
|
62
|
+
# accessor methods. For the Person-to-House association this would be:
|
63
|
+
#
|
64
|
+
# h1 = House.new
|
65
|
+
# h1.address = "Street1"
|
66
|
+
# h2 = House.new
|
67
|
+
# h2.address = "Street2"
|
68
|
+
# p.addHomes(h1)
|
69
|
+
# p.addHomes(h2)
|
70
|
+
# p.removeHomes(h1)
|
71
|
+
# p.homes # => [ h2 ]
|
72
|
+
#
|
73
|
+
# The Person-to-House association is _bidirectional_. This means that with the
|
74
|
+
# addition of a House to a Person, the Person is also added to the House. Thus:
|
75
|
+
#
|
76
|
+
# h1.inhabitants # => []
|
77
|
+
# h2.inhabitants # => [ p ]
|
78
|
+
#
|
79
|
+
# Note that the association is defined between two specific classes, instances of
|
80
|
+
# different classes can not be added. Thus, the following would result in an
|
81
|
+
# exception:
|
82
|
+
#
|
83
|
+
# p.addHomes(:justASymbol) # => exception: can not put a Symbol where a House is expected
|
84
|
+
#
|
85
|
+
# _Unidirectional_ associations can be thought of as attributes as shown above. This means
|
86
|
+
# that 'has_one' or 'has_many' can be used to define such associations. Again, the
|
87
|
+
# type of the attribute/association can be specified as a second argument.
|
88
|
+
#
|
89
|
+
module MetamodelBuilder
|
90
|
+
|
91
|
+
# Use this class as a start for new metamodel elements (i.e. Ruby classes)
|
92
|
+
# by inheriting for it.
|
93
|
+
#
|
94
|
+
# See MetamodelBuilder for an example.
|
95
|
+
class MMBase
|
96
|
+
include BuilderRuntime
|
97
|
+
extend BuilderExtensions
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# RGen Framework
|
2
|
+
# (c) Martin Thiede, 2006
|
3
|
+
|
4
|
+
require 'rgen/name_helper'
|
5
|
+
|
6
|
+
module RGen
|
7
|
+
|
8
|
+
module MetamodelBuilder
|
9
|
+
|
10
|
+
class BuildHelper
|
11
|
+
include NameHelper
|
12
|
+
|
13
|
+
def self.build(mod,bnd,tpl)
|
14
|
+
mod.module_eval ERB.new(tpl).result(BuildHelper.new(bnd).bnd)
|
15
|
+
end
|
16
|
+
def initialize(bnd)
|
17
|
+
@outer_binding = bnd
|
18
|
+
end
|
19
|
+
def bnd
|
20
|
+
binding
|
21
|
+
end
|
22
|
+
def method_missing(m)
|
23
|
+
eval("#{m}",@outer_binding)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
# RGen Framework
|
2
|
+
# (c) Martin Thiede, 2006
|
3
|
+
|
4
|
+
require 'erb'
|
5
|
+
require 'rgen/metamodel_builder/build_helper'
|
6
|
+
|
7
|
+
module RGen
|
8
|
+
|
9
|
+
module MetamodelBuilder
|
10
|
+
|
11
|
+
# This module provides methods which can be used to setup a metamodel element.
|
12
|
+
# The module is used to +extend+ MetamodelBuilder::MMBase, i.e. add the module's
|
13
|
+
# methods as class methods.
|
14
|
+
#
|
15
|
+
# MetamodelBuilder::MMBase should be used as a start for new metamodel elements.
|
16
|
+
# See MetamodelBuilder for an example.
|
17
|
+
#
|
18
|
+
module BuilderExtensions
|
19
|
+
|
20
|
+
# Add a single attribute or unidirectional association.
|
21
|
+
# 'role' specifies the name which is used to access the attribute.
|
22
|
+
# 'target_class' is optional and can be used to fix the type of objects which
|
23
|
+
# can be held by this attribute.
|
24
|
+
#
|
25
|
+
# This class method adds the following instance methods, where 'role' is to be
|
26
|
+
# replaced by the given role name:
|
27
|
+
# class#role # getter
|
28
|
+
# class#role=(value) # setter
|
29
|
+
#
|
30
|
+
def has_one(role, target_class=nil)
|
31
|
+
has_one_internal(role, target_class)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Add an unidirectional _many_ association.
|
35
|
+
# 'role' specifies the name which is used to access the attribute.
|
36
|
+
# 'target_class' is optional and can be used to fix the type of objects which
|
37
|
+
# can be referenced by this association.
|
38
|
+
#
|
39
|
+
# This class method adds the following instance methods, where 'role' is to be
|
40
|
+
# replaced by the given role name:
|
41
|
+
# class#addRole(value)
|
42
|
+
# class#removeRole(value)
|
43
|
+
# class#role # getter, returns an array
|
44
|
+
# Note that the first letter of the role name is turned into an uppercase
|
45
|
+
# for the add and remove methods.
|
46
|
+
#
|
47
|
+
def has_many(role, target_class=nil)
|
48
|
+
has_many_internal(role, target_class)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Add a bidirectional one-to-many association between two classes.
|
52
|
+
# The class this method is called on is refered to as _own_class_ in
|
53
|
+
# the following.
|
54
|
+
#
|
55
|
+
# Instances of own_class can use 'own_role' to access _many_ associated instances
|
56
|
+
# of type 'target_class'. Instances of 'target_class' can use 'target_role' to
|
57
|
+
# access _one_ associated instance of own_class.
|
58
|
+
#
|
59
|
+
# This class method adds the following instance methods where 'ownRole' and
|
60
|
+
# 'targetRole' are to be replaced by the given role names:
|
61
|
+
# own_class#addOwnRole(value)
|
62
|
+
# own_class#removeOwnRole(value)
|
63
|
+
# own_class#ownRole
|
64
|
+
# target_class#targetRole
|
65
|
+
# target_class#targetRole=(value)
|
66
|
+
# Note that the first letter of the role name is turned into an uppercase
|
67
|
+
# for the add and remove methods.
|
68
|
+
#
|
69
|
+
# When an element is added/set on either side, this element also receives the element
|
70
|
+
# is is added to as a new element.
|
71
|
+
#
|
72
|
+
def one_to_many(own_role, target_class, target_role)
|
73
|
+
has_many_internal(own_role,target_class,target_role,:one)
|
74
|
+
target_class.has_one_internal(target_role,self,own_role,:many)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Add a bidirectional many-to-many association between two classes.
|
78
|
+
# The class this method is called on is refered to as _own_class_ in
|
79
|
+
# the following.
|
80
|
+
#
|
81
|
+
# Instances of own_class can use 'own_role' to access _many_ associated instances
|
82
|
+
# of type 'target_class'. Instances of 'target_class' can use 'target_role' to
|
83
|
+
# access _many_ associated instances of own_class.
|
84
|
+
#
|
85
|
+
# This class method adds the following instance methods where 'ownRole' and
|
86
|
+
# 'targetRole' are to be replaced by the given role names:
|
87
|
+
# own_class#addOwnRole(value)
|
88
|
+
# own_class#removeOwnRole(value)
|
89
|
+
# own_class#ownRole
|
90
|
+
# target_class#addTargetRole
|
91
|
+
# target_class#removeTargetRole=(value)
|
92
|
+
# target_class#targetRole
|
93
|
+
# Note that the first letter of the role name is turned into an uppercase
|
94
|
+
# for the add and remove methods.
|
95
|
+
#
|
96
|
+
# When an element is added on either side, this element also receives the element
|
97
|
+
# is is added to as a new element.
|
98
|
+
#
|
99
|
+
def many_to_many(own_role, target_class, target_role)
|
100
|
+
has_many_internal(own_role,target_class,target_role,:many)
|
101
|
+
target_class.has_many_internal(target_role,self,own_role,:many)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Add a bidirectional one-to-one association between two classes.
|
105
|
+
# The class this method is called on is refered to as _own_class_ in
|
106
|
+
# the following.
|
107
|
+
#
|
108
|
+
# Instances of own_class can use 'own_role' to access _one_ associated instance
|
109
|
+
# of type 'target_class'. Instances of 'target_class' can use 'target_role' to
|
110
|
+
# access _one_ associated instance of own_class.
|
111
|
+
#
|
112
|
+
# This class method adds the following instance methods where 'ownRole' and
|
113
|
+
# 'targetRole' are to be replaced by the given role names:
|
114
|
+
# own_class#ownRole
|
115
|
+
# own_class#ownRole=(value)
|
116
|
+
# target_class#targetRole
|
117
|
+
# target_class#targetRole=(value)
|
118
|
+
#
|
119
|
+
# When an element is set on either side, this element also receives the element
|
120
|
+
# is is added to as the new element.
|
121
|
+
#
|
122
|
+
def one_to_one(own_role, target_class, target_role)
|
123
|
+
has_one_internal(own_role,target_class,target_role,:one)
|
124
|
+
target_class.has_one_internal(target_role,self,own_role,:one)
|
125
|
+
end
|
126
|
+
|
127
|
+
protected
|
128
|
+
|
129
|
+
def has_one_internal(name, cls=nil, role=nil, kind=nil)
|
130
|
+
BuildHelper.build self, binding, <<-CODE
|
131
|
+
@@one_assocs ||= []
|
132
|
+
@@one_assocs << name
|
133
|
+
def #{name}=(val)
|
134
|
+
return if val == @#{name}
|
135
|
+
<% if cls %>
|
136
|
+
raise _assignmentTypeError(self,val,#{cls}) unless val.nil? or val.is_a? #{cls}
|
137
|
+
<% end %>
|
138
|
+
oldval = @#{name}
|
139
|
+
@#{name} = val
|
140
|
+
_unregister(self,oldval,"#{role}","#{kind}")
|
141
|
+
_register(self,val,"#{role}","#{kind}")
|
142
|
+
end
|
143
|
+
def #{name}
|
144
|
+
@#{name}
|
145
|
+
end
|
146
|
+
alias get<%= firstToUpper(name) %> #{name}
|
147
|
+
alias set<%= firstToUpper(name) %> #{name}=
|
148
|
+
CODE
|
149
|
+
end
|
150
|
+
|
151
|
+
def has_many_internal(name, cls=nil, role=nil, kind = nil)
|
152
|
+
BuildHelper.build self, binding, <<-CODE
|
153
|
+
@@many_assocs ||= []
|
154
|
+
@@many_assocs << name
|
155
|
+
def add<%= firstToUpper(name) %>(val)
|
156
|
+
@#{name} = [] unless @#{name}
|
157
|
+
return if val.nil? or @#{name}.include?(val)
|
158
|
+
<% if cls %>
|
159
|
+
raise _assignmentTypeError(self,val,#{cls}) unless val.nil? or val.is_a? #{cls}
|
160
|
+
<% end %>
|
161
|
+
@#{name}.push val
|
162
|
+
_register(self, val, "#{role}", "#{kind}")
|
163
|
+
end
|
164
|
+
def remove<%= firstToUpper(name) %>(val)
|
165
|
+
@#{name} = [] unless @#{name}
|
166
|
+
return unless @#{name}.include?(val)
|
167
|
+
@#{name}.delete val
|
168
|
+
_unregister(self, val, "#{role}", "#{kind}")
|
169
|
+
end
|
170
|
+
def #{name}
|
171
|
+
( @#{name} ? @#{name}.dup : [] )
|
172
|
+
end
|
173
|
+
def #{name}=(val)
|
174
|
+
return if val.nil?
|
175
|
+
raise _assignmentTypeError(self, val, Array) unless val.is_a? Array
|
176
|
+
#{name}.each {|e|
|
177
|
+
remove<%= firstToUpper(name) %>(e)
|
178
|
+
}
|
179
|
+
val.each {|v|
|
180
|
+
add<%= firstToUpper(name) %>(v)
|
181
|
+
}
|
182
|
+
end
|
183
|
+
alias get<%= firstToUpper(name) %> #{name}
|
184
|
+
alias set<%= firstToUpper(name) %> #{name}=
|
185
|
+
CODE
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|