Skip to content

Commit dafa96d

Browse files
author
Chris Santero
committed
implement related resource URLs for to-one relationships
1 parent dea14d0 commit dafa96d

5 files changed

Lines changed: 67 additions & 3 deletions

File tree

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"data": {
3+
"type": "users",
4+
"id": "401",
5+
"attributes": {
6+
"first-name": "Alice",
7+
"last-name": "Smith"
8+
},
9+
"relationships": {
10+
"comments": {
11+
"links": {
12+
"self": "https://www.example.com/users/401/relationships/comments",
13+
"related": "https://www.example.com/users/401/comments"
14+
}
15+
},
16+
"posts": {
17+
"links": {
18+
"self": "https://www.example.com/users/401/relationships/posts",
19+
"related": "https://www.example.com/users/401/posts"
20+
}
21+
},
22+
"user-groups": {
23+
"links": {
24+
"self": "https://www.example.com/users/401/relationships/user-groups",
25+
"related": "https://www.example.com/users/401/user-groups"
26+
}
27+
}
28+
}
29+
}
30+
}

JSONAPI.EntityFramework.Tests/Acceptance/RelatedResourcesTests.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,21 @@ public async Task Get_related_to_many()
2626
await AssertResponseContent(response, @"Acceptance\Fixtures\RelatedResources\Responses\Get_related_to_many_response.json", HttpStatusCode.OK);
2727
}
2828
}
29+
30+
[TestMethod]
31+
[DeploymentItem(@"Acceptance\Data\Comment.csv", @"Acceptance\Data")]
32+
[DeploymentItem(@"Acceptance\Data\Post.csv", @"Acceptance\Data")]
33+
[DeploymentItem(@"Acceptance\Data\PostTagLink.csv", @"Acceptance\Data")]
34+
[DeploymentItem(@"Acceptance\Data\Tag.csv", @"Acceptance\Data")]
35+
[DeploymentItem(@"Acceptance\Data\User.csv", @"Acceptance\Data")]
36+
public async Task Get_related_to_one()
37+
{
38+
using (var effortConnection = GetEffortConnection())
39+
{
40+
var response = await SubmitGet(effortConnection, "posts/201/author");
41+
42+
await AssertResponseContent(response, @"Acceptance\Fixtures\RelatedResources\Responses\Get_related_to_one_response.json", HttpStatusCode.OK);
43+
}
44+
}
2945
}
3046
}

JSONAPI.EntityFramework.Tests/JSONAPI.EntityFramework.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@
187187
<EmbeddedResource Include="Acceptance\Fixtures\Posts\Requests\PatchWithMissingToOneLinkageRequest.json" />
188188
<EmbeddedResource Include="Acceptance\Fixtures\Posts\Responses\PatchWithMissingToOneLinkageResponse.json" />
189189
<EmbeddedResource Include="Acceptance\Fixtures\RelatedResources\Responses\Get_related_to_many_response.json" />
190+
<EmbeddedResource Include="Acceptance\Fixtures\RelatedResources\Responses\Get_related_to_one_response.json" />
190191
<None Include="App.Config">
191192
<SubType>Designer</SubType>
192193
</None>

JSONAPI.EntityFramework/DbContextExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public static class DbContextExtensions
2121
/// <returns></returns>
2222
public static IEnumerable<string> GetKeyNames(this DbContext dbContext, Type type)
2323
{
24-
var openMethod = typeof(DbContextExtensions).GetMethod("GetKeyNamesFromGeneric", BindingFlags.NonPublic | BindingFlags.Static);
24+
var openMethod = typeof(DbContextExtensions).GetMethod("GetKeyNamesFromGeneric", BindingFlags.Public | BindingFlags.Static);
2525
var method = openMethod.MakeGenericMethod(type);
2626
try
2727
{

JSONAPI.EntityFramework/Http/EntityFrameworkPayloadMaterializer.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public class EntityFrameworkPayloadMaterializer<T> : IPayloadMaterializer<T> whe
2424
private readonly IQueryableResourceCollectionPayloadBuilder _queryableResourceCollectionPayloadBuilder;
2525
private readonly ISingleResourcePayloadBuilder _singleResourcePayloadBuilder;
2626
private readonly MethodInfo _getRelatedToManyMethod;
27+
private readonly MethodInfo _getRelatedToOneMethod;
2728

2829
/// <summary>
2930
/// Creates a new EntityFrameworkPayloadMaterializer
@@ -44,6 +45,8 @@ public EntityFrameworkPayloadMaterializer(
4445
_singleResourcePayloadBuilder = singleResourcePayloadBuilder;
4546
_getRelatedToManyMethod = GetType()
4647
.GetMethod("GetRelatedToMany", BindingFlags.NonPublic | BindingFlags.Instance);
48+
_getRelatedToOneMethod = GetType()
49+
.GetMethod("GetRelatedToOne", BindingFlags.NonPublic | BindingFlags.Instance);
4750
}
4851

4952
public virtual Task<IResourceCollectionPayload> GetRecords(HttpRequestMessage request, CancellationToken cancellationToken)
@@ -62,7 +65,6 @@ public virtual async Task<ISingleResourcePayload> GetRecordById(string id, HttpR
6265
public virtual async Task<IJsonApiPayload> GetRelated(string id, string relationshipKey, HttpRequestMessage request,
6366
CancellationToken cancellationToken)
6467
{
65-
var apiBaseUrl = GetBaseUrlFromRequest(request);
6668
var registration = _resourceTypeRegistry.GetRegistrationForType(typeof (T));
6769
var relationship = (ResourceTypeRelationship) registration.GetFieldByName(relationshipKey);
6870

@@ -74,7 +76,9 @@ public virtual async Task<IJsonApiPayload> GetRelated(string id, string relation
7476
}
7577
else
7678
{
77-
throw new NotImplementedException();
79+
var method = _getRelatedToOneMethod.MakeGenericMethod(relationship.RelatedType);
80+
var result = (Task<ISingleResourcePayload>)method.Invoke(this, new object[] { id, relationship, request, cancellationToken });
81+
return await result;
7882
}
7983
}
8084

@@ -143,6 +147,19 @@ private async Task<IResourceCollectionPayload> GetRelatedToMany<TRelated>(string
143147
return await _queryableResourceCollectionPayloadBuilder.BuildPayload(relatedResourceQuery, request, cancellationToken);
144148
}
145149

150+
// ReSharper disable once UnusedMember.Local
151+
private async Task<ISingleResourcePayload> GetRelatedToOne<TRelated>(string id,
152+
ResourceTypeRelationship relationship, HttpRequestMessage request, CancellationToken cancellationToken)
153+
{
154+
var param = Expression.Parameter(typeof(T));
155+
var accessorExpr = Expression.Property(param, relationship.Property);
156+
var lambda = Expression.Lambda<Func<T, TRelated>>(accessorExpr, param);
157+
158+
var primaryEntityQuery = FilterById<T>(id);
159+
var relatedResource = await primaryEntityQuery.Select(lambda).FirstOrDefaultAsync(cancellationToken);
160+
return _singleResourcePayloadBuilder.BuildPayload(relatedResource, GetBaseUrlFromRequest(request), null);
161+
}
162+
146163
private IQueryable<TResource> Filter<TResource>(Expression<Func<TResource, bool>> predicate,
147164
params Expression<Func<TResource, object>>[] includes) where TResource : class
148165
{

0 commit comments

Comments
 (0)