Copy and update record expression doesn't work - f#

I'm trying to copy an entity in a table using LinqPad.
let dc = new TypedDataContext()
let title = "Some title"
let newestUser= dc.UserIDs |> Seq.filter (fun(x)->x.Funktion="SomeFunction") |> Seq.last
let newUser= {newestUserwith Title= title}
The type 'UserID' does not contain a field 'Title'
But Title is a public field. I'm seeing it in the list by using GetFields()
Has anyone any idea what the reason of that is?

The with keyword is used to copy and update F# records. A record is a dumb set of fields containing values and no internal state, which is easy to copy. It cannot work on normal objects since there is no defined way to copy any given object.
The error message given by F# when trying to use this syntax on a normal object is The record label 'Title' is not defined. It would be more helpful if it was 'newestUser' is not a record.

Related

Defining element type in a table

so when i define the type of a variable, it goes totally alright:
local Player: Player = nil
but when i try to define the type of an element in a table, it doesnt go exactly how i thought it would:
PlayerProfile.Player: Player = nil
Missed symbol `(`.Luau Syntax Check.(miss-symbol)
this is my first time working with type so anyone know the correct way of doing this?
You can't arbitrary set type to random table member in Luau. You need to set types for all members on table creation or inside its creation scope.
On creation you can simply set type for each field directly:
type PlayerProfile = {Player: SomeType, OtherField: SomeOtherType}
Or you can express member types by first creating table with zero expression {} and assigning typed values to its members before leaving creation scope. But as soon as you leave that scope the table is "sealed" and no more changes are allowed.
local PlayerProfile = {}
PlayerProfile.Player = "string"
PlayerProfile.SomeField = 123 -- number, types are inferred from initialization values

DXL simple attribute return

i am new to DXL.
What i am trying to achieve:
I would like to create a DXL column in a module which displays the object's ForeignID plus a prefix.
What i tried:
Module m = current
Object o
for o in m do {
string s = o."ForeignID"
displayRich("Prefix " s)
}
but this only results in the entire list Prefix+ForeignID of all module's objects within each cell of the DXL column.
What do i need to change so every object will only show it's own Prefix+ForeignID within the DXL cell.
Thanks in advance for your help
You can get some information here: https://www.ibm.com/docs/en/ermd/9.7.1?topic=definitions-dxl-attributes-layout-dxl-columns. Also check the DXL reference which is linked on that page
The code in DXL Layout columns is executed for each Object, there is a variable called "obj", which points to the Object which is being calculated at the moment (N.B. that is NOT the "current" Object, which is the Object that the user has clicked on).
Your code would simply be
string s = obj."ForeignID"
displayRich("Prefix " s)
or as a one-liner
displayRich("Prefix " obj."ForeignID" "")
(in this case, display would suffice. displayRich is only need when you have RTF (formatted) text, like the one in "Object Text".)

Saving record in RavenDb with F# adding extra Id column

When I save a new F# Record, I'm getting an extra column called Id# in the RavenDb document, and it shows up when I load or view the object in code; it's even being converted to JSON through my F# API.
Here is my F# record type:
type Campaign = { mutable Id : string; name : string; description : string }
I'm not doing anything very exciting to save it:
let save c : Campaign =
use session = store.OpenSession()
session.Store(c)
session.SaveChanges()
c
Saving a new instance of a record creates a document with the Id of campaigns/289. Here is the full value of the document in RavenDb:
{
"Id#": "campaigns/289",
"name": "Recreating Id bug",
"description": "Hello StackOverflow!"
}
Now, when I used this same database (and document) in C#, I didn't get the extra Id# value. This is what a record looks like when I saved it in C#:
{
"Description": "Hello StackOverflow!",
"Name": "Look this worked fine",
}
(Aside - "name" vs "Name" means I have 2 name columns in my document. I understand that problem, at least).
So my question is: How do I get rid of the extra Id# property being created when I save an F# record in RavenDb?
As noted by Fyodor, this is caused by how F# generates a backing field when you create a record type. The default contract resolver for RavenDB serializes that backing field instead of the public property.
You can change the default contract resolver in ravendb. It will look something like this if you want to use the Newtonsoft Json.Net:
DocumentStore.Conventions.JsonContractResolver <- new CamelCasePropertyNamesContractResolver()
There is an explanation for why this works here (see the section titled: "The explanation"). Briefly, the Newtonsoft library uses the public properties of the type instead of the private backing fields.
I also recommend, instead of having the mutable property on the Id, you can put the [<CLIMutable>] attribute on the type itself like:
[<CLIMutable>]
type Campaign = { Id : string; name : string; description : string }
This makes it so libraries can mutate the values while preventing it in your code.
This is a combination of... well, you can't quite call them "bugs", so let's say "non-straightforward features" in both F# compiler and RavenDb.
The F# compiler generates a public backing field for the Id record field. This field is named Id# (a standard pattern for all F# backing fields), and it's public, because the record field is mutable. For immutable record fields, backing fields will be internal. Why it needs to generate a public backing field for mutable record fields, I don't know.
Now, RavenDb, when generating the schema, apparently looks at both properties and fields. This is a bit non-standard. The usual practice is to consider only properties. But alas, Raven picks up the public field named Id#, and makes it part of the schema.
You can combat this problem in two ways:
First, you could make the Id field immutable. I'm not sure whether that would work for you or RavenDb. Perhaps not, since the Id is probably generated on insert.
Second, you could declare your Campaign not as an F# record, but as a true class:
type Campaign( id: int, name: string, description: string ) =
member val Id = id with get, set
member val name = name
member val description = description
This way, all backing fields stay internal and no confusion will arise. The drawback is that you have to write every field twice: first as constructor argument, then as class member.

Equality test for a record type with generated fields

If a record type includes generated fields such as an auto-generated id and a timestamp, e.g.:
type UserCreated = {
Id: Guid
Name: string
CreationTime: Instant
}
what is the best way to write unit tests against this? It is not possible to simply assert that the record is equal to any particular value, because it cannot be known in advance what the Id and CreationTime values will be.
Possible solutions:
Make individual assertions for each field
pass a function into the user creation function that handles generation of ids and dates. Unit tests could then inject a stub function that returns pre-determined values. Or indeed make callers pass in an id and timestamp directly.
Use some sort of lenses library
Never auto-generate anything, with the client deciding all fields in advance
Use a custom equality comparer (this doesn't sound like a good idea at all)
Something else?
What is considered the best way to do this?
My approach would indeed be to pass a function into the user creation function that handles generation of ids and dates. If there are too many parameters that you have to pass in, then that's probably a clue that you need to refactor your design.
Here's the domain layer, for example:
// =================
// Domain
// =================
open System
type UserCreated = {
Id: Guid
Name: string
CreationTime: DateTime
}
// the generation functions are passed in
let createCompleteRecord generateGuid generateTime name =
{
Id = generateGuid()
Name = name // add validation?
CreationTime = generateTime()
}
In the application code, you'd use partial application to bake in the generators and create a useful function createRecord
// =================
// Application code
// =================
let autoGenerateGuid() = Guid.NewGuid()
let autoGenerateTime() = DateTime.UtcNow
// use partial application to get a useful version
let createRecord = createCompleteRecord autoGenerateGuid autoGenerateTime
let recForApp = createRecord "myname"
In the test code, you'd use partial application to bake in other generators and create a different function createRecordForTesting
// =================
// Test code
// =================
let explicitGenerateGuid() = Guid.Empty
let explicitGenerateTime() = DateTime.MinValue
// use partial application to get a useful version
let createRecordForTesting = createCompleteRecord explicitGenerateGuid explicitGenerateTime
let recForTest = createRecordForTesting "myname"
Assert.AreEqual(Guid.Empty,recForTest.Id)
Assert.AreEqual("myname",recForTest.Name)
Assert.AreEqual(DateTime.MinValue,recForTest.CreationTime)
and since the "generated" fields now have hard-coded values, you can also test the whole record equality logic too:
let recForTest1 = createRecordForTesting "myname"
let recForTest2 = createRecordForTesting "myname"
Assert.AreEqual(recForTest1,recForTest2)
You don't really test the record type itself - it's just dumb data. What you want to test are functions that operate on that data.
A function is simple to test when it's pure. Both calling Guid.NewGuid() and DateTime.Now, which is what I assume you do for the two problematic fields here, are side effects. So refactoring those two bits out into functions you pass in explicitly as arguments would be the way to go in my book (your bullet point 2). It would make the code simpler to test and a bit more pure (perhaps a bit less readable as well, I guess you need to balance that).
That said - for most test cases that would involve your record you should know the values of those fields when you arrange the test. It's only when you test the function that creates the record 'from nothing' that you don't know them beforehand, and for that case you could just check if the Name field has the expected value (so your bullet point 1 where necessary, comparing records 'as is' everywhere else).
Edit: I don't know if you missed it or not, but one other possibility is to define a dedicated comparison function in the test project, and ensure the 'generated' values are the same in both records before comparing them:
let compareWithoutGenerated (a: UserCreated) (b: UserCreated) =
a = { b with Id = a.Id; CreationTime = a.CreationTime }
And then:
WhateverUnit.Assert.True(compareWithoutGenerated a b)
Obviously this is uglyish, but I would say it's fair game for tests. Arguably there are better ways to do it, but at the very least, this one doesn't inflict test-induced damage on your production code.

How do I edit an XML file using type providers?

I understand how to retrieve data from an XML source using type providers. However, I need to then modify a particular part of the XML and save it to disk. I have tried assigning a value to the node using <- but the property is read-only.
For example:
let doc = MyXml.load fileName
doc.ItemId.Id <- "newId" // doesn't work
doc |> saveXml
There is a similar question for JSON where the suggestion is to create a new object, but this is specifically for XML.
While researching my question I found that you can use the .XElement accessor to get a reference to a mutable XElement object. Thus a solution is:
let doc = MyXml.load fileName
doc.ItemId.XElement.Element(XName.Get "Id").Value <- "newId" // tada
doc.XDocument.Save(...)
Note that you have to use the .XElement accessor on the parent if you're modifying a leaf node. This is because the leaf node's type is a primitive and doesn't have an .XElement accessor of its own. A slight shame, but I suppose it makes life easier on the other side when you want read-only access to the value.

Resources