📜 ⬆️ ⬇️

Reflection in Caché



Reflection has rarely been raised on Caché forums or blogs. Perhaps because the concept of reflection as such is not explicitly indicated in Caché. However, reflection in Caché is present and can be a very useful tool in development.

What is reflection?


The concept of reflection or reflection means a process during which a program can monitor and modify its own structure and behavior at run time.

Some programs are able to process their own constructions as data, performing reflexive modifications.
')
Reflex-oriented programming includes self-checking, self-modification, and self-cloning. Nevertheless, the main advantage of the reflexive-oriented paradigm lies in the dynamic modification of the program, which can be determined and executed during the operation of the program.

This concept was first introduced by Brian Cantwell Smith in his PhD thesis .

The site oracle.com on the use of reflection states that it is usually used by programs that require checking or changing the behavior of the application during code execution. This is a relatively advanced technology and should only be used by developers with a serious understanding of the basics of the language. With this in mind, you can use reflection as a powerful tool and make it possible that which previously seemed not possible in your application.

If you are not familiar with Reflection in practice, this concept is not very clear.
Reflection is usually reduced to having thought through the instructions for the program, not knowing at what point in time it will work with a particular object, to change the behavior of the application in some way or another.

Reflection in Caché


As such, the section or the highlighted concept I never met.
However, some features are related to this wonderful topic.
So, we meet:

$ CLASSMETHOD - performs the specified class method in the desired class (from any point of the program);
$ CLASSNAME - returns the name of the class;
$ ISOBJECT - checks if the specified expression is an object or not;
$ METHOD - allows you to call a method on a given instance of the class;
$ NAME - returns the name of the variable;
$ PROPERTY - refers to a specific property of an object and returns its value;
$ PARAMETER - returns the value of the specified class parameter.

And separately I want to highlight
$ XECUTE - executes the code passed as a string with the specified parameters.

You can read more about these functions and find examples of their use in the documentation .

Usage example:
$XECUTE ("set name = ##class(Data.SampleDict1).%OpenId("_param_").Name") 


those. we can execute any string as code. All anything, but do not forget about security. If we have a web application and at some moment it will be able to slip a command like “kill the system” (there could be a smiley, but the rules forbid).
And do not forget that this is a compilation at runtime, so use only in extreme cases.

The rest of the reflexive functions work in much the same way as in other languages.

Apply in practice


Suppose you want to create a web application consisting of a workspace with two tabs. Tabs should have different composition of fields and work with them will be associated with real objects in the database.
For example, the first tab is responsible for reference information of one kind, the second of the other.
The user should be able to create an entry in the directory and view the results of work in the workspace in the form of a journal of entries. With the ability to search and sort by the specified parameter. Our application is a demo version of using several reflexive functions when working with a database.
You can download the source code from the link: sample application and simply import into Studio.

Consider how the dict1.CSP page will look like:
code dict1.csp
< script language = "Cache" runat = "Server" >
do ## class ( Front.Blocks ). PrintHeader ( "sampleDict" , % session )
</ script >
< script language = "Cache" method = "logout" >
do % session . Logout ()
</ script >
< script language = "Cache" runat = "Server" >
// Prepare data structures for search fields:
set searchFields = ## class ( % ListOfDataTypes ). % New ()
do searchFields . Insert (
## class ( Front.Helpers.SearchColumn ). % New ( "Name" , "String" )
)

// - for the list form fields
set listFields = ## class ( % ListOfDataTypes ). % New ()
do listFields . Insert (
## class ( Front.Helpers.ListColumn ). % New ( "ID" , "#" , 100)
)
do listFields . Insert (
## class ( Front.Helpers.ListColumn ). % New ( "Name" , "Name" , 200)
)
do listFields . Insert (
## class ( Front.Helpers.ListColumn ). % New ( "Code" , "Code" , 300)
)

// - for edit form fields
set detailFields = ## class ( % ListOfDataTypes ). % New ()
do detailFields . Insert (
## class ( Front.Helpers.DetailColumn ). % New ( "Name" , "Name" , 300, 1)
)
do detailFields . Insert (
## class ( Front.Helpers.DetailColumn ). % New ( "Code" , "Code" , 300, 1)
)

// - for tab headers
set titleFields = ## class ( % ListOfDataTypes ). % New ()
do titleFields . Insert (
"ID"
)
do titleFields . Insert (
"Name"
)
do ## class ( Front.Blocks ). PrintAngularJs ()
do ## class ( Front.Blocks ). PrintGridJs ()
do ## class ( Front.Blocks ). PrintNgGridFlexibleHeightPluginJs ()
do ## class ( Front.Blocks ). PrintBootstrapJs ()
do ## class ( Front.Blocks ). PrintDatePickerJs ()
do ## class ( Front.Blocks ). PrintDftabmenuJs ()
do ## class ( Front.Blocks ). PrintModalJs ()
do ## class ( Front.LDController ). Initialize ( searchFields , listFields , detailFields , "Data.SampleDict1" , "Test Area" , titleFields , 1)
do ## class ( Front.Blocks ). PrintFooter ()
</ script >

To draw certain parts of the page (Header, Footer), special methods are used in Front.Blocks .
To specify the fields by which we will search, we create a searchFields list, for the edit area, the detailFields list and for the listFields viewing area .

Then we initialize our content in LDController .
do ## class ( Front.LDController ). initialize ( searchFields , listFields , detailFields , "Data.SampleDict1" , "Test Area1" , titleFields , 1)

The dict2.CSP will respectively
do ## class ( Front.LDController ). initialize ( searchFields , listFields , detailFields , "Data.SampleDict2" , "Test Area2" , titleFields , 1)

LDController is a generic class. It contains the basic functions of the typical csp pages, such as our test reference books.
In this case, it allows you to get a list of reference information records from the database, filter it by a given field (we set only the filter by name), know the number of records in total and how many per page, create and edit records.

Methods of class LDController :

initialize - is the html code of the edit form and the viewport. This is a mad mix of scripts and cache code, I will not go into details describing it.
saveItemData - saves data from the edit area to the database.

In general, everywhere, except perhaps the initialize method, we managed to maintain a clear and simple code structure, implement the functionality we need and achieve extensibility of the application. Even for a beginner, it will not be difficult to create a mapping for other database objects in the form of a new csp page. The functional parts are divided into logical components, it is clear what and where you need to change, if necessary.

The app looks like this.

View pane and search existing entries


The edit area of ​​the first directory element


The edit area of ​​the second reference element


Viewing pane with saved entry


Let us consider an example of creating / editing an object using the example of the saveItemData method. Parameters are the list of object fields, form data in the form of a JSON structure, and the name of the class. Using the $ CLASSMETHOD function, we can create / open a building class object (by its name). Fill class properties with values ​​from the data structure from the tab using the $ PROPERTY function and save the object to the database.
saveItemData method code
ClassMethod saveItemData (ListColumnsJSON As% String, ItemData As% String, DatasourceClassName As% String)
{
set listColumns = ## class ( Utils.JSON ). Decode ( ListColumnsJSON )
$$$ THROWONERROR ( st , ## class ( % ZEN.Auxiliary.jsonProvider ). % ConvertJSONToObject ( ItemData ,,. ItemData , 1))

if ( itemData . ID = 0) {
set obj = $ CLASSMETHOD ( DatasourceClassName , "% New" )
}
else {
set obj = $ CLASSMETHOD ( DatasourceClassName , "% OpenId" , itemData . ID )
}

for {
set field = listColumns . GetNext (. Idx )
quit : idx = ""
set fieldName = field . GetAt ( "Field" )
if ( fieldName '= "ID" ) {
set $ PROPERTY ( obj , fieldName ) = $ PROPERTY ( itemData , fieldName )
}
}
do obj . % Save ()
}

The same can be done when retrieving records from the database and deleting them.

As you can see, using Caché is very simple and easy to use.
Let not too many possibilities be solved, but the task is completed - we managed to get away from the objects and work with the objects as such, and it also turned out to simplify the structure of the code, to make it understandable. For those who are just beginning to explore the possibilities of language, this example can be a good start for their own discoveries and solutions.

List of sources:

1. Reflection (computer programming), Wikipedia
2. Procedural reflection in programming languages, by Brian Cantwell Smith
3. The Reflection API, Oracle
4. Caché ObjectScript Functions, Intersystems
5. CacheJSON is a JSON encoder / decoder for Intersystems Cache

Source: https://habr.com/ru/post/263021/


All Articles