I have inherited some Drools code and am trying to understand exactly how it is working.
The code in question should go through a number of dates from two different sources, and for one source find the earliest and the other source find the latest.
The code is:
when
$flags: Properties()
// these properties contain key/value pairs with dates (as Strings)
$root: Map()
Map.Entry( key == "someRequest", $someRequestValue : value) from $root.entrySet()
$someRequestMap: Map() from $someRequestValue
// this map contains some timestamps as Strings
$earliestTimestamp: String() from accumulate(
$value : String(),
init( String found = null; ),
action( found = found == null? $value : ($value.compareTo(found) < 0? $value: found); ),
result( found )
)
$latestTimestamp: String() from accumulate(
$value : String(),
init( String found = null; ),
action( found = found == null? $value : ($value.compareTo(found) > 0? $value: found); ),
result( found )
)
then
log("earliestTimestamp: {}", $earliestTimestamp);
String latestDate = (String) $flags.get($latestTimestamp);
log("latestDate: {}", latestDate);
end
The $flags Properties are populated as so:
when
$flags: Properties()
$root: Map()
Map.Entry( key == "someRequest", $someRequestValue : value) from $root.entrySet()
$someRequestMap: Map() from $someRequestValue
Map.Entry( key == "timestamp", $timestampValue: value) from $someRequestMap.entrySet()
Map.Entry( key == "data", $dataValue: value) from $someRequestMap.entrySet()
$data: Map() from $dataValue
Map.Entry( key == "simple", $simpleValue: value) from $data.entrySet()
$simple: Map() from $simpleValue
Map.Entry( key == "somedate", $somedateValue: value) from $simple.entrySet()
then
$someRequestMap.remove("data");
update($root);
$flags.put($timestampValue, $somedateValue);
insert ($timestampValue);
end
And the JSON which is input to the rules is as so (with the timestamp strings):
{
"someRequest": {
"data": {
"simple": {
"somedate": "13-01-2016",
}
},
"timestamp": "2016-01-01 12:34:56.789"
}
}
I can see that it gets all the String facts available in working memory to get earliestTimestamp.
I think that for latestTimestamp, since it is called in the RHS on $flags.get, it is actually finding all Strings in $flags.
However if I insert log outputs inside the two accumulate action sections, all I see is the timestamp strings from working memory (I don't see the $flags strings).
I am comfortable enough using Drools but this bit of code has confused me in terms of how it works with the Properties.
Any info appreciated, thanks.
Related
I am struggling in retrieving the results obtained from a survey built with survey_kit package. A SurveyResult object is supposed to contain a list of StepResults and the FinishReason. The StepResult contains a list of QuestionResults. I cannot access the StepResult in anyway.
Example proposed in the documentation:
SurveyKit
(
onResult: (SurveyResult result) {
//Read finish reason from result (result.finishReason)
//and evaluate the results }
)
I already tried to tap something like result.stepResult but no variable was suggested.
I'm not sure if its the best approach but after looking into SurveyKit I've found that the SurveyResult class holds a list of StepResult and each of them holds a list of QuestionResult which in turn holds a value field called valueIdentifier.
so you can Iterate over them and extract the values. but first, you need to ensure that all of your answers have value. for example:
answerFormat: const SingleChoiceAnswerFormat(
textChoices: [
TextChoice(text: 'option 1', value: '10'),
TextChoice(text: 'option 2', value: '-2'),
]
),
Then after you did that iterate over them like that:
onResult: (SurveyResult result) {
int score = 0;
for (var stepResult in result.results) {
for (var questionResultin stepResult.results) {
score += int.tryParse(questionResult.valueIdentifier ?? "0") ?? 0;
}
}
print("final Score is $score");
/* call a new widget to show the results*/
},
As you can see I use two null checks when adding a question value to the score.
The first one is because the value is of type "String?" (and not "String") and the second one is because the String value might be non-numeric.
Of course, after you have calculated the results you can show the result on a new screen with Navigator.
I'm migrating a code base to null safety, and it includes lots of code like this:
MyType convert(OtherType value) {
return MyType(
field1: value.field1,
field2: value.field2 != null ? MyWrapper(value.field2) : null,
);
}
Unfortunately, the ternary operator doesn't support type promotion with null checks, which means I have to add ! to assert that it's not null in order to make it compile under null safety:
MyType convert(OtherType value) {
return MyType(
field1: value.field1,
field2: value.field2 != null ? MyWrapper(value.field2!) : null,
);
}
This makes the code a bit unsafe; one could easily image a scenario where the null check is modified or some code is copied and pasted into a situation where that ! causes a crash.
So my question is whether there is a specific best practice to handle this situation more safely? Rewriting the code to take advantage of flow analysis and type promotion directly is unwieldy:
MyType convert(OtherType value) {
final rawField2 = value.field2;
final MyWrapper? field2;
if (rawField2 != null) {
field2 = MyWrapper(rawField2);
} else {
field2 = null;
}
return MyType(
field1: value.field1,
field2: field2,
);
}
As someone who thinks a lot in terms of functional programming, my instinct is to think about about nullable types as a monad, and define map accordingly:
extension NullMap<T> on T? {
U? map<U>(U Function(T) operation) {
final value = this;
if (value == null) {
return null;
} else {
return operation(value);
}
}
}
Then this situation could be handled like this:
MyType convert(OtherType value) {
return MyType(
field1: value.field1,
field2: value.field2.map((f) => MyWrapper(f)),
);
}
This seems like a good approach to maintain both safety and concision. However, I've searched long and hard online and I can't find anyone else using this approach in Dart. There are a few examples of packages that define an Optional monad that seem to predate null safety, but I can't find any examples of Dart developers defining map directly on nullable types. Is there a major "gotcha" here that I'm missing? Is there another approach this is both ergonomic and more conventional in Dart?
Unfortunately, the ternary operator doesn't support type promotion with null checks
This premise is not correct. The ternary operator does do type promotion. However, non-local variables cannot be promoted. Also see:
https://dart.dev/tools/non-promotion-reasons
"The operator can’t be unconditionally invoked because the receiver can be null" error after migrating to Dart null-safety.
Therefore you should just introduce a local variable (which you seem to have already realized in your if-else and NullFlatMap examples):
MyType convert(OtherType value) {
final field2 = value.field2;
return MyType(
field1: value.field1,
field2: field2 != null ? MyWrapper(field2) : null,
);
}
I want to update only those fields in mongo document that have values in the POJO. How do I do this?
I have a User collection with the following document:
{
_id: ObjectId("someId"),
name: "Foo",
age: 20,
gender: "male"
}
Now I want to update only the age to 22 so, I have a User pojo with age = 22 and _id = ObjectId("someId").
I don't want to use Updates.set("age",22) because then I'll have to handle every field. The field to be populated may be something other than age.
So, I used reflection to make a generic method to get the list of Update.sets for the pojo
classMembers = User::class.memberProperties
// get hashmap from pojo
val fieldsMap: HashMap<String, Any> =
mapper.convertValue(pojo, object : TypeReference<HashMap<String, Any>>() {}) ?:
throw CustomException( HttpStatus.BAD_REQUEST, "Could not parse request" )
// get hashmap from default/empty pojo
val defaultMap: HashMap<String, Any> =
mapper.convertValue(emptyPojo, object : TypeReference<HashMap<String, Any>>() {}) ?:
throw CustomException( HttpStatus.BAD_REQUEST, "Could not parse request" )
for (member in classMembers) {
val name = member.name
val value = fieldsMap[name]
//add to list only if the current value is not null or default and current value is different
if (value != null && defaultMap[member.name] != value && member.getter.annotations.isNotEmpty()) {
val field = (member.getter.annotations[0] as BsonProperty).value
setList.add(Updates.set(field, value))
}
}
This works. But I wanted to know if theres a better way to do it? A default way to do this via mongoClient or mongoTemplate or mongoRepository?
I am using linq expression trees to build a query.
My array:
string[] { null, null }
condition I want to implement:
x == null ? null : x.ToLower()
My linq expression looks like this:
{Param_0 => value(System.String[]).Any(Param_1 => (Param_0.FirstName.ToLower() == IIF((Param_1 == null), null, Param_1.ToLower())))}
This is my first attempt and I can't seem to find the correct way to do it
Constant = Expression.Condition(Expression.Equal(Constant, Expression.Constant(null, typeof(string))), Expression.Constant(null, typeof(string)), Expression.Call(Constant, "ToLower", null));
The expected result is to be able to call .ToLower() on elements that are not null
It seems to me that you want an expression that represents a function call with input a string, and output a string.
Expression<Func<string, string>>
How about a Lambda expression?
Expression<Func<string, string>> myExpression = (x) => (x==null) ? null : x.ToLower();
This expression can be used in a Queryable Select statement, like below:
IQueryable<string> myItems = new List<sring>()
{
"Abc",
null,
"DEF",
null,
"gHI",
}
.AsQueryable();
IQueryable<string> myLowerCaseItems = myItems.Select(myExpression);
foreach (string item in myLowerCaseItems)
{
if (item == null)
Console.WriteLine("<null>");
else
Console.WriteLine(item);
}
This yields the following output:
abc
def
ghi
I am trying to collect some objects in Drools, but I want to only collect objects which have the same attribute. To wit, imagine a TestData class:
public class TestData {
int number;
String name;
//Getters, setters, rest of class
}
I want to use collect to get all TestDatas which have the same name. For the following data set, I want a rule which can collect the first two (both having the name 'Test1') and the second two ('Test2') as separate collections.
custom.package.TestData< number: 1, name:'Test1' >
custom.package.TestData< number: 2, name:'Test1' >
custom.package.TestData< number: 3, name:'Test2' >
custom.package.TestData< number: 4, name:'Test2' >
Something like
rule "Test Rule"
when
$matchingTestDatas : ArrayList( size > 1 ) from collect ( TestData( *magic* ) )
then
//Code
end
But obviously that won't work without the magic- it gets me an array of all the TestData entries with every name. I can use that as the basis for a rule, and do extended processing in the right hand side iterating over all the test data entries, but it feels like there should be something in drools which is smart enough to match this way.
Presumably the "magic" is just:
TestData( name == 'Test1' )
or
TestData( name == 'Test2' )
... for each of the collections. But that seems too obvious. Am I missing something?
Based on the clarification from the OP in the comments on this answer, it would appear that a Map of collections is required, keyed from the name. To support this, accumulate is required, rather than collect.
$tests: HashMap()
from accumulate( $test : TestData( name == $name ),
init( HashMap tests = new HashMap(); ),
action(
if (tests.get($name) == null) tests.put($name, new ArrayList();
tests.get($name).add($test);
),
reverse(
tests.get($name).remove($test);
),
result( tests )
);