Skip to content

Commit

Permalink
Merge pull request #35 from Research-Institute/sideload-relationships
Browse files Browse the repository at this point in the history
Sideload relationships
  • Loading branch information
jaredcnance authored Feb 22, 2017
2 parents abc7de0 + 464fc41 commit e95befd
Show file tree
Hide file tree
Showing 9 changed files with 254 additions and 19 deletions.
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@ before_script:
- psql -c 'create database JsonApiDotNetCoreExample;' -U postgres
mono: none
dotnet: 1.0.0-preview2-1-003177
branches:
only:
- master
script:
- ./build.sh
85 changes: 72 additions & 13 deletions src/JsonApiDotNetCore/Builders/DocumentBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,17 @@ public Document Build(IIdentifiable entity)
Data = _getData(contextEntity, entity)
};

document.Included = _appendIncludedObject(document.Included, contextEntity, entity);

return document;
}

public Documents Build(IEnumerable<IIdentifiable> entities)
{
var entityType = entities
.GetType()
.GenericTypeArguments[0];
.GetType()
.GenericTypeArguments[0];

var contextEntity = _contextGraph.GetContextEntity(entityType);

var documents = new Documents
Expand All @@ -44,9 +46,25 @@ public Documents Build(IEnumerable<IIdentifiable> entities)
};

foreach (var entity in entities)
{
documents.Data.Add(_getData(contextEntity, entity));
documents.Included = _appendIncludedObject(documents.Included, contextEntity, entity);
}

return documents;
return documents;
}

private List<DocumentData> _appendIncludedObject(List<DocumentData> includedObject, ContextEntity contextEntity, IIdentifiable entity)
{
var includedEntities = _getIncludedEntities(contextEntity, entity);
if (includedEntities.Count > 0)
{
if (includedObject == null)
includedObject = new List<DocumentData>();
includedObject.AddRange(includedEntities);
}

return includedObject;
}

private DocumentData _getData(ContextEntity contextEntity, IIdentifiable entity)
Expand All @@ -61,20 +79,21 @@ private DocumentData _getData(ContextEntity contextEntity, IIdentifiable entity)
return data;

data.Attributes = new Dictionary<string, object>();
data.Relationships = new Dictionary<string, RelationshipData>();

contextEntity.Attributes.ForEach(attr =>
{
data.Attributes.Add(attr.PublicAttributeName, attr.GetValue(entity));
});

_addRelationships(data, contextEntity, entity);
if (contextEntity.Relationships.Count > 0)
_addRelationships(data, contextEntity, entity);

return data;
}

private void _addRelationships(DocumentData data, ContextEntity contextEntity, IIdentifiable entity)
{
data.Relationships = new Dictionary<string, RelationshipData>();
var linkBuilder = new LinkBuilder(_jsonApiContext);

contextEntity.Relationships.ForEach(r =>
Expand All @@ -88,12 +107,12 @@ private void _addRelationships(DocumentData data, ContextEntity contextEntity, I
}
};

if (_hasRelationship(r.RelationshipName))
if (_relationshipIsIncluded(r.RelationshipName))
{
var navigationEntity = _jsonApiContext.ContextGraph
.GetRelationship(entity, r.RelationshipName);

if(navigationEntity is IEnumerable)
if (navigationEntity is IEnumerable)
relationshipData.ManyData = _getRelationships((IEnumerable<object>)navigationEntity, r.RelationshipName);
else
relationshipData.SingleData = _getRelationship(navigationEntity, r.RelationshipName);
Expand All @@ -103,20 +122,60 @@ private void _addRelationships(DocumentData data, ContextEntity contextEntity, I
});
}

private bool _hasRelationship(string relationshipName)
private List<DocumentData> _getIncludedEntities(ContextEntity contextEntity, IIdentifiable entity)
{
var included = new List<DocumentData>();

contextEntity.Relationships.ForEach(r =>
{
if (!_relationshipIsIncluded(r.RelationshipName)) return;

var navigationEntity = _jsonApiContext.ContextGraph.GetRelationship(entity, r.RelationshipName);

if (navigationEntity is IEnumerable)
foreach (var includedEntity in (IEnumerable)navigationEntity)
included.Add(_getIncludedEntity((IIdentifiable)includedEntity));
else
included.Add(_getIncludedEntity((IIdentifiable)navigationEntity));
});

return included;
}

private DocumentData _getIncludedEntity(IIdentifiable entity)
{
return _jsonApiContext.IncludedRelationships != null &&
var contextEntity = _jsonApiContext.ContextGraph.GetContextEntity(entity.GetType());

var data = new DocumentData
{
Type = contextEntity.EntityName,
Id = entity.Id.ToString()
};

data.Attributes = new Dictionary<string, object>();

contextEntity.Attributes.ForEach(attr =>
{
data.Attributes.Add(attr.PublicAttributeName, attr.GetValue(entity));
});

return data;
}

private bool _relationshipIsIncluded(string relationshipName)
{
return _jsonApiContext.IncludedRelationships != null &&
_jsonApiContext.IncludedRelationships.Contains(relationshipName.ToProperCase());
}

private List<Dictionary<string, string>> _getRelationships(IEnumerable<object> entities, string relationshipName)
{
var objType = entities.GetType().GenericTypeArguments[0];

var typeName = _jsonApiContext.ContextGraph.GetContextEntity(objType);

var relationships = new List<Dictionary<string, string>>();
foreach(var entity in entities)
foreach (var entity in entities)
{
relationships.Add(new Dictionary<string, string> {
{"type", typeName.EntityName.Dasherize() },
Expand All @@ -128,7 +187,7 @@ private List<Dictionary<string, string>> _getRelationships(IEnumerable<object> e
private Dictionary<string, string> _getRelationship(object entity, string relationshipName)
{
var objType = entity.GetType();

var typeName = _jsonApiContext.ContextGraph.GetContextEntity(objType);

return new Dictionary<string, string> {
Expand Down
4 changes: 4 additions & 0 deletions src/JsonApiDotNetCore/Models/Document.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using Newtonsoft.Json;

namespace JsonApiDotNetCore.Models
Expand All @@ -6,5 +7,8 @@ public class Document
{
[JsonProperty("data")]
public DocumentData Data { get; set; }

[JsonProperty("included")]
public List<DocumentData> Included { get; set; }
}
}
1 change: 0 additions & 1 deletion src/JsonApiDotNetCore/Models/DocumentData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ public string Type

[JsonProperty("attributes")]
public Dictionary<string, object> Attributes { get; set; }


[JsonProperty("relationships")]
public Dictionary<string, RelationshipData> Relationships { get; set; }
Expand Down
3 changes: 3 additions & 0 deletions src/JsonApiDotNetCore/Models/Documents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@ public class Documents
{
[JsonProperty("data")]
public List<DocumentData> Data { get; set; }

[JsonProperty("included")]
public List<DocumentData> Included { get; set; }
}
}
2 changes: 1 addition & 1 deletion src/JsonApiDotNetCore/project.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "0.2.10",
"version": "0.2.11",

"dependencies": {
"Microsoft.NETCore.App": {
Expand Down
4 changes: 2 additions & 2 deletions src/JsonApiDotNetCoreExample/Models/Person.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ public class Person : Identifiable<int>
{
public override int Id { get; set; }

[Attr("firstName")]
[Attr("first-name")]
public string FirstName { get; set; }

[Attr("lastName")]
[Attr("last-name")]
public string LastName { get; set; }

public virtual List<TodoItem> TodoItems { get; set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using DotNetCoreDocs;
using DotNetCoreDocs.Writers;
using JsonApiDotNetCoreExample;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Newtonsoft.Json;
using Xunit;
using Person = JsonApiDotNetCoreExample.Models.Person;
using JsonApiDotNetCore.Models;
using JsonApiDotNetCoreExample.Data;
using Bogus;
using JsonApiDotNetCoreExample.Models;
using System;

namespace JsonApiDotNetCoreExampleTests.Acceptance.Spec.DocumentTests
{
[Collection("WebHostCollection")]
public class Included
{
private DocsFixture<Startup, JsonDocWriter> _fixture;
private AppDbContext _context;
private Faker<Person> _personFaker;
private Faker<TodoItem> _todoItemFaker;

public Included(DocsFixture<Startup, JsonDocWriter> fixture)
{
_fixture = fixture;
_context = fixture.GetService<AppDbContext>();
_personFaker = new Faker<Person>()
.RuleFor(p => p.FirstName, f => f.Name.FirstName())
.RuleFor(p => p.LastName, f => f.Name.LastName());

_todoItemFaker = new Faker<TodoItem>()
.RuleFor(t => t.Description, f => f.Lorem.Sentence())
.RuleFor(t => t.Ordinal, f => f.Random.Number());
}

[Fact]
public async Task GET_Included_Contains_SideloadedData_ForManyToOne()
{
// arrange
var builder = new WebHostBuilder()
.UseStartup<Startup>();

var httpMethod = new HttpMethod("GET");
var route = $"/api/v1/todo-items?include=owner";

var server = new TestServer(builder);
var client = server.CreateClient();
var request = new HttpRequestMessage(httpMethod, route);

// act
var response = await client.SendAsync(request);
var documents = JsonConvert.DeserializeObject<Documents>(await response.Content.ReadAsStringAsync());
var data = documents.Data[0];

// assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.NotEmpty(documents.Included);
Assert.Equal(documents.Data.Count, documents.Included.Count);
}

[Fact]
public async Task GET_ById_Included_Contains_SideloadedData_ForManyToOne()
{
// arrange
var person = _personFaker.Generate();
var todoItem = _todoItemFaker.Generate();
todoItem.Owner = person;
_context.TodoItems.Add(todoItem);
_context.SaveChanges();

var builder = new WebHostBuilder()
.UseStartup<Startup>();

var httpMethod = new HttpMethod("GET");

var route = $"/api/v1/todo-items/{todoItem.Id}?include=owner";

var server = new TestServer(builder);
var client = server.CreateClient();
var request = new HttpRequestMessage(httpMethod, route);

// act
var response = await client.SendAsync(request);
var responseString = await response.Content.ReadAsStringAsync();
var document = JsonConvert.DeserializeObject<Document>(responseString);

// assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.NotEmpty(document.Included);
Assert.Equal(person.Id.ToString(), document.Included[0].Id);
Assert.Equal(person.FirstName, document.Included[0].Attributes["first-name"]);
Assert.Equal(person.LastName, document.Included[0].Attributes["last-name"]);
}

[Fact]
public async Task GET_Included_Contains_SideloadedData_OneToMany()
{
// arrange
_context.People.RemoveRange(_context.People); // ensure all people have todo-items
var person = _personFaker.Generate();
var todoItem = _todoItemFaker.Generate();
todoItem.Owner = person;
_context.TodoItems.Add(todoItem);
_context.SaveChanges();

var builder = new WebHostBuilder()
.UseStartup<Startup>();

var httpMethod = new HttpMethod("GET");
var route = $"/api/v1/people?include=todo-items";

var server = new TestServer(builder);
var client = server.CreateClient();
var request = new HttpRequestMessage(httpMethod, route);

// act
var response = await client.SendAsync(request);
var documents = JsonConvert.DeserializeObject<Documents>(await response.Content.ReadAsStringAsync());
var data = documents.Data[0];

// assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.NotEmpty(documents.Included);
Assert.Equal(documents.Data.Count, documents.Included.Count);
}

[Fact]
public async Task GET_ById_Included_Contains_SideloadedData_ForOneToMany()
{
// arrange
const int numberOfTodoItems = 5;
var person = _personFaker.Generate();
for (var i = 0; i < numberOfTodoItems; i++)
{
var todoItem = _todoItemFaker.Generate();
todoItem.Owner = person;
_context.TodoItems.Add(todoItem);
_context.SaveChanges();
}

var builder = new WebHostBuilder()
.UseStartup<Startup>();

var httpMethod = new HttpMethod("GET");

var route = $"/api/v1/people/{person.Id}?include=todo-items";

var server = new TestServer(builder);
var client = server.CreateClient();
var request = new HttpRequestMessage(httpMethod, route);

// act
var response = await client.SendAsync(request);
var responseString = await response.Content.ReadAsStringAsync();
var document = JsonConvert.DeserializeObject<Document>(responseString);

// assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.NotEmpty(document.Included);
Assert.Equal(numberOfTodoItems, document.Included.Count);
}
}
}
Loading

0 comments on commit e95befd

Please sign in to comment.