Skip to content

Commit

Permalink
Add MapToObject functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
ipavlic committed Oct 30, 2021
1 parent cc6cb5c commit 94b2d36
Show file tree
Hide file tree
Showing 17 changed files with 367 additions and 23 deletions.
15 changes: 15 additions & 0 deletions sfdx-source/apex-fp/main/classes/collection/ObjectCollection.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
public with sharing class ObjectCollection {
private List<Object> objects;

public ObjectCollection(List<Object> objects) {
this.objects = objects;
}

public List<Object> asList() {
return new List<Object>(objects);
}

public Set<Object> asSet() {
return new Set<Object>(objects);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>52.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,14 @@ public with sharing class SObjectCollection {
return SObjectCollection.of(mapped);
}

public ObjectCollection mapAll(SObjectToObjectFunction fn) {
List<Object> mapped = new List<Object>();
for (SObject record : records) {
mapped.add(fn.call(record));
}
return new ObjectCollection(mapped);
}

public SObjectCollection mapSome(SObjectPredicate predicate, SObjectToSObjectFunction fn) {
List<SObject> transformed = new List<SObject>();
for (SObject record : records) {
Expand Down
6 changes: 5 additions & 1 deletion sfdx-source/apex-fp/main/classes/function/Fn.cls
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@ public with sharing class Fn {

public static SObjectPredicate NotNull = new NotNull();

public static MapToSObject MapToSObject(Schema.SObjectType type) {
public static MapToSObject MapTo(Schema.SObjectType type) {
return new MapToSObject(type);
}

public static MapToObject MapTo(Type type) {
return new MapToObject(type);
}

public static Match Match = new Match();

public class Match {
Expand Down
75 changes: 75 additions & 0 deletions sfdx-source/apex-fp/main/classes/function/MapToObject.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
public with sharing class MapToObject implements SObjectToObjectFunction {
Type type;
Map<String, String> fieldMappings;
SObjectFieldReader fieldReader;
Map<String, Object> fieldValues = new Map<String, Object>();

public MapToObject(Type type) {
this.type = type;
this.fieldValues = new Map<String, Object>();
this.fieldMappings = new Map<String, String>();
this.fieldReader = new SObjectFieldReader();
}

private Object setField(Map<String, Object> objectData, String path, Object value) {
Integer dotPos = path.indexOf('.');
if (dotPos == -1) {
return objectData.put(path, value);
} else {
String childObject = path.substring(0, dotPos);
String remainingPath = path.substring(dotPos + 1, path.length());

if (!objectData.containsKey(childObject)) {
objectData.put(childObject, new Map<String, Object>());
}
Map<String, Object> childObjectData = (Map<String, Object>) objectData.get(childObject);
return setField(childObjectData, remainingPath, value);
}
}

public MapToObject setField(String targetFieldPath, Object value) {
setField(fieldValues, targetFieldPath, value);
return this;
}

public MapToObject setFields(Map<String, Object> fieldValues) {
for (String targetFieldPath : fieldValues.keySet()) {
setField(targetFieldPath, fieldValues.get(targetFieldPath));
}
return this;
}

public MapToObject mapField(String targetFieldPath, String sourceFieldRelation) {
fieldMappings.put(targetFieldPath, sourceFieldRelation);
return this;
}

public MapToObject mapField(String targetFieldPath, Schema.SObjectField sourceField) {
fieldMappings.put(targetFieldPath, sourceField.getDescribe().getName());
return this;
}

public MapToObject mapFields(Map<String, Schema.SObjectField> fieldMappings) {
for (String targetFieldPath : fieldMappings.keySet()) {
Schema.SObjectField sourceField = fieldMappings.get(targetFieldPath);
mapField(targetFieldPath, sourceField);
}
return this;
}

public MapToObject mapFields(Map<String, String> fieldMappings) {
for (String targetFieldPath : fieldMappings.keySet()) {
String sourceFieldRelation = fieldMappings.get(targetFieldPath);
mapField(targetFieldPath, sourceFieldRelation);
}
return this;
}

public Object call(SObject record) {
for (String targetFieldPath : fieldMappings.keySet()) {
String sourceFieldRelation = fieldMappings.get(targetFieldPath);
setField(targetFieldPath, fieldReader.read(record, sourceFieldRelation));
}
return JSON.deserialize(JSON.serialize(fieldValues), type);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>52.0</apiVersion>
<status>Active</status>
</ApexClass>
8 changes: 8 additions & 0 deletions sfdx-source/apex-fp/main/classes/function/MapToSObject.cls
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ public with sharing class MapToSObject implements SObjectToSObjectFunction {
return this;
}

public MapToSObject mapFields(Map<Schema.SObjectField, String> fieldMappings) {
for (Schema.SObjectField targetField : fieldMappings.keySet()) {
String sourceFieldRelation = fieldMappings.get(targetField);
mapField(targetField, sourceFieldRelation);
}
return this;
}

public MapToSObject setField(String fieldName, Object value) {
fieldValues.put(fieldName, value);
return this;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
public interface SObjectToObjectFunction {
Object call(SObject record);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>52.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
Expand Up @@ -723,4 +723,21 @@ private class SObjectCollectionTest {
OptionalSObject maybeBaz = accountCollection.find(Fn.Match.field(Account.Name).equals('Baz'));
System.assertEquals(false, maybeBaz.isPresent());
}

private class MappingTarget {
public String name;
}

@IsTest
private static void testMapAllToObjects() {
List<MappingTarget> result = (List<MappingTarget>) SObjectCollection.of(new List<Account>{new Account(Name = 'foo'), new Account(Name = 'bar')})
.mapAll(
Fn.MapTo(MappingTarget.class).mapField('name', Account.Name)
)
.asList();

System.assertEquals(2, result.size());
System.assertEquals('foo', result[0].name);
System.assertEquals('bar', result[1].name);
}
}
80 changes: 80 additions & 0 deletions sfdx-source/apex-fp/test/classes/function/MapToObjectTest.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
@IsTest(isParallel=true)
private class MapToObjectTest {

private class MappingTarget {
public String name;
public MappingTarget child;
}

@IsTest
private static void setFieldSetsFieldValue() {
MapToObject m = new MapToObject(MappingTarget.class).setField('name', 'foo');
MappingTarget mapped = (MappingTarget) m.call(new Account());
System.assertEquals('foo', mapped.name);
}

@IsTest
private static void setFieldSetsNestedFieldValue() {
MapToObject m = new MapToObject(MappingTarget.class).setField('child.name', 'foo');
MappingTarget mapped = (MappingTarget) m.call(new Account());
System.assertEquals('foo', mapped.child.name);
}

@IsTest
private static void fieldValuesCanBeProvidedAsAMap() {
MapToObject m = new MapToObject(MappingTarget.class).setFields(
new Map<String, Object>{
'name' => 'foo',
'child.name' => 'bar'
});
MappingTarget mapped = (MappingTarget) m.call(new Account(Name = 'foo'));
System.assertEquals('foo', mapped.name);
System.assertEquals('bar', mapped.child.name);
}

@IsTest
private static void fieldRelationsCanBeMappedToFieldPaths() {
MapToObject m = new MapToObject(MappingTarget.class).mapField('name', 'Name');
MappingTarget mapped = (MappingTarget) m.call(new Account(Name = 'foo'));
System.assertEquals('foo', mapped.name);
}


@IsTest
private static void fieldRelationsCanBeMappedToNestedFieldPaths() {
MapToObject m = new MapToObject(MappingTarget.class).mapField('child.name', 'Name');
MappingTarget mapped = (MappingTarget) m.call(new Account(Name = 'foo'));
System.assertEquals('foo', mapped.child.name);
}

@IsTest
private static void nestedFieldRelationsCanBeMappedToNestedFieldPaths() {
MapToObject m = new MapToObject(MappingTarget.class).mapField('child.name', 'Account.Name');
MappingTarget mapped = (MappingTarget) m.call(new Opportunity(Account = new Account(Name = 'foo')));
System.assertEquals('foo', mapped.child.name);
}

@IsTest
private static void fieldRelationsMappingsCanBeProvidedAsAMap() {
MapToObject m = new MapToObject(MappingTarget.class).mapFields(
new Map<String, String>{
'name' => 'Name',
'child.name' => 'Name'
});
MappingTarget mapped = (MappingTarget) m.call(new Account(Name = 'foo'));
System.assertEquals('foo', mapped.name);
System.assertEquals('foo', mapped.child.name);
}

@IsTest
private static void fieldMappingsCanBeProvidedAsAMap() {
MapToObject m = new MapToObject(MappingTarget.class).mapFields(
new Map<String, Schema.SObjectField>{
'name' => Account.Name,
'child.name' => Account.Name
});
MappingTarget mapped = (MappingTarget) m.call(new Account(Name = 'foo'));
System.assertEquals('foo', mapped.name);
System.assertEquals('foo', mapped.child.name);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>52.0</apiVersion>
<status>Active</status>
</ApexClass>
33 changes: 14 additions & 19 deletions website/docs/api/collection/SObjectCollection.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,38 +294,33 @@ SObjectCollection picked = SObjectCollection.of(opportunities).pick(new Set<Stri

## mapAll

Maps all elements of `SObjectCollection` view into another `SObjectCollection` view with the provided function `fn`.
Maps all elements of `SObjectCollection` view into another `SObjectCollection` or `ObjectCollection` view with the provided function `fn`.

**Signature**
```
SObjectCollection mapAll(SObjectToSObjectFunction fn)
ObjectCollection mapAll(ObjectToSObjectFunction fn)
```

**Example**
```apex title="Mapping with MapTo function factory"
```apex title="Mapping to records with MapTo function factory"
List<Task> followUpTasks = SObjectCollection.of(opps)
.mapAll(
MapTo.SObject(Task.SObjectType)
Fn.MapTo(Task.SObjectType)
.mapField(Task.WhatId, Opportunity.Id)
.setField(Task.Subject, 'Follow up')
.setField(Task.ActivityDate, Date.today())
);
).asList();
```

```apex title="Custom mapping"
private class DoubleAmount implements SObjectToSObjectFunction {
public SObject call(SObject record) {
record.put('Amount', 2 * (Decimal) record.get('Amount'));
return record;
}
}
List<Opportunity> opps = new List<Opportunity>{
new Opportunity(Amount = 100),
new Opportunity(Amount = 150)
};
SObjectCollection.of(opps).mapAll(new DoubleAmount()); // amounts have been doubled
```apex title="Mapping to objects with MapTo function factory"
List<OpportunityAction> actions = (List<OpportunityAction>) SObjectCollection.of(opps)
.mapAll(
Fn.MapTo(OpportunityAction.class)
.mapField('oppId', Opportunity.Id)
.setField('action', 'follow up')
.setField('createdAt', Datetime.now())
).asList();
```

## mapSome
Expand All @@ -352,7 +347,7 @@ List<Opportunity> opps = new List<Opportunity>{
new Opportunity(Amount = 150)
};
SObjectCollection.of(opps).mapSome(Match.field('Amount').gt(120), new DoubleAmount()); // 100 remains, but 150 has been doubled to 300
SObjectCollection.of(opps).mapSome(Fn.Match.field('Amount').gt(120), new DoubleAmount()); // 100 remains, but 150 has been doubled to 300
```

## mapTo
Expand Down
2 changes: 1 addition & 1 deletion website/docs/api/function/Fn.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Returns a new [`AssignToSObject`](AssignToSObject) function.

## MapTo

Returns a new [`MapToSObject`](MapToSObject) function.
Returns a new [`MapToSObject`](MapToSObject) or a [`MapToObject`](MapToObject) function.

## Match

Expand Down
Loading

0 comments on commit 94b2d36

Please sign in to comment.