Optional Record Types, Templates
Hello everyone,
In the sample code below, you will find Source, Target, and Intermediate templates.
In this dummy use case, the intermediate template receives some optional record types and/or templates and extracts the fields of interest from them to create and fill the Target template accordingly.
Regardless of the time I spent on this, I could not be sure about the most efficient and proper way to do realize this simple idea. Moreover, things get easily uglier when a record type or template specified as input parameters contain some other templates or record types within them and maybe with some additional optional types.
I believe once you check the code & comments, the idea will become clearer to you. I’m open to any suggestions on the issue. Thank you very much for your interest and time in advance.
module Sample where
type Source_Key = (Party, Text, Int)
type Target_Key = (Party, Int)
type Intermediate_Key = (Party, Text)
data Passport = Passport with
name : Optional Text
age : Int
deriving(Eq, Show)
template Source
with
sign : Party
name: Text
age: Int
where
signatory sign
key (sign, name, age) : Source_Key
maintainer key._1
template Target
with
sign: Party
name: Text
age: Int
country: Text
gender: Text
where
signatory sign
key (sign, age) : Target_Key
maintainer key._1
template Intermediate
with
sign : Party
attr : Text
where
signatory sign
key (sign, attr) : Intermediate_Key
maintainer key._1
controller sign can
nonconsuming Create : ()
with
passport : Optional Passport
template_key : Optional Source_Key
do
case passport of -- check if passport is set
None -> -- pass.
return()
Some x ->
-- get fields of interest from the record type and assign them into corresponding set of variables.
return ()
case template_key of -- check if template key is set
None -> -- do pass
return ()
Some cid -> do
(_, fetched_template) <- fetchByKey @Source cid -- fetch the contract
-- get fields of interest from the fetched template type and assign them into corresponding set of variables.
return ()
-- finally create the Target template and fill it with the previously taken data.
create Target with sign = sign, name = fetched_template.name, age = passport.age..
return ()
Hi @Chenav 
The DA team is on a Christmas break so it might take a bit longer to get a reply.
Hope you understand 
Hi @nemanja, of course. Merry Christmas everyone! 
Hi @Chenav, how do you want to handle the case where passport and template key are None? Your comments suggest that you want to continue. However, you won’t be able to create the Target contract afterwards if those do not exist.
I see a few options:
- Abort if they are None. That works, however in that case I’m not sure why you would be accepting an
Optionalat all. - Continue but make some fields on Target optional to account for them being missing.
- Continue if at least one of them is set. In that case, what do you want to do if both are missing or if both are set but have different values for some fields?
- Continue but don’t create Target. In that case, I’d probably lean towards not making the fields optional in the first place.
Does one of those options cover what you had in mind?
As an alternative to having a single method with two optional arguments, you could have N methods (1-4) that match the expected valid combinations. Naming may get a bit verbose if there aren’t good, domain-specific terms for the operations, but that may help reduce confusion.
So, for example, if it is valid to specify either both or just a passport, you could do:
template Intermediate
with
sign : Party
attr : Text
where
signatory sign
key (sign, attr) : Intermediate_Key
maintainer key._1
controller sign can
nonconsuming Create : ()
with
passport : Passport
template_key : Source_Key
do
-- Both args are mandatory so you can count on them being here
create Target with ...
return ()
nonconsuming CreateWithPassport : ()
with
passport : Passport
do
-- get fields of interest from the record type and assign them into corresponding set of variables.
-- we know we don't have a template_key, so no need to check for it
create Target with ...
return ()
In effect, you’re moving the case statement on optional arguments ‘up’ one level in the code, while moving the responsibility for what to do if arguments are missing to the caller. This may in some cases provide a cleaner, easier-to-understand API, even though it will end up having more methods.