Skip to content

Commit

Permalink
Merge pull request #822 from bart-degreed/soft-deletion-fixes
Browse files Browse the repository at this point in the history
Fixes in resource definition callbacks
  • Loading branch information
Bart Koelman committed Sep 10, 2020
2 parents 52016b5 + 3751a4c commit 38d23a3
Show file tree
Hide file tree
Showing 13 changed files with 607 additions and 14 deletions.
12 changes: 9 additions & 3 deletions src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -189,10 +189,12 @@ public QueryLayer WrapLayerForSecondaryEndpoint<TId>(QueryLayer secondaryLayer,
primaryProjection[secondaryRelationship] = secondaryLayer;
primaryProjection[primaryIdAttribute] = null;

var primaryFilter = GetFilter(Array.Empty<QueryExpression>(), primaryResourceContext);

return new QueryLayer(primaryResourceContext)
{
Include = RewriteIncludeForSecondaryEndpoint(innerInclude, secondaryRelationship),
Filter = CreateFilterById(primaryId, primaryResourceContext),
Filter = IncludeFilterById(primaryId, primaryResourceContext, primaryFilter),
Projection = primaryProjection
};
}
Expand All @@ -206,12 +208,16 @@ private IncludeExpression RewriteIncludeForSecondaryEndpoint(IncludeExpression r
return new IncludeExpression(new[] {parentElement});
}

private FilterExpression CreateFilterById<TId>(TId id, ResourceContext resourceContext)
private FilterExpression IncludeFilterById<TId>(TId id, ResourceContext resourceContext, FilterExpression existingFilter)
{
var primaryIdAttribute = resourceContext.Attributes.Single(a => a.Property.Name == nameof(Identifiable.Id));

return new ComparisonExpression(ComparisonOperator.Equals,
FilterExpression filterById = new ComparisonExpression(ComparisonOperator.Equals,
new ResourceFieldChainExpression(primaryIdAttribute), new LiteralConstantExpression(id.ToString()));

return existingFilter == null
? filterById
: new LogicalExpression(LogicalOperator.And, new[] {filterById, existingFilter});
}

public IDictionary<ResourceFieldAttribute, QueryLayer> GetSecondaryProjectionForRelationshipEndpoint(ResourceContext secondaryResourceContext)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ private IIdentifiable GetTrackedHasOneRelationshipValue(IIdentifiable relationsh
}

/// <inheritdoc />
public async Task UpdateRelationshipsAsync(object parent, RelationshipAttribute relationship, IReadOnlyCollection<string> relationshipIds)
public async Task UpdateRelationshipAsync(object parent, RelationshipAttribute relationship, IReadOnlyCollection<string> relationshipIds)
{
_traceWriter.LogMethodStart(new {parent, relationship, relationshipIds});
if (parent == null) throw new ArgumentNullException(nameof(parent));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ public interface IResourceWriteRepository<in TResource, in TId>
Task UpdateAsync(TResource requestResource, TResource databaseResource);

/// <summary>
/// Updates relationships in the underlying data store.
/// Updates a relationship in the underlying data store.
/// </summary>
Task UpdateRelationshipsAsync(object parent, RelationshipAttribute relationship, IReadOnlyCollection<string> relationshipIds);
Task UpdateRelationshipAsync(object parent, RelationshipAttribute relationship, IReadOnlyCollection<string> relationshipIds);

/// <summary>
/// Deletes a resource from the underlying data store.
Expand Down
17 changes: 12 additions & 5 deletions src/JsonApiDotNetCore/Services/JsonApiResourceService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ private async Task<TResource> GetPrimaryResourceById(TId id, bool allowTopSparse
var primaryLayer = _queryLayerComposer.Compose(_request.PrimaryResource);
primaryLayer.Sort = null;
primaryLayer.Pagination = null;
primaryLayer.Filter = CreateFilterById(id);
primaryLayer.Filter = IncludeFilterById(id, primaryLayer.Filter);

if (!allowTopSparseFieldSet && primaryLayer.Projection != null)
{
Expand All @@ -176,12 +176,16 @@ private async Task<TResource> GetPrimaryResourceById(TId id, bool allowTopSparse
return primaryResource;
}

private FilterExpression CreateFilterById(TId id)
private FilterExpression IncludeFilterById(TId id, FilterExpression existingFilter)
{
var primaryIdAttribute = _request.PrimaryResource.Attributes.Single(a => a.Property.Name == nameof(Identifiable.Id));

return new ComparisonExpression(ComparisonOperator.Equals,
FilterExpression filterById = new ComparisonExpression(ComparisonOperator.Equals,
new ResourceFieldChainExpression(primaryIdAttribute), new LiteralConstantExpression(id.ToString()));

return existingFilter == null
? filterById
: new LogicalExpression(LogicalOperator.And, new[] {filterById, existingFilter});
}

/// <inheritdoc />
Expand Down Expand Up @@ -279,12 +283,15 @@ public virtual async Task<TResource> UpdateAsync(TId id, TResource requestResour
// triggered by PATCH /articles/1/relationships/{relationshipName}
public virtual async Task UpdateRelationshipAsync(TId id, string relationshipName, object relationships)
{
_traceWriter.LogMethodStart(new {id, relationshipName, related = relationships});
_traceWriter.LogMethodStart(new {id, relationshipName, relationships});
if (relationshipName == null) throw new ArgumentNullException(nameof(relationshipName));

AssertRelationshipExists(relationshipName);

var secondaryLayer = _queryLayerComposer.Compose(_request.SecondaryResource);
secondaryLayer.Projection = _queryLayerComposer.GetSecondaryProjectionForRelationshipEndpoint(_request.SecondaryResource);
secondaryLayer.Include = null;

var primaryLayer = _queryLayerComposer.WrapLayerForSecondaryEndpoint(secondaryLayer, _request.PrimaryResource, id, _request.Relationship);
primaryLayer.Projection = null;

Expand All @@ -306,7 +313,7 @@ public virtual async Task UpdateRelationshipAsync(TId id, string relationshipNam
: ((IEnumerable<IIdentifiable>) relationships).Select(e => e.StringId).ToArray();
}

await _repository.UpdateRelationshipsAsync(primaryResource, _request.Relationship, relationshipIds ?? Array.Empty<string>());
await _repository.UpdateRelationshipAsync(primaryResource, _request.Relationship, relationshipIds ?? Array.Empty<string>());

if (_hookExecutor != null && primaryResource != null)
{
Expand Down
5 changes: 2 additions & 3 deletions test/JsonApiDotNetCoreExampleTests/AppDbContextExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
using System;
using System.Threading.Tasks;
using JsonApiDotNetCoreExample.Data;
using Microsoft.EntityFrameworkCore;
using Npgsql;

namespace JsonApiDotNetCoreExampleTests
{
public static class AppDbContextExtensions
{
public static async Task ClearTableAsync<TEntity>(this AppDbContext dbContext) where TEntity : class
public static async Task ClearTableAsync<TEntity>(this DbContext dbContext) where TEntity : class
{
var entityType = dbContext.Model.FindEntityType(typeof(TEntity));
if (entityType == null)
Expand All @@ -30,7 +29,7 @@ public static class AppDbContextExtensions
}
}

public static void ClearTable<TEntity>(this AppDbContext dbContext) where TEntity : class
public static void ClearTable<TEntity>(this DbContext dbContext) where TEntity : class
{
var entityType = dbContext.Model.FindEntityType(typeof(TEntity));
if (entityType == null)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Controllers;
using JsonApiDotNetCore.Services;
using Microsoft.Extensions.Logging;

namespace JsonApiDotNetCoreExampleTests.IntegrationTests.SoftDeletion
{
public sealed class CompaniesController : JsonApiController<Company>
{
public CompaniesController(IJsonApiOptions options, ILoggerFactory loggerFactory,
IResourceService<Company> resourceService)
: base(options, loggerFactory, resourceService)
{
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Collections.Generic;
using JsonApiDotNetCore.Resources;
using JsonApiDotNetCore.Resources.Annotations;

namespace JsonApiDotNetCoreExampleTests.IntegrationTests.SoftDeletion
{
public sealed class Company : Identifiable
{
[Attr]
public string Name { get; set; }

[Attr]
public bool IsSoftDeleted { get; set; }

[HasMany]
public ICollection<Department> Departments { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Linq;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Queries.Expressions;
using JsonApiDotNetCore.Resources;

namespace JsonApiDotNetCoreExampleTests.IntegrationTests.SoftDeletion
{
public sealed class CompanyResourceDefinition : ResourceDefinition<Company>
{
private readonly IResourceGraph _resourceGraph;

public CompanyResourceDefinition(IResourceGraph resourceGraph) : base(resourceGraph)
{
_resourceGraph = resourceGraph;
}

public override FilterExpression OnApplyFilter(FilterExpression existingFilter)
{
var resourceContext = _resourceGraph.GetResourceContext<Company>();
var isSoftDeletedAttribute = resourceContext.Attributes.Single(attribute => attribute.Property.Name == nameof(Company.IsSoftDeleted));

var isNotSoftDeleted = new ComparisonExpression(ComparisonOperator.Equals,
new ResourceFieldChainExpression(isSoftDeletedAttribute), new LiteralConstantExpression("false"));

return existingFilter == null
? (FilterExpression) isNotSoftDeleted
: new LogicalExpression(LogicalOperator.And, new[] {isNotSoftDeleted, existingFilter});
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using JsonApiDotNetCore.Resources;
using JsonApiDotNetCore.Resources.Annotations;

namespace JsonApiDotNetCoreExampleTests.IntegrationTests.SoftDeletion
{
public sealed class Department : Identifiable
{
[Attr]
public string Name { get; set; }

[Attr]
public bool IsSoftDeleted { get; set; }

[HasOne]
public Company Company { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Linq;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Queries.Expressions;
using JsonApiDotNetCore.Resources;

namespace JsonApiDotNetCoreExampleTests.IntegrationTests.SoftDeletion
{
public sealed class DepartmentResourceDefinition : ResourceDefinition<Department>
{
private readonly IResourceGraph _resourceGraph;

public DepartmentResourceDefinition(IResourceGraph resourceGraph) : base(resourceGraph)
{
_resourceGraph = resourceGraph;
}

public override FilterExpression OnApplyFilter(FilterExpression existingFilter)
{
var resourceContext = _resourceGraph.GetResourceContext<Department>();
var isSoftDeletedAttribute = resourceContext.Attributes.Single(attribute => attribute.Property.Name == nameof(Department.IsSoftDeleted));

var isNotSoftDeleted = new ComparisonExpression(ComparisonOperator.Equals,
new ResourceFieldChainExpression(isSoftDeletedAttribute), new LiteralConstantExpression("false"));

return existingFilter == null
? (FilterExpression) isNotSoftDeleted
: new LogicalExpression(LogicalOperator.And, new[] {isNotSoftDeleted, existingFilter});
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Controllers;
using JsonApiDotNetCore.Services;
using Microsoft.Extensions.Logging;

namespace JsonApiDotNetCoreExampleTests.IntegrationTests.SoftDeletion
{
public sealed class DepartmentsController : JsonApiController<Department>
{
public DepartmentsController(IJsonApiOptions options, ILoggerFactory loggerFactory,
IResourceService<Department> resourceService)
: base(options, loggerFactory, resourceService)
{
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Microsoft.EntityFrameworkCore;

namespace JsonApiDotNetCoreExampleTests.IntegrationTests.SoftDeletion
{
public sealed class SoftDeletionDbContext : DbContext
{
public DbSet<Company> Companies { get; set; }
public DbSet<Department> Departments { get; set; }

public SoftDeletionDbContext(DbContextOptions<SoftDeletionDbContext> options) : base(options)
{
}
}
}

0 comments on commit 38d23a3

Please sign in to comment.