Moose-Query is an internal domain-specific language (DSL) to build queries for entities in Moose. It replaces Moose-Chef, and is designed to simplify and standardize the way to query Moose models.
An application model in Moose is composed of two concepts:
MooseQuery allows exploring a model via those two concepts. Then, you can:
In this documentation, we detail the use of the DSL. With this tool, you will be able to query your Moose model, for example, to:
Navigating the containment tree is the easiest way to query a model. It is possible with the association relations in the model. To do this, there is a really simple API on entities:
Selector | Description |
---|---|
#children | Direct children of the entity |
#parents | Direct parents of the entity |
#allChildren | Children of the entity and their children recursively |
#allParents | Parents of the entity and their parents recursively |
For example, the model in the example figure can be requested as follows:
package1 children. "=> { package2 . class1 }"
class3 children. "=> { attribute1 . attribute2 }"
package1 allChildren. "=> { package2 . class1 . class2 . class3 . attribute1 . attribute2 }"
class3 allChildren. "=> { attribute1 . attribute2 }"
package1 parents. "=> { }"
class3 parents. "=> { package2 }"
class4 parents. "=> { package3 . namespace1 }"
class3 allParents. "=> { package2 . package1 }"
attribute1 allParents. "=> { class3 . package2 . package1 }"
A navigation query returns a result as a MooseQueryResult. There are three types of query results:
These query result classes are special collections with some new features.
One of the most useful ones is the #opposites method present on MooseIncomingQueryResult and MooseOutgoingQueryResult. It returns a MooseObjectQueryResult containing all the opposite entities of the query result relative to the receiver of the query.
Indeed, when we query a model, it is often not the associations that we want as result but the source/target entities of these associations. For example, if we want the instance variables accessed by a given method, #query:with: will return the accesses, whereas sending the message #opposites on the result returns the variables themselves.
Taking the previous model as an example we can do:
class1 queryAll: #in. "=> { inheritance1 . reference1 }"
(class1 queryAll: #in) opposites. "=> { class2 . method2 }"
Another possibility is to exclude all the results where the opposite is included in the receiver of the original query. This can be done via the method #withoutSelfLoop. It is handy when we want to query a model to find external dependencies without internal ones.
Another feature is to be able to change the scope of the result. This feature is covered in the next section.
Depending on the analysis we perform, it might be relevant to handle the queries at a different scope. For example, we might want to know the associations between a given class and all the other classes of the project, whatever the type of the associations. But maybe the associations between classes can only be inheritance. Or maybe two classes are also linked if a method of one of them invoked a method of the other. So, it is necessary to consider the invocation at the scope of the class whereas it occurs between some of their methods. Exactly the same occurs for the other types of associations.
For this scenario, it is possible to use the scoped queries. A scoped query can be applied to:
A scoped query will specify a direction, i.e., look for entities higher, lower or both higher and lower than the receiver in the containment tree.
Selector | Parameter | Description |
---|---|---|
#atScope: | Famix class defining the scope of the query | Return all the entities at this given famix class scope that are above the entity in the containment tree of the meta-model. |
#toScope: | Famix class defining the scope of the query | Return all the entities at this given famix class scope that are below the entity in the containment tree of the meta-model. |
#withScope: | Famix class defining the scope of the query | Return all the entities at this given famix class scope that are both above and below the entity in the containment tree of the meta-model. |
For example, if we take the model present in the figure as an example, we can do:
package2 atScope: FAMIXPackage. "=> { package2 }"
package2 toScope: FAMIXPackage. "=> { }"
package2 withScope: FAMIXPackage. "=> { package2 }"
method1 atScope: FAMIXType. "=> { class3 }"
method1 toScope: FAMIXType. "=> { class4 }"
method1 withScope: FAMIXType. "=> { class3 . class4 }"
method1 atScope: FAMIXPackage. "=> { package2 }"
method1 toScope: FAMIXPackage. "=> { }"
method1 withScope: FAMIXPackage. "=> { package2 }"
As we see in those examples, if we find and entity matching the search kind (possibly the receiver of the query), we stop here and we do not check its parents. This is the reason why package2 atScope: FAMIXPackage does not return package1 and why method1 atScope: FAMIXPackage only returns package2.
If we want to query all the entities matching the searched kind, it is possible to use a variant of those methods.
Selector | Parameter | Description |
---|---|---|
#allAtScope: | Famix class defining the scope of the query | Return all the entities at this given Famix class scope that are above the entity in the containment tree of the meta-model on multiple levels (it will not stop at the first encountered entity of the right kind). |
#allToScope: | Famix class defining the scope of the query | Return all the entities at this given Famix class scope that are below the entity in the containment tree of the meta-model on multiple levels (it will not stop at the first encountered entity of the right kind). |
#allWithScope: | Famix class defining the scope of the query | Return all the entities at this given famix class scope that are both above and below the entity in the containment tree of the meta-model on multiple levels (it will not stop at the first encountered entity of the right kind). |
For example, if we take the model present in the figure as an example, we can do:
package2 allAtScope: FAMIXPackage. "=> { package2. package1 }"
package2 allToScope: FAMIXPackage. "=> { }"
package2 allWithScope: FAMIXPackage. "=> { package2. package1 }"
method1 allAtScope: FAMIXType. "=> { class3 }"
method1 allToScope: FAMIXType. "=> { class4 }"
method1 allWithScope: FAMIXType. "=> { class3 . class4 }"
method1 allAtScope: FAMIXPackage. "=> { package2. package1 }"
method1 allToScope: FAMIXPackage. "=> { }"
method1 allWithScope: FAMIXPackage. "=> { package2. package1 }"
package1 allToScope: FAMIXType. "=> { class1 . class2 . class3 . class4 }"
class4 allAtScope: FAMIXType. "=> { class3 . class4 }"
You will find here various examples of the use of MooseQuery API.
"Since #withScope: will take the opposites of the associations by itself, there is no need to call explicitly #opposites."
(aClass query: #out) withoutSelfLoop withScope: FAMIXMethod