Timothy Leavitt · Apr 12, 2017 go to post

The problem with using the strategy from that code snippet on breakpoints is that they're separate persistent objects - the approach in the code snippet is to make a temporary change to the object that will be exported, but this doesn't seem to work (after a few attempts with different approaches) for breakpoints without breaking things in Studio.

Having project items sorted alphabetically is helpful to avoid conflicts in source control with multiple people working on the same project at the same time.

Example:

User A adds a file. User B adds a file. User A commits their change. User B goes to commit their change and can't because a conflict is detected due to User A's change to the same line of code (because both added new project items at the end of the list).

Timothy Leavitt · Apr 14, 2017 go to post

Perhaps consider using SOAP logging (see documentation) to compare a valid request from a web client that works (using a Caché-based client or a third-party tool like SoapUI) to what your PHP client is sending - that might make it more obvious how the correct XML differs from what PHP is sending.

Timothy Leavitt · May 24, 2017 go to post

Thanks Vitaliy - that's really interesting.

It's also fascinating what Google Translate does to Caché ObjectScript!

    if  ex The Name "<the METHOD of DOES the NOT EXIST>"  
      s  st $$$ the ERROR $$$ MethodDoesNotExist method 

smiley

Timothy Leavitt · Jul 11, 2017 go to post

That does look useful, but I'm looking to compare all the globals in two databases (the output of different but theoretically equivalent build processes) to see if some globals are present from one / missing in another or if the contents of any are different, and (if so) what the differences are.

Timothy Leavitt · Aug 18, 2017 go to post

After actually trying out my own suggestion, I think this would actually be better:

ClassMethod OnLogin() As %Status
{
    #dim %session As %CSP.Session
    If $Get(^ZPMGSYSTEM("%DOWNFLAG")) = 1 {
        Set %session.EndSession = 1
    }
    Quit $$$OK
}

My original suggestion doesn't actually end the session, it just results in an error response for one request. Trying to load the desired page again seems to actually work in that case.

Timothy Leavitt · Aug 21, 2017 go to post

A simpler workaround seems to be:

if $get(seen(""_$THIS)) quit

And here's another way to demonstrate that something weird is going on under the hood:

SAMPLES>k
 
SAMPLES>d $system.OBJ.ShowObjects()
No registered objects
 
SAMPLES>s obj = ##class(Test.Store).%OpenId(1)
 
SAMPLES>d $system.OBJ.ShowObjects()
Oref      Class Name                    Ref Count
----      ----------                    ---------
1         Test.Store                    1
 
SAMPLES>d obj.SomeMethod()
 
SAMPLES>d $system.OBJ.ShowObjects()
Oref      Class Name                    Ref Count
----      ----------                    ---------
1         Test.Store                    2
 
SAMPLES>k obj
 
SAMPLES>d $system.OBJ.ShowObjects()
Oref      Class Name                    Ref Count
----      ----------                    ---------
1         Test.Store                    1

Interestingly, $data doesn't seem to have the same issue.

Timothy Leavitt · Aug 22, 2017 go to post

Because Zen is written in XML, "&#44;" in an attribute actually does get turned in to a comma in the generated code (see %CreatePage in the .INT code), and then is treated like a comma when the column headers are $piece'd out.

Timothy Leavitt · Aug 22, 2017 go to post

That's not actually the case. If you look at the .INT code for the generated %CreatePage method of a Zen page with &#65292; in columnHeaders (I tested this out with ZENDemo.FormDemo in the SAMPLES namespace), you'll see something like:

Set dtCmb7.columnHeaders="House number,apartment"

The numeric character reference is converted to the Unicode character when the Zen XML is imported; the reason &#44; doesn't work is that it's converted to a plain old comma, which is no different from just putting a comma in the XML in terms of what Zen does with it.

Timothy Leavitt · Sep 20, 2017 go to post

I can't speak to a fix, but a few thoughts:

  • There is a critical difference between Studio and Atelier projects: Atelier projects are not even present on the server / in the database. This may prevent them from being used interchangeably with Studio projects for purposes of your source control extension and change control processes.
    • Because of this difference, it would probably not be a good idea to populate ^||%Studio.Project with the name of the project in Atelier.
  • One option might be updating your source control extension (perhaps adding new menu items and UserAction/AfterUserAction handling for them) to support creating a %Studio.Project and associating/dissociating files with one, even from Atelier. This would support mixed use of Studio and Atelier and make migration simpler.
  • If you want to be able to detect in your source control extension code whether it is being called from Studio or Atelier, you can look at the StudioVersion property (defined in %Studio.Extension.Base). Particularly, this will contain the string "Atelier" if invoked from Atelier.

A nice enhancement for Atelier might be supporting a project definition that is shared between the client and server.

Timothy Leavitt · Dec 12, 2017 go to post

Thanks Eduard, this looks promising. I'll try it out and post any interesting results.

Timothy Leavitt · Mar 20, 2018 go to post

What are you trying to do? This?

USER>Set rm=##class(%Regex.Matcher).%New("[0-9]","Word 123456")

USER>Write rm.ReplaceAll(".")
Word ......

Or:

USER>Set rm=##class(%Regex.Matcher).%New("[0-9]+","Word 123456")

USER>Write rm.ReplaceAll(".")
Word .
Timothy Leavitt · Apr 30, 2018 go to post

For completeness, here's an extension of the above example that actually works the way you'd expect (using PublicList to handle scope, and New to avoid leaking any variables):

Class DC.Demo.Indirection
{

ClassMethod Driver() [ PublicList = x ]
{
    New x
    Do ..Run()
    Do ..RunSets()
    Do ..Run()
}

ClassMethod Run() [ PublicList = x ]
{
    Set x = 5
    Try {
        Write @"x"
    } Catch e {
        Write e.DisplayString()
    }
    Write !
    Try {
        Xecute "write x"
    } Catch e {
        Write e.DisplayString()
    }
    Write !
}

ClassMethod RunSets() [ PublicList = x ]
{
    Set x = 5
    Set @"x" = 42
    Write x,!
    Xecute "set x = 42"
    Write x,!
}

}

Results:

USER>d ##class(DC.Demo.Indirection).Driver()
5
5
42
42
5
5
 
USER>w

USER>

But that doesn't mean it's a good idea to do things like this.

Timothy Leavitt · May 9, 2018 go to post

With a dynaForm, this would look like:

Class DC.Demo.ZenLocalizationModel Extends %ZEN.DataModel.ObjectDataModel
{

Property Descrição As %String(ZENATTRS = "requiredMessage:obrigatório.") [ Required ];

}

And:

Class DC.Demo.ZenLocalization Extends %ZEN.Component.page
{

XData Contents [ XMLNamespace = "http://www.intersystems.com/zen" ]
{
<page xmlns="http://www.intersystems.com/zen">
<dataController id="myController" modelClass="DC.Demo.ZenLocalizationModel" />
<dynaForm controllerId="myController" injectControls="before">
<submit caption="Enviar" />
</dynaForm>
</page>
}

}

For more information on how to customize data model behavior with property parameters, see the class reference for %ZEN.DataModel.objectModelParameters.

Timothy Leavitt · May 18, 2018 go to post

Thanks for pointing that out, but it's not really ideal for my use case, since it's not a % class and explicitly documented "Only to be used in the context of DataCheck." I think I'd also like to permit extended global references, which my original approach does.

Timothy Leavitt · Jan 2, 2019 go to post

My top place by day was 242 on day 8 (that was the only day that I actually did the puzzle at midnight though - I'm not at all a night owl).

My three wishes for ObjectScript would be:

  1. A greater variety of high-performance data structures with low-level support (rather than shoehorning everything into a local array/PPG/$ListBuild list to keep it wicked fast, or building more expensive objects that do exactly what I want)
  2. Libraries for working with those (and existing) data structures (or, better yet, an OO approach to everything, similar to %Library.DynamicArray/%Library.DynamicObject)
  3. Functional programming capabilities
Timothy Leavitt · Jan 3, 2019 go to post

Generally speaking, I'd design this as a relationship to a set of "roles" that can be added or removed. (What if the person ceases to be a Doctor?) Methods of the person would dispatch to the roles.

Timothy Leavitt · Mar 8, 2019 go to post

Hi Robert,

Access rights are part of my concern. You can connect using an SSL/TLS configuration without having read permission on %DB_CACHESYS (or the IRIS equivalent).

Timothy Leavitt · Mar 12, 2019 go to post

In my experience, code coverage measurement is useful even in development. It helps identify areas that are untested as you're writing tests.

Timothy Leavitt · Apr 1, 2019 go to post

A few weird things with custom login pages:

CSPSystem needs read permission on the DB containing them. (You'll need to close CSPGateway connections after making this change so that it can reconnect with the right privileges.)

It's specified with the ".cls" extension in the web application configuration, not just the classname.

Timothy Leavitt · Sep 4, 2019 go to post

With correct web server configuration to route everything through the CSPGateway, the above example should handle URLs for other resources like that without issue (as long as the content served by the dashboard server has relative links to those endpoints, not absolute) - that's the point of the <base> element.

In my sample use case, it also handles several requests for images and other assets.

Timothy Leavitt · Nov 21, 2019 go to post

This is nifty! Note, you can make the extent manager happy by using:


Class DC.Demo.SometimesPersistent Extends %Persistent
{

Property Foo As %String;

ClassMethod Demo()
{
    New %storage,%fooD,%fooI,%fooS
    Set obj = ##class(DC.Demo.SometimesPersistent).%New()
    Set obj.Foo = "bar"
    Set %storage = "%foo"
    Write !,obj.%Save()
    Kill obj
    Set obj = ..%OpenId(1)
    w ! zw obj
    zw %fooD
}

Storage Default
{
<Data name="SometimesPersistentDefaultData">
<Value name="1">
<Value>%%CLASSNAME</Value>
</Value>
<Value name="2">
<Value>Foo</Value>
</Value>
</Data>
<DataLocation>@($Get(%storage,"^DC.Demo.SometimesPersistent")_"D")</DataLocation>
<DefaultData>SometimesPersistentDefaultData</DefaultData>
<IdLocation>@($Get(%storage,"^DC.Demo.SometimesPersistent")_"D")</IdLocation>
<IndexLocation>@($Get(%storage,"^DC.Demo.SometimesPersistent")_"I")</IndexLocation>
<StreamLocation>@($Get(%storage,"^DC.Demo.SometimesPersistent")_"S")</StreamLocation>
<Type>%Library.CacheStorage</Type>
}

}