Skip to content

Commit 201a837

Browse files
committed
feat: affectedresource helpers easier to use in unit tests
1 parent b3fbe75 commit 201a837

File tree

12 files changed

+248
-119
lines changed

12 files changed

+248
-119
lines changed

src/Examples/JsonApiDotNetCoreExample/Resources/TagResource.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ public TagResource(IResourceGraph graph) : base(graph)
1313
{
1414
}
1515

16+
public override IEnumerable<Tag> BeforeCreate(IAffectedResources<Tag> affected, ResourcePipeline pipeline)
17+
{
18+
return base.BeforeCreate(affected, pipeline);
19+
}
20+
1621
public override IEnumerable<Tag> OnReturn(HashSet<Tag> entities, ResourcePipeline pipeline)
1722
{
1823
return entities.Where(t => t.Name != "This should be not be included");
Lines changed: 75 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections;
33
using System.Collections.Generic;
4+
using System.Collections.ObjectModel;
45
using System.Linq;
56
using JsonApiDotNetCore.Models;
67

@@ -11,44 +12,104 @@ public interface IAffectedRelationships { }
1112
/// <summary>
1213
/// A helper class that provides insights in which relationships have been updated for which entities.
1314
/// </summary>
14-
public interface IAffectedRelationships<TDependent> : IAffectedRelationships where TDependent : class, IIdentifiable
15+
public interface IAffectedRelationships<TDependentResource> : IAffectedRelationships where TDependentResource : class, IIdentifiable
1516
{
1617
/// <summary>
1718
/// Gets a dictionary of all entities grouped by affected relationship.
1819
/// </summary>
19-
Dictionary<RelationshipAttribute, HashSet<TDependent>> AllByRelationships();
20+
Dictionary<RelationshipAttribute, HashSet<TDependentResource>> AllByRelationships();
2021

2122
/// <summary>
22-
/// Gets a dictionary of all entities that have an affected relationship to type <typeparamref name="TPrincipal"/>
23+
/// Gets a dictionary of all entities that have an affected relationship to type <typeparamref name="TPrincipalResource"/>
2324
/// </summary>
24-
Dictionary<RelationshipAttribute, HashSet<TDependent>> GetByRelationship<TPrincipal>() where TPrincipal : class, IIdentifiable;
25+
Dictionary<RelationshipAttribute, HashSet<TDependentResource>> GetByRelationship<TPrincipalResource>() where TPrincipalResource : class, IIdentifiable;
2526
/// <summary>
2627
/// Gets a dictionary of all entities that have an affected relationship to type <paramref name="principalType"/>
2728
/// </summary>
28-
Dictionary<RelationshipAttribute, HashSet<TDependent>> GetByRelationship(Type principalType);
29+
Dictionary<RelationshipAttribute, HashSet<TDependentResource>> GetByRelationship(Type principalType);
2930
}
3031

31-
public class AffectedRelationships<TDependent> : IAffectedRelationships<TDependent> where TDependent : class, IIdentifiable
32+
/// <inheritdoc />
33+
public class AffectedRelationships<TDependentResource> : IAffectedRelationships<TDependentResource> where TDependentResource : class, IIdentifiable
3234
{
33-
private readonly Dictionary<RelationshipAttribute, HashSet<TDependent>> _groups;
35+
internal static Dictionary<RelationshipAttribute, HashSet<TDependentResource>> ConvertRelationshipDictionary(Dictionary<RelationshipAttribute, IEnumerable> relationships)
36+
{
37+
return relationships.ToDictionary(pair => pair.Key, pair => (HashSet<TDependentResource>)pair.Value);
38+
}
39+
40+
/// <summary>
41+
/// a dictionary with affected relationships as keys and values being the corresponding resources
42+
/// that were affected
43+
/// </summary>
44+
private readonly Dictionary<RelationshipAttribute, HashSet<TDependentResource>> _groups;
3445

35-
public Dictionary<RelationshipAttribute, HashSet<TDependent>> AllByRelationships()
46+
/// <inheritdoc />
47+
public AffectedRelationships(Dictionary<RelationshipAttribute, HashSet<TDependentResource>> relationships)
3648
{
37-
return _groups;
49+
_groups = relationships;
3850
}
39-
public AffectedRelationships(Dictionary<RelationshipAttribute, IEnumerable> relationships)
51+
52+
/// <summary>
53+
/// Used internally by the ResourceHookExecutor to make live a bit easier with generics
54+
/// </summary>
55+
internal AffectedRelationships(Dictionary<RelationshipAttribute, IEnumerable> relationships) : this(ConvertRelationshipDictionary(relationships)) { }
56+
57+
public Dictionary<RelationshipAttribute, HashSet<TDependentResource>> AllByRelationships()
4058
{
41-
_groups = relationships.ToDictionary(kvp => kvp.Key, kvp => new HashSet<TDependent>((IEnumerable<TDependent>)kvp.Value));
59+
return _groups;
4260
}
4361

44-
public Dictionary<RelationshipAttribute, HashSet<TDependent>> GetByRelationship<TPrincipal>() where TPrincipal : class, IIdentifiable
62+
/// <inheritdoc />
63+
public Dictionary<RelationshipAttribute, HashSet<TDependentResource>> GetByRelationship<TPrincipalResource>() where TPrincipalResource : class, IIdentifiable
4564
{
46-
return GetByRelationship(typeof(TPrincipal));
65+
return GetByRelationship(typeof(TPrincipalResource));
4766
}
4867

49-
public Dictionary<RelationshipAttribute, HashSet<TDependent>> GetByRelationship(Type principalType)
68+
/// <inheritdoc />
69+
public Dictionary<RelationshipAttribute, HashSet<TDependentResource>> GetByRelationship(Type principalType)
5070
{
5171
return _groups?.Where(p => p.Key.PrincipalType == principalType).ToDictionary(p => p.Key, p => p.Value);
5272
}
5373
}
74+
75+
///// <inheritdoc />
76+
//public class AffectedRelationships<TDependentResource> : ReadOnlyDictionary<RelationshipAttribute, HashSet<TDependentResource>>, IAffectedRelationships<TDependentResource> where TDependentResource : class, IIdentifiable
77+
//{
78+
// private readonly Dictionary<RelationshipAttribute, HashSet<TDependentResource>> _groups;
79+
80+
// private static IDictionary<RelationshipAttribute, HashSet<TDependentResource>> test(Dictionary<RelationshipAttribute, IEnumerable> relationship)
81+
// {
82+
// return relationship.ToDictionary(kvp => kvp.Key, kvp => new HashSet<TDependentResource>((IEnumerable<TDependentResource>)kvp.Value));
83+
// }
84+
85+
// public AffectedRelationships(Dictionary<RelationshipAttribute, IEnumerable> relationship) : base(test(relationship))
86+
// {
87+
// }
88+
89+
90+
// /// <inheritdoc />
91+
// public AffectedRelationships(Dictionary<RelationshipAttribute, IEnumerable> relationships)
92+
// {
93+
// _groups = relationships.ToDictionary(kvp => kvp.Key, kvp => new HashSet<TDependentResource>((IEnumerable<TDependentResource>)kvp.Value));
94+
// }
95+
96+
// public Dictionary<RelationshipAttribute, HashSet<TDependentResource>> AllByRelationships()
97+
// {
98+
// return _groups;
99+
// }
100+
101+
102+
103+
// /// <inheritdoc />
104+
// public Dictionary<RelationshipAttribute, HashSet<TDependentResource>> GetByRelationship<TPrincipalResource>() where TPrincipalResource : class, IIdentifiable
105+
// {
106+
// return GetByRelationship(typeof(TPrincipalResource));
107+
// }
108+
109+
// /// <inheritdoc />
110+
// public Dictionary<RelationshipAttribute, HashSet<TDependentResource>> GetByRelationship(Type principalType)
111+
// {
112+
// return _groups?.Where(p => p.Key.PrincipalType == principalType).ToDictionary(p => p.Key, p => p.Value);
113+
// }
114+
//}
54115
}

src/JsonApiDotNetCore/Hooks/Execution/AffectedResourceDiff.cs

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,58 +14,83 @@ namespace JsonApiDotNetCore.Hooks
1414
/// Any relationships that are updated can be retrieved via the methods implemented on
1515
/// <see cref="IAffectedRelationships{TDependent}"/>.
1616
/// </summary>
17-
public interface IAffectedResourcesDiff<TEntity> : IAffectedResources<TEntity> where TEntity : class, IIdentifiable
17+
public interface IAffectedResourcesDiff<TResource> : IAffectedResources<TResource> where TResource : class, IIdentifiable
1818
{
19-
HashSet<TEntity> DatabaseValues { get; }
20-
IEnumerable<ResourceDiffPair<TEntity>> GetDiffs();
19+
/// <summary>
20+
/// the current database values of the affected resources collection.
21+
/// </summary>
22+
HashSet<TResource> DatabaseValues { get; }
23+
24+
/// <summary>
25+
/// Matches the resources from the request to the database values that have been loaded
26+
/// and exposes them in ResourceDiffPair wrapper
27+
/// </summary>
28+
IEnumerable<ResourceDiffPair<TResource>> GetDiffs();
2129
}
2230

23-
public class AffectedResourceDiff<TEntity> : AffectedResources<TEntity>, IAffectedResourcesDiff<TEntity> where TEntity : class, IIdentifiable
31+
public class AffectedResourceDiff<TResource> : AffectedResources<TResource>, IAffectedResourcesDiff<TResource> where TResource : class, IIdentifiable
2432
{
2533

26-
private readonly HashSet<TEntity> _databaseValues;
34+
private readonly HashSet<TResource> _databaseValues;
2735
private readonly bool _databaseValuesLoaded;
2836

29-
/// <summary>
30-
/// the current database values of the affected resources collection.
31-
/// </summary>
32-
public HashSet<TEntity> DatabaseValues { get => _databaseValues ?? ThrowNoDbValuesError(); }
3337

34-
public AffectedResourceDiff(IEnumerable requestEntities,
35-
IEnumerable databaseEntities,
36-
Dictionary<RelationshipAttribute, IEnumerable> relationships) : base(requestEntities, relationships)
38+
/// <inheritdoc />
39+
public HashSet<TResource> DatabaseValues { get => _databaseValues ?? ThrowNoDbValuesError(); }
40+
41+
public AffectedResourceDiff(HashSet<TResource> requestEntities,
42+
HashSet<TResource> databaseEntities,
43+
Dictionary<RelationshipAttribute, HashSet<TResource>> relationships) : base(requestEntities, relationships)
3744
{
38-
_databaseValues = (HashSet<TEntity>)databaseEntities;
45+
_databaseValues = databaseEntities;
3946
_databaseValuesLoaded |= _databaseValues != null;
4047
}
4148

42-
public IEnumerable<ResourceDiffPair<TEntity>> GetDiffs()
49+
/// <summary>
50+
/// Used internally by the ResourceHookExecutor to make live a bit easier with generics
51+
/// </summary>
52+
internal AffectedResourceDiff(IEnumerable requestEntities,
53+
IEnumerable databaseEntities,
54+
Dictionary<RelationshipAttribute, IEnumerable> relationships)
55+
: this((HashSet<TResource>)requestEntities, (HashSet<TResource>)databaseEntities, ConvertRelationshipDictionary(relationships)) { }
56+
57+
/// <inheritdoc />
58+
public IEnumerable<ResourceDiffPair<TResource>> GetDiffs()
4359
{
4460
if (!_databaseValuesLoaded) ThrowNoDbValuesError();
4561

46-
foreach (var entity in Entities)
62+
foreach (var entity in Resources)
4763
{
48-
TEntity currentValueInDatabase = null;
64+
TResource currentValueInDatabase = null;
4965
currentValueInDatabase = _databaseValues.Single(e => entity.StringId == e.StringId);
50-
yield return new ResourceDiffPair<TEntity>(entity, currentValueInDatabase);
66+
yield return new ResourceDiffPair<TResource>(entity, currentValueInDatabase);
5167
}
5268
}
5369

54-
private HashSet<TEntity> ThrowNoDbValuesError()
70+
private HashSet<TResource> ThrowNoDbValuesError()
5571
{
5672
throw new MemberAccessException("Cannot access database entities if the LoadDatabaseValues option is set to false");
5773
}
5874
}
5975

60-
public class ResourceDiffPair<TEntity> where TEntity : class, IIdentifiable
76+
/// <summary>
77+
/// A wrapper that contains a resource from the request matches to its current database value
78+
/// </summary>
79+
public class ResourceDiffPair<TResource> where TResource : class, IIdentifiable
6180
{
62-
public ResourceDiffPair(TEntity entity, TEntity databaseValue)
81+
public ResourceDiffPair(TResource resource, TResource databaseValue)
6382
{
64-
Entity = entity;
83+
Resource = resource;
6584
DatabaseValue = databaseValue;
6685
}
6786

68-
public TEntity Entity { get; private set; }
69-
public TEntity DatabaseValue { get; private set; }
87+
/// <summary>
88+
/// The resource from the request matching the resource from the database.
89+
/// </summary>
90+
public TResource Resource { get; private set; }
91+
/// <summary>
92+
/// The resource from the database matching the resource from the request.
93+
/// </summary>
94+
public TResource DatabaseValue { get; private set; }
7095
}
7196
}

src/JsonApiDotNetCore/Hooks/Execution/AffectedResources.cs

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,43 @@
55

66
namespace JsonApiDotNetCore.Hooks
77
{
8-
9-
public interface IAffectedResources<TEntity> : IEnumerable<TEntity> where TEntity : class, IIdentifiable
10-
{
11-
HashSet<TEntity> Entities { get; }
12-
}
13-
14-
public class AffectedResources<TEntity> : AffectedRelationships<TEntity>, IAffectedResources<TEntity> where TEntity : class, IIdentifiable
8+
/// <summary>
9+
/// Basically just a list of <typeparamref name="TResource"/>, but also contains information
10+
/// about updated relationships through inheritance of IAffectedRelationships<typeparamref name="TResource"/>>
11+
/// </summary>
12+
public interface IAffectedResources<TResource> : IAffectedRelationships<TResource>, IEnumerable<TResource> where TResource : class, IIdentifiable
1513
{
1614
/// <summary>
1715
/// The entities that are affected by the request.
1816
/// </summary>
19-
public HashSet<TEntity> Entities { get; }
17+
HashSet<TResource> Resources { get; }
18+
}
2019

21-
public AffectedResources(IEnumerable entities,
22-
Dictionary<RelationshipAttribute, IEnumerable> relationships) : base(relationships)
20+
public class AffectedResources<TResource> : AffectedRelationships<TResource>, IAffectedResources<TResource> where TResource : class, IIdentifiable
21+
{
22+
/// <inheritdoc />
23+
public HashSet<TResource> Resources { get; }
24+
25+
public AffectedResources(HashSet<TResource> entities,
26+
Dictionary<RelationshipAttribute, HashSet<TResource>> relationships) : base(relationships)
2327
{
24-
Entities = new HashSet<TEntity>(entities.Cast<TEntity>());
28+
Resources = new HashSet<TResource>(entities.Cast<TResource>());
2529
}
26-
public IEnumerator<TEntity> GetEnumerator()
30+
31+
/// <summary>
32+
/// Used internally by the ResourceHookExecutor to make live a bit easier with generics
33+
/// </summary>
34+
internal AffectedResources(IEnumerable entities,
35+
Dictionary<RelationshipAttribute, IEnumerable> relationships)
36+
: this((HashSet<TResource>)entities, ConvertRelationshipDictionary(relationships)) { }
37+
38+
/// <inheritdoc />
39+
public IEnumerator<TResource> GetEnumerator()
2740
{
28-
return Entities.GetEnumerator();
41+
return Resources.GetEnumerator();
2942
}
3043

44+
/// <inheritdoc />
3145
IEnumerator IEnumerable.GetEnumerator()
3246
{
3347
return GetEnumerator();

src/JsonApiDotNetCore/Hooks/Execution/HookExecutorHelper.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,10 +206,15 @@ public Dictionary<RelationshipAttribute, IEnumerable> LoadImplicitlyAffected(
206206
}
207207
}
208208

209-
return implicitlyAffected.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
209+
return implicitlyAffected.ToDictionary(kvp => kvp.Key, kvp => TypeHelper.CreateHashSetFor(kvp.Key.DependentType, kvp.Value));
210210

211211
}
212212

213+
private IEnumerable CreateHashSet(Type type, IList elements)
214+
{
215+
return (IEnumerable)Activator.CreateInstance(typeof(HashSet<>).MakeGenericType(type), new object[] { elements });
216+
}
217+
213218
bool IsHasManyThrough(KeyValuePair<RelationshipAttribute, IEnumerable> kvp,
214219
out IEnumerable entities,
215220
out RelationshipAttribute attr)

0 commit comments

Comments
 (0)