MetamodelBuilder can be used to create a metamodel, i.e. Ruby classes which act as metamodel elements.
To create a new metamodel element, create a Ruby class which inherits from MetamodelBuilder::MMBase
class Person < RGen::MetamodelBuilder::MMBase end
This way a couple of class methods are made available to the new class. These methods can be used to:
add attributes to the class
add associations with other classes
Here is an example:
class Person < RGen::MetamodelBuilder::MMBase has_attr 'name', String has_attr 'age', Integer end class House < RGen::MetamodelBuilder::MMBase has_attr 'address' # String is default end Person.many_to_many 'homes', House, 'inhabitants'
See BuilderExtensions for details about the available class methods.
The example above creates two classes 'Person' and 'House'. Person has the attributes 'name' and 'age', House has the attribute 'address'. The attributes can be accessed on instances of the classes in the following way:
p = Person.new p.name = "MyName" p.age = 22 p.name # => "MyName" p.age # => 22
Note that the class Person takes care of the type of its attributes. As declared above, a 'name' can only be a String, an 'age' must be an Integer. So the following would return an exception:
p.name = :myName # => exception: can not put a Symbol where a String is expected
If the type of an attribute should be left undefined, use Object as type.
As well as attributes show up as instance methods, associations bring their own accessor methods. For the Person-to-House association this would be:
h1 = House.new h1.address = "Street1" h2 = House.new h2.address = "Street2" p.addHomes(h1) p.addHomes(h2) p.removeHomes(h1) p.homes # => [ h2 ]
The Person-to-House association is bidirectional. This means that with the addition of a House to a Person, the Person is also added to the House. Thus:
h1.inhabitants # => [] h2.inhabitants # => [ p ]
Note that the association is defined between two specific classes, instances of different classes can not be added. Thus, the following would result in an exception:
p.addHomes(:justASymbol) # => exception: can not put a Symbol where a House is expected
The class methods described above are used to create a Ruby representation of the metamodel we have in mind in a very simple and easy way. We don't have to care about all the details of a metamodel at this point (e.g. multiplicities, changeability, etc).
At the same time however, an instance of the ECore metametamodel (i.e. a ECore based description of our metamodel) is provided for all the Ruby classes and modules we create. Since we did not provide the nitty-gritty details of the metamodel, defaults are used to fully complete the ECore metamodel description.
In order to access the ECore metamodel description, just call the ecore method on a Ruby class or module object belonging to your metamodel.
Here is the example continued from above:
Person.ecore.eAttributes.name # => ["name", "age"] h2pRef = House.ecore.eReferences.first h2pRef.eType # => Person h2pRef.eOpposite.eType # => House h2pRef.lowerBound # => 0 h2pRef.upperBound # => -1 h2pRef.many # => true h2pRef.containment # => false
Note that the use of array_extensions.rb is assumed here to make model navigation convenient.
The following metamodel builder methods are supported, see individual method description for details:
Attributes:
Unidirectional references:
Bidirectional references:
Every builder command can optionally take a specification of further ECore properties. Additional properties for Attributes and References are (with defaults in brackets):
:ordered (true),
:unique (true),
:changeable (true),
:volatile (false),
:transient (false),
:unsettable (false),
:derived (false),
:lowerBound (0),
:resolveProxies (true) references only,
Using these additional properties, the above example can be refined as follows:
class Person < RGen::MetamodelBuilder::MMBase has_attr 'name', String, :lowerBound => 1 has_attr 'yearOfBirth', Integer, has_attr 'age', Integer, :derived => true def age_derived Time.now.year - yearOfBirth end end Person.many_to_many 'homes', House, 'inhabitants', :upperBound => 5 Person.ecore.eReferences.find{|r| r.name == 'homes'}.upperBound # => 5
This way we state that there must be a name for each person, we introduce a new attribute 'yearOfBirth' and make 'age' a derived attribute. We also say that a person can have at most 5 houses in our metamodel.
If the attribute 'derived' of an attribute or reference is set to true, a method attributeName_derived has to be provided. This method is called whenever the original attribute is accessed. The original attribute can not be written if it is derived.
The purpose of the ConstantOrderHelper is to capture the definition order of RGen metamodel builder classes, modules and enums. The problem is that Ruby doesn't seem to track the order of constants being created in a module. However the order is important because it defines the order of eClassifiers and eSubpackages in a EPackage.
It would be helpful here if Ruby provided a const_added callback, but this is not the case up to now.
The idea for capturing is that all events of creating a RGen class, module or enum are reported to the ConstantOrderHelper singleton. For classes and modules it tries to add their names to the parent's _constantOrder array. The parent module is derived from the class's or module's name. However, the new name is only added if the respective parent module has a new constant (which is not yet in _constantOrder) which points to the new class or module. For enums it is a bit more complicated, because at the time the enum is created, the parent module does not yet contain the constant to which the enum is assigned. Therefor, the enum is remembered and it is tried to be stored on the next event (class, module or enum) within the module which was created last (which was last extended with ModuleExtension). If it can not be found in that module, all parent modules of the last module are searched. This way it should also be correctly entered in case it was defined outside of the last created module. Note that an enum is not stored to the constant order array unless another event occurs. That's why it is possible that one enum is missing at the enum. This needs to be taken care of by the ECore transformer.
This way of capturing should be sufficient for the regular use cases of the RGen metamodel builder language. However, it is possible to write code which messes this up, see unit tests for details. In the worst case, the new classes, modules or enums will just not be found in a parent module and thus be ignored.
# File lib/rgen/metamodel_builder/mm_multiple.rb, line 6 def self.MMMultiple(*superclasses) c = Class.new(MMBase) class << c attr_reader :multiple_superclasses end c.instance_variable_set(:@multiple_superclasses, superclasses) superclasses.collect{|sc| sc.ancestors}.flatten. reject{|m| m.is_a?(Class)}.each do |arg| c.instance_eval do include arg end end return c end
Generated with the Darkfish Rdoc Generator 2.