Written by

Senior Cloud Architect at InterSystems
MOD
Question Eduard Lebedyuk · Apr 8, 2019

Can datatype redefine getter/setter?

I have a lot of string properties, which need custom but similar Getters and Setters.

Currently I'm generating Getter and Setter in class generation code but that expands class definition.

Is there a way to specify custom datatype which would generate custom getters and setters?

Comments

Robert Cemper · Apr 8, 2019

I could imagine inheriting standard data types for a customized data type and then 

adding methods LogicalToStorage and StorageToLogical  similar as LogicalToObs, ....
So your manipulations happen on the way from Global to the local variable.

Additional Parameters eventually may control the generated code.

It's just a vague idea.

0
Eduard Lebedyuk  Apr 9, 2019 to Robert Cemper

Here's what I've coded.

Property:

Class Test.String Extends %String
{

ClassMethod StorageToLogical(%val As %String) As %String [ CodeMode = objectgenerator ]
{
	set property = $g(%member)
	do %code.WriteLine($c(9) _ "Quit """ _ property _ """")
	quit $$$OK
}

ClassMethod LogicalToStorage(%val As %String) As %String [ CodeMode = objectgenerator ]
{
	set property = $g(%member)
	do %code.WriteLine($c(9) _ "Quit """ _ property _ """")
	quit $$$OK
}

}

Class:

Class Test.Obj Extends %RegisteredObject
{

Property prop As Test.String;

}

However reference to property does not return anything (expected to return property name):

w obj.prop
zw obj.prop
>""

Any ideas?

0
Eduard Lebedyuk  Apr 9, 2019 to Dmitry Maslennikov

Can you please provide an example?

I'm aware only of %XML.PropertyParameters but there's nothing but parameters.

0
Eduard Lebedyuk  Apr 9, 2019 to Eduard Lebedyuk

Turns out it's much easier than I thought:

Class Test.String Extends %String
{

ClassMethod Get() As %String [ CodeMode = objectgenerator ]
{
    do %code.WriteLine($c(9) _ "Quit $g(^Test.String, 0)")
    quit $$$OK
}

ClassMethod Set(%val As %String) As %String [ CodeMode = objectgenerator ]
{
    do %code.WriteLine($c(9) _ "Set ^Test.String = %val")
    quit $$$OK
}

}

Example:

set obj = ##class(Test.Obj).%New()
write obj.prop
>0
set obj.prop=1
write obj.prop
>1
0
Eduard Lebedyuk  Apr 9, 2019 to Eduard Lebedyuk

Okay, how do I make this work with methods?

On property get/set I need to call instance method.

I've tried this:

Class Test.String Extends %String
{

Method Get() As %String [ CodeMode = objectgenerator ]
{
    do %code.WriteLine($c(9) _ "Quit ..Test()")
    quit $$$OK
}

}

And class:

/// set obj = ##class(Test.Obj).%New()
/// w obj.prop
Class Test.Obj Extends %RegisteredObject
{

Property prop As Test.String;

Method Test()
{
    quit $random(100)
}

}

But it fails to compile.

0
Robert Cemper  Apr 9, 2019 to Eduard Lebedyuk

instead of 

do %code.WriteLine($c(9) _ "Quit ..Test()")

use

do %code.WriteLine($c(9) _ "Quit zTest()")

It worked for me best

0
Eduard Lebedyuk  Apr 10, 2019 to Robert Cemper

Can you share the code please?

Changed .. to z and compilation fails for me.

0
Vitaliy Serdtsev  Apr 10, 2019 to Robert Cemper

It doesn't work for me too, there is an error

#1056: Missing subscript : '}'

0
Eduard Lebedyuk  Apr 10, 2019 to Vitaliy Serdtsev

Thank you!

NoContext helped. What does it mean?

This works too btw:

d %code.WriteLine("  q ..Test()")
0
Vitaliy Serdtsev  Apr 10, 2019 to Eduard Lebedyuk
NoContext helped. What does it mean?
NoContext With the same success it is possible to specify NoContext for the class:
Class Test.String Extends %String [ NoContext ]
{

</FONT><FONT COLOR="#000080">Method </FONT><FONT COLOR="#000000">Get() </FONT><FONT COLOR="#000080">As %String </FONT><FONT COLOR="#000000">[ </FONT><FONT COLOR="#000080">CodeMode </FONT><FONT COLOR="#000000">= objectgenerator ] {   </FONT><FONT COLOR="#0000ff">d </FONT><FONT COLOR="#800000">%code</FONT><FONT COLOR="#000000">.</FONT><FONT COLOR="#0000ff">WriteLine</FONT><FONT COLOR="#000000">(</FONT><FONT COLOR="#008000">"  q $this.Test()"</FONT><FONT COLOR="#000000">)   </FONT><FONT COLOR="#0000ff">q $$$OK </FONT><FONT COLOR="#000000">}

}</FONT>

This works too btw:
It doesn't work for me:

MPP5376 : Method or Property 'Test' does not exist in this class.

0
Eduard Lebedyuk  Apr 10, 2019 to Vitaliy Serdtsev

That's because first code generation is run during Test.String compilation, which does not have Test method.

Add something like this at the beginning:

#; don't generate any code if it not for a property
quit:%mode="method" $$$OK
0
Vitaliy Serdtsev  Apr 10, 2019 to Eduard Lebedyuk

Then it's better:

<FONT COLOR="#000080">Method </FONT><FONT COLOR="#000000">Get() </FONT><FONT COLOR="#000080">As %String </FONT><FONT COLOR="#000000">[ </FONT><FONT COLOR="#000080">CodeMode </FONT><FONT COLOR="#000000">= objectgenerator, NoContext]
{
  </FONT><FONT COLOR="#0000ff">d</FONT><FONT COLOR="#000000">:</FONT><FONT COLOR="#800000">%mode</FONT><FONT COLOR="#000000">=</FONT><FONT COLOR="#008000">"propertymethod" </FONT><FONT COLOR="#800000">%code</FONT><FONT COLOR="#000000">.</FONT><FONT COLOR="#0000ff">WriteLine</FONT><FONT COLOR="#000000">(</FONT><FONT COLOR="#008000">"  q ..Test()"</FONT><FONT COLOR="#000000">)
  </FONT><FONT COLOR="#0000ff">q $$$OK
</FONT><FONT COLOR="#000000">}</FONT>

0
Robert Cemper  Apr 10, 2019 to Vitaliy Serdtsev

Sorry,  I had more changes. 
My approach in details

Class DC.String Extends %String  {
ClassMethod Get() As %String [ CodeMode = objectgenerator ]
{    do %code.WriteLine($c(9) _ "Quit $g(^Test.String, 0)")
    quit $$$OK  }
ClassMethod Set(%val As %String) [ CodeMode = objectgenerator ] {
    do %code.WriteLine($c(9) _ "Set ^Test.String = %val")
    do %code.WriteLine($c(9) _ "do zTest()")
    quit $$$OK }
/// this generates Method propTest()
/// but satisfies the Compiler for this class
ClassMethod Test() { quit  ;; from Data definition }
}

compiles this routine:

 ;DC.String.1
 ;(C)InterSystems, generated for class DC.String. Do NOT edit. 10/04/2019 12:00:42PM
 ;;42345370;DC.String
 ;
zGet() public  Quit $g(^Test.String, 0) }
zSet(%val) public Set ^Test.String = %val
      do zTest() }
zTest() public  quit  ;; from Data definition }

and the  using class

Class DC.StringTest Extends %RegisteredObject  {
Property prop As DC.String;
Method Test()
 b   quit ;; from using Class }
}

compiled as 

 ;DC.StringTest.1
 ;(C)InterSystems, generated for class DC.StringTest. Do NOT edit. 10/04/2019 12:02:35PM
 ;;49614632;DC.StringTest
 ;
%NormalizeObject() public {
If '$system.CLS.GetModified() Quit 1
If m%prop Set:i%prop'="" i%prop=(..propNormalize(i%prop))
Quit 1 }
%ValidateObject(force=0,checkserial=1) public {
set sc=1
If '$system.CLS.GetModified() Quit sc
If m%prop Set iv=..prop If iv'="" Set rc=(..propIsValid(iv)) If ('rc) Set sc=$$EmbedErr^%occSystem(sc,rc,5802,"prop",iv)
Quit sc }
zTest() public {
 b   quit ;; from using Class
}
zpropGet() public {
Quit $g(^Test.String, 0) }
zpropSet(%val) public {
Set ^Test.String = %val
do zTest() }
zpropTest() public {
 quit  ;; from Data definition
}

and the test

 SAMPLES>s obj=##class(DC.StringTest).%New()
SAMPLES>set obj.prop=123
 b   quit ;; from using Class
 ^
<BREAK>zTest+1^DC.StringTest.1
SAMPLES 3d1>

Well, the code generator can be tricky.
It's not one of my favorites. But sometimes you have no choice.

0
Vitaliy Serdtsev  Apr 10, 2019 to Eduard Lebedyuk

Try this

<FONT COLOR="#000080">Class Test.String Extends %String
</FONT><FONT COLOR="#000000">{

</FONT><FONT COLOR="#000080">Method </FONT><FONT COLOR="#000000">Get() </FONT><FONT COLOR="#000080">As %String </FONT><FONT COLOR="#000000">[ </FONT><FONT COLOR="#000080">CodeMode </FONT><FONT COLOR="#000000">= objectgenerator, NoContext ] {   </FONT><FONT COLOR="#0000ff">d </FONT><FONT COLOR="#800000">%code</FONT><FONT COLOR="#000000">.</FONT><FONT COLOR="#0000ff">WriteLine</FONT><FONT COLOR="#000000">(</FONT><FONT COLOR="#008000">"  q $this.Test()"</FONT><FONT COLOR="#000000">)   </FONT><FONT COLOR="#0000ff">q $$$OK </FONT><FONT COLOR="#000000">}

}</FONT>

0
Robert Cemper  Apr 9, 2019 to Eduard Lebedyuk

OK.
StorageTo../..ToStorage only works in persistent classes where you move content from/to globals.
No chance without storage. 

It doesn't get called in Registered Classes and not when the object wasn't saved.
see my test with %Persistent

set obj=##class(Test.String).%New()
write 
obj.prop    ;nothing loaded yet
set obj.prop=77 write obj.prop   ; unchanged as neither stored nor loaded
77
do obj.%Save(),obj,%Reload() ; force reload to trigger StorageToLogical
write obj.prop  ; and here we go
prop


This proves that there is limited use of the approach.

0
Dmitry Maslennikov  Apr 10, 2019 to Eduard Lebedyuk

Sorry, usually I use it to add custom methods to properties. Did not not, that it doesn't work for such methods as Set/Get. But you can add any other method.

0