Eduard Lebedyuk · Jul 29, 2016 go to post

If they are defined in one class, try:

ClientMethod logout() [ Language = javascript ]  {   this.DoLogout();  } 
Eduard Lebedyuk · Jul 28, 2016 go to post

Okay, then you need to use your original storage definition, but when selecting invalid, select it in a NVL function:

SELECT
ID, Code, Description, NVL(Invalid, 0)
FROM Wendy.LTCodes

It should return 0 instead of empty string.

Eduard Lebedyuk · Jul 26, 2016 go to post

These changes fixed it:

  • Removed highlited part of storage definition
  • Made invalid property Transient
  • Added 0 default to $Get
Class Wendy.LTCodes Extends %Persistent [ StorageStrategy = LTCStorage ]
{

Property Code As %String;

Property Description As %String;

Property Invalid As %Library.Boolean [ SqlComputeCode = { set {*} = ##class(Wendy.LTCodes).GetInvalid({Code})}, SqlComputed, Transient ];

Index CodeIndex On Code [ IdKey, PrimaryKey, Unique ];

ClassMethod GetInvalid(WSCode) As %Boolean
{
    Quit $G(^LTCODES(WSCode,"INVALID"),0)
}

Method InvalidGet() As %Boolean
{
    Quit ..GetInvalid(i%Code)
}

Method InvalidSet(value As %Boolean) As %Status
{
    Set ^LTCODES(i%Code,"INVALID")=value
    quit $$$OK
}

/// Do ##class(Wendy.LTCodes).SetData()
ClassMethod SetData()
{
    kill ^LTCODES
    S ^LTCODES("N001")="ANYOLD DESC"
    S ^LTCODES("N001","INVALID")=1
    S ^LTCODES("N002")="C5 REPEAT 1"
    S ^LTCODES("N111")="SPECIMEN COMMENT"
    S ^LTCODES("N200")="MSUD SCREEN|MSUD"
    S ^LTCODES("N500")="Sickle Cell Screen"
}

Storage LTCStorage
{
<SQLMap name="LTCMap">
<Data name="Description">
<Delimiter>"|"</Delimiter>
<Piece>1</Piece>
</Data>
<Global>^LTCODES</Global>
<Subscript name="1">
<Expression>{Code}</Expression>
</Subscript>
<Type>data</Type>
</SQLMap>
<StreamLocation>^Wendy.LTCodesS</StreamLocation>
<Type>%CacheSQLStorage</Type>
}

}
Eduard Lebedyuk · Jul 26, 2016 go to post

I imported your example, executed:

Do ##class(Wendy.LTCodes).SetData()

Then executed this sql:

SELECT
ID, Code, Description, Invalid
FROM Wendy.LTCodes

and received the following results:


ID Code Description   Invalid
N001 N001 ANYOLD DESC 1
N002 N002 C5 REPEAT 1  
N111 N111 SPECIMEN COMMENT  
N200 N200 MSUD SCREEN  
N500 N500 Sickle Cell Screen  

seems to be working.

 

But then I didn't really understand the use of Parameter InvalidGLVN = "^Utils.GlobalPropP";

it's for use with indirection.  Example:

set ^Utils.GlobalPropP = 123
set glvn = "^Utils.GlobalPropP"
write glvn
> ^Utils.GlobalPropP
write @glvn
> 123
Eduard Lebedyuk · Jul 26, 2016 go to post

Your error:

<INVALID 0REF>zSrvGetData+2

Points to the line;

set item = ..%GetComponentById("Item").value

And means that the result of this call:

..%GetComponentById("Item") 

Is not an object, so you can't access a property "value".

Eduard Lebedyuk · Jul 26, 2016 go to post

You can define a property of  %Stream.Object class and store %Stream.FileBinary/%Stream.FileCharacter there.

Eduard Lebedyuk · Jul 25, 2016 go to post

I removed InvalidGet method and object access to the property stopped working.

Class Utils.GlobalProp Extends %Persistent
{

Parameter InvalidGLVN = "^Utils.GlobalPropP";

Property Invalid As %String [ SqlComputeCode = {set {*} = ##class(Utils.GlobalProp).InvalidStatic()}, SqlComputed, Transient ];

ClassMethod InvalidStatic() As %String
{
    Return $Get(@..#InvalidGLVN)
}

Method InvalidSet(val As %String) As %Status
{
    Set @..#InvalidGLVN = val
    Return $$$OK
}

/// Do ##class(Utils.GlobalProp).Test()
ClassMethod Test()
{
    Do ..%KillExtent()
    Set obj = ..%New()
    Write "Invalid old: " _ obj.Invalid,!
    Set obj.Invalid = $Random(100)
    Write "Invalid new: " _ obj.Invalid,!
    Do obj.%Save()
    Kill obj
    &sql(SELECT Invalid INTO :invalid FROM Utils.GlobalProp WHERE Id = 1)
    Write "SQLCODE: " _ SQLCODE,!
    Write "Invalid sql: " _ invalid,!
}

Outputs:

Invalid old:
Invalid new:
SQLCODE: 0
Invalid sql: 65

Related Int code:

zInvalidCompute(%id)
    New %tException,%val set %val = ""
    try {
    set %val = ##class(Utils.GlobalProp).InvalidStatic()
    } catch %tException { throw %tException }
    Quit %val
zInvalidGet() public {
    If i%Invalid = "" { Set ..Invalid=..InvalidCompute($listget(i%"%%OID")) } Quit i%Invalid }
zInvalidSQLCompute()
    // Compute code for field Invalid
 set %d(2) = ##class(Utils.GlobalProp).InvalidStatic()
 QUIT

    Do $System.Status.DisplayError(tStatus)
        Write !
    }
    Quit
}
}
Eduard Lebedyuk · Jul 23, 2016 go to post

If our SVN repository already is storing discrete .cls files, does Atelier do any conversion when we load from SVN into our server instance?

No, except maybe for repository structure. That depends is Atelier + EGit support repository structure you use. For how to use EGit with Atelier check this article.

At what point would I see .udl files? I am thinking I would only see that if SVN was storing .xml and each of those would be converted to .udl.

There are no .udl files. There are just cls/mac/inc etc files in udl format, meaning they are represented on disk as is and not in the xml format. The extension would be .cls and so on. Here's the sample repository created with Atelier.

Eduard Lebedyuk · Jul 22, 2016 go to post

Getter and Setter are object related concepts, SQL does not use them. You can, however  specify SqlComputeCode  for SELECT access to a property. This example stores and retrieves Invalid property value from ^Utils.GlobalPropP global.

Class Utils.GlobalProp Extends %Persistent
{

Parameter InvalidGLVN = "^Utils.GlobalPropP";

Property Invalid As %String [ SqlComputeCode = {set {*} = ##class(Utils.GlobalProp).InvalidStatic()}, SqlComputed, Transient ];

Method InvalidGet() As %String
{
    Return ..InvalidStatic()
}

ClassMethod InvalidStatic() As %String
{
    Return $Get(@..#InvalidGLVN)
}

Method InvalidSet(val As %String) As %Status
{
    Set @..#InvalidGLVN = val
    Return $$$OK
}

/// Do ##class(Utils.GlobalProp).Test()
ClassMethod Test()
{
    Do ..%KillExtent()
    Set obj = ..%New()
    Write "Invalid old: " _ obj.Invalid,!
    Set obj.Invalid = $Random(100)
    Write "Invalid new: " _ obj.Invalid,!
    Do obj.%Save()
    Kill obj
    &sql(SELECT Invalid INTO :invalid FROM Utils.GlobalProp WHERE Id = 1)
    Write "SQLCODE: " _ SQLCODE,!
    Write "Invalid sql: " _ invalid,!
}
}

Code on GitHub.

Eduard Lebedyuk · Jul 21, 2016 go to post

I tried a lot of open-sourced/generally available Source Control hooks for Studio/git integration and cache-tort-git offers the best wokflow as you don't need to switch from studio window for ~95% of source  control usage cases.

Eduard Lebedyuk · Jul 20, 2016 go to post

%ZEN.proxyObject works alright with first empty element in a list. Here's a sample:

set json="{""Choices"":["""",10,20,30]}"
do ##class(%ZEN.Auxiliary.jsonProvider).%ConvertJSONToObject(json,,.obj)
do ##class(%ZEN.Auxiliary.jsonProvider).%ObjectToJSON(obj)

{
        "Choices":["",10,20,30
        ]
}

As for converting persistent objects to/from json, I would recommend first getting an example of json:

set obj = ##class(Driver.Entity).%OpenId(id)
do ##class(%ZEN.Auxiliary.jsonProvider).%ObjectToJSON(obj)

Would output json from object of  Driver.Entity class. Then you can modify your json, so it would have the same structure. The important part is that json should contain _class property = Driver.Entity, this way %ConvertJSONToObject knows which class to convert json into.

Eduard Lebedyuk · Jul 20, 2016 go to post

You can determine class based on a path by enforcing one standard of internal<->external name conversion.

So you have two methods:

ClassMethod GetExternalName(InternalName) As %String {}
ClassMethod GetInternalName(ExternalName) As %String {}

And the value of expressions:

Write InternalName=..GetInternalName(..GetExternalName(InternalName))
Write ExternalName=..GetExternalName(..GetInternalName(ExternalName))

Is always 1 for any valid InternalName/ExternalName.

Eduard Lebedyuk · Jul 19, 2016 go to post

That's useful.

set %Stream=##class(%Stream.TmpCharacter).%New() 

Is there any particular reason to use % variable here? I think local variable would be enough.

Eduard Lebedyuk · Jul 19, 2016 go to post

For every query (which can be a simple SQL query or a custom class query, here’s my post about them and their uses) QueryFunc method gets generated:

ClassMethod QueryFunc(Arg1, Arg2) As %SQL.StatementResult

which returns a %SQL.StatementResult used to iterate over the query. For example your Display query for LastName.BasicClassQuery class can be called from object context with this code:

Set ResultSet=##class(LastName.BasicClassQuery).DisplayFunc()
While ResultSet.%Next() { Write ResultSet.Name,! }
Eduard Lebedyuk · Jul 18, 2016 go to post

I don't think SPA works with browser back/forward buttons. You need to code back/forward buttons in your application.

Eduard Lebedyuk · Jul 18, 2016 go to post

This is not, generally a good idea to insert potentially long and slow code inside of object constructor.

Why? %OnNew should contain code which is absolutely required on object initiation regardless of the execution speed. There is no use case for this class to construct an object and not call %Connect, so %Connect should be moved into %OnNew. That way client code needs to make one mandatory call instead of two.

Eduard Lebedyuk · Jul 18, 2016 go to post

Why use JSON object instead of a usual signature?

Method %Connect(IP = "127.0.0.1", Port = {^%SYS("SSPort")}, Namespace = "%SYS", Username, Password, ClientIP, ClientPort ) As Sample.RemoteProxy
{
}

Also, it can be moved into the %OnNew method.

Eduard Lebedyuk · Jul 18, 2016 go to post

Here's working example:

Class Sample.XSLTransform [ Abstract ]
{

ClassMethod test(tData = "<HHSOS><DIAGNOSES><DIAGNOSIS_DATA><DIAGNOSIS_DATA_GUID>3762875</DIAGNOSIS_DATA_GUID><DIAGNOSIS_DATA_GUID>37628752</DIAGNOSIS_DATA_GUID></DIAGNOSIS_DATA><DIAGNOSIS_DATA></DIAGNOSIS_DATA><DIAGNOSIS_DATA></DIAGNOSIS_DATA><DIAGNOSIS_DATA_GUID>37628753</DIAGNOSIS_DATA_GUID></DIAGNOSES></HHSOS>", tSelect = "//DIAGNOSIS_DATA_GUID[1]", tXSL = "ExampleXSL")
{
    set tXML= ##class(%GlobalCharacterStream).%New()
    do tXML.Write(tData)
    set tXSL=##class(%Dictionary.CompiledXData).%OpenId($classname() _ "||" _ tXSL ).Data

    kill tParams
    set tParams("selectParam") = tSelect
    set tSC=##class(%XML.XSLT.Transformer).TransformStream(tXML,tXSL,.tOutput,,.tParams)
    zwrite tSC
    set tSC=tOutput.OutputToDevice()
}

XData ExampleXSL
{
<?xml version="1.0"?>

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:param name="selectParam"/>
<xsl:template match="/">
    <xsl:copy-of select="$selectParam"/>
</xsl:template>
</xsl:stylesheet>
}

}

Example:

Do ##class(Sample.XSLTransform).test()
tSC=1
<?xml version="1.0" encoding="UTF-8"?><DIAGNOSIS_DATA_GUID>3762875</DIAGNOSIS_DATA_GUID><DIAGNOSIS_DATA_GUID>37628753</DIAGNOSIS_DATA_GUID>
Eduard Lebedyuk · Jul 17, 2016 go to post

tParams should be an array:

set tParms("tExperssion") = tExperssion
set tParms("tIndex") = tIndex

and this line:

<xsl:copy-of select="$tExperssion"/>

should maybe be:

<xsl:value-of select="$tExperssion"/>
Eduard Lebedyuk · Jul 15, 2016 go to post

You can change wildcard programmatically, see this topic. Though I think the better solution would be subclassing and calculating wildcard there. 

Eduard Lebedyuk · Jul 15, 2016 go to post

Time Stamp Specifications for Filenames - while configuring business operations and business services that transmit data to and from files, you can often specify input and output filenames in a string that includes date and time format codes, such as %Y%M%d%h%m%s_%f.txt. At runtime, the format codes within this string resolve dynamically based on the current date and time.

Eduard Lebedyuk · Jul 15, 2016 go to post

That's generally a questionable idea, here's why:

  • It's the slowest possible method - if we're iterating over result set the application logic may decide to end processing not after the last row but earlier. If a whole result set is returned, then we still spent CPU/RAM to calculate something we may not even need
  • Result set may be quite large which would cause application logic errors
  • Result set may be larger than amount of RAM available
  • Time to first result is usually smaller than getting all results, so the user can start working with the first row, while an application fetches more rows