Skip to content

Commit 49837ae

Browse files
author
Raphael Hoppe
committed
Adding AfterModel action for two sided relationships
1 parent c367f97 commit 49837ae

File tree

12 files changed

+295
-1
lines changed

12 files changed

+295
-1
lines changed

src/Commons.Builders.Model/Builder/ModelBuilder.cs

+20-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.ComponentModel.DataAnnotations;
33
using System.Reflection;
44

@@ -64,6 +64,7 @@ public TModel Build()
6464
{
6565
_factory.PreBuild.Execute<TModel>(this);
6666
_model = BuildModel();
67+
AfterModel(_model);
6768
_factory.PostBuild.Execute<TModel>(_model!);
6869
}
6970
return _model;
@@ -74,6 +75,24 @@ public TModel Build()
7475
/// </summary>
7576
protected abstract TModel BuildModel();
7677

78+
79+
/// <summary>
80+
/// Method that can be overwritten for common 'Chicken and egg' situations during setup
81+
/// This will run before the factories post actions are executed, but after the model is build.
82+
/// This allows us to resolve a dependency of a child builder, without running into a cyclic call.
83+
///
84+
/// The way this works is, the parent will get build, without the children connected,
85+
/// and then the children will be built in this method and then connected to the parent.
86+
///
87+
/// model = Parent.Build();
88+
/// child = _childBuilder.WithParent(Parent).Build();
89+
/// model.Add(child);
90+
/// </summary>
91+
protected virtual void AfterModel(TModel model)
92+
{
93+
// intentionally empty, since this is a optional action, to be overwritten by specific builders
94+
}
95+
7796
/// <summary>
7897
/// Validation if the builder is locked or not.
7998
/// This method should be called in every builder method, to warn the user early
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using NUnit.Framework;
2+
3+
using FluentAssertions;
4+
5+
using Queo.Commons.Builders.Model.Examples;
6+
using Queo.Commons.Builders.Model.Examples.Relations;
7+
8+
namespace Queo.Commons.Builders.Model.Tests.AfterModel;
9+
10+
[TestFixture]
11+
public class AfterModelTests
12+
{
13+
[Test]
14+
public void CreateCountry_WithPresident()
15+
{
16+
Country c = Create.Country().WithName("USA")
17+
.WithPresident(p => p.WithName("Roosevelt"));
18+
19+
c.Name.Should().Be("USA");
20+
c.President?.Name.Should().Be("Roosevelt");
21+
c.Should().Be(c.President?.Country);
22+
}
23+
24+
[Test]
25+
public void CreatePresident_WithCountry()
26+
{
27+
President p = Create.President().WithName("Roosevelt")
28+
.WithCountry(c => c.WithName("USA"));
29+
30+
p.Name.Should().Be("Roosevelt");
31+
p.Country.Name.Should().Be("USA");
32+
p.Country.President.Should().Be(p);
33+
}
34+
35+
[Test]
36+
public void CreateUser_WithOrg()
37+
{
38+
User u = Create.User().WithName("George")
39+
.WithOrg(o => o.WithName("GOrg"));
40+
41+
u.Name.Should().Be("George");
42+
u.Org?.Name.Should().Be("GOrg");
43+
u.Org?.Members.Should().Contain(u);
44+
}
45+
46+
[Test]
47+
public void CreateOrg_WithUsers()
48+
{
49+
Org o = Create.Org().WithName("GOrg")
50+
.WithAdmin(u => u.WithName("George"))
51+
.AddMember(u => u.WithName("Other"));
52+
53+
o.Name.Should().Be("GOrg");
54+
o.Admin.Name.Should().Be("George");
55+
o.Admin.Org.Should().Be(o);
56+
o.Members.Should().Contain(m => m.Name.Equals("Other"));
57+
o.Members.Should().Contain(m => m.Name.Equals("George"));
58+
}
59+
}

tests/Examples/Create.cs

+7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Queo.Commons.Builders.Model.Examples.DAG;
33
using Queo.Commons.Builders.Model.Examples.Person;
44
using Queo.Commons.Builders.Model.Examples.Person.Mocks;
5+
using Queo.Commons.Builders.Model.Examples.Relations;
56
using Queo.Commons.Builders.Model.Examples.Tree;
67
using Queo.Commons.Builders.Model.Factory;
78

@@ -25,5 +26,11 @@ public static class Create
2526

2627
public static PersonBuilder Person() => Factory.Create<PersonBuilder>();
2728
public static PersonGeneratorBuilder GeneratedPerson() => GeneratorFactory.Create<PersonGeneratorBuilder>();
29+
30+
public static CountryBuilder Country() => Factory.Create<CountryBuilder>();
31+
public static PresidentBuilder President() => Factory.Create<PresidentBuilder>();
32+
33+
public static UserBuilder User() => Factory.Create<UserBuilder>();
34+
public static OrgBuilder Org() => Factory.Create<OrgBuilder>();
2835
}
2936
}

tests/Examples/ExampleFactory.cs

+5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using Queo.Commons.Builders.Model.Examples.Car.Builders;
55
using Queo.Commons.Builders.Model.Examples.DAG;
66
using Queo.Commons.Builders.Model.Examples.Person;
7+
using Queo.Commons.Builders.Model.Examples.Relations;
78
using Queo.Commons.Builders.Model.Examples.Tree;
89
using Queo.Commons.Builders.Model.Factory;
910

@@ -33,6 +34,10 @@ public TBuilder Create<TBuilder>()
3334
Type car when car == typeof(CarBuilder) => new CarBuilder(this),
3435
Type proxy when proxy == typeof(ProxyBuilder) => new ProxyBuilder(this),
3536
Type wheel when wheel == typeof(WheelBuilder) => new WheelBuilder(this),
37+
Type t when t == typeof(CountryBuilder) => new CountryBuilder(this),
38+
Type t when t == typeof(PresidentBuilder) => new PresidentBuilder(this),
39+
Type t when t == typeof(UserBuilder) => new UserBuilder(this),
40+
Type t when t == typeof(OrgBuilder) => new OrgBuilder(this),
3641
_ => throw new InvalidOperationException($"Factory does not know how to instantiate: {typeof(TBuilder).Name}")
3742
};
3843

tests/Examples/Relations/Country.cs

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace Queo.Commons.Builders.Model.Examples.Relations;
2+
3+
public class Country(string name)
4+
{
5+
public string Name => name;
6+
public President? President { set; get; }
7+
}
+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
2+
using System;
3+
4+
using Queo.Commons.Builders.Model.Builder;
5+
using Queo.Commons.Builders.Model.Factory;
6+
7+
namespace Queo.Commons.Builders.Model.Examples.Relations;
8+
9+
public class CountryBuilder : ModelBuilder<Country>
10+
{
11+
private string _name;
12+
private IBuilder<President>? _president;
13+
14+
public CountryBuilder(IBuilderFactory factory) : base(factory)
15+
{
16+
_name = $"Country-{BuilderIndex}";
17+
_president = null;
18+
}
19+
20+
public CountryBuilder WithName(string name) => Set(() => _name = name);
21+
public CountryBuilder WithPresident(Action<PresidentBuilder> buildAction) => Set(() =>
22+
{
23+
var builder = FromAction<PresidentBuilder, President>(buildAction);
24+
builder.WithCountry(this);
25+
_president = builder;
26+
});
27+
28+
protected override Country BuildModel() => new(_name);
29+
30+
protected override void AfterModel(Country model)
31+
{
32+
if (_president is not null)
33+
{
34+
// President can only be build, if the country is already available
35+
model.President = _president.Build();
36+
}
37+
}
38+
39+
protected override CountryBuilder Set(Action action) => Set<CountryBuilder>(action);
40+
}

tests/Examples/Relations/Org.cs

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System.Collections.Generic;
2+
3+
namespace Queo.Commons.Builders.Model.Examples.Relations;
4+
5+
public class Org(string name, User admin)
6+
{
7+
public string Name => name;
8+
public User Admin => admin;
9+
10+
public ICollection<User> Members { get; } = [];
11+
}
12+
+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System;
2+
3+
using Queo.Commons.Builders.Model.Builder;
4+
using Queo.Commons.Builders.Model.Factory;
5+
6+
namespace Queo.Commons.Builders.Model.Examples.Relations;
7+
8+
public class OrgBuilder : ModelBuilder<Org>
9+
{
10+
public string _name;
11+
public IBuilder<User> _admin;
12+
public BuilderCollection<UserBuilder, User> _members;
13+
14+
public OrgBuilder(IBuilderFactory factory) : base(factory)
15+
{
16+
_name = $"Org-{BuilderIndex}";
17+
_admin = factory.Create<UserBuilder>();
18+
19+
_members = new(factory);
20+
}
21+
22+
public OrgBuilder WithName(string name) => Set(() => _name = name);
23+
24+
public OrgBuilder WithAdmin(IBuilder<User> admin) => Set(() => _admin = admin);
25+
public OrgBuilder WithAdmin(Action<UserBuilder> buildAction) => Set(() =>
26+
{
27+
_admin = FromAction<UserBuilder, User>(buildAction);
28+
});
29+
30+
public OrgBuilder AddMember(IBuilder<User> member) => Set(() => _members.Add(member));
31+
public OrgBuilder AddMember(Action<UserBuilder> buildAction) => Set(() =>
32+
{
33+
_members.Add(buildAction);
34+
});
35+
36+
protected override Org BuildModel()
37+
{
38+
User admin = _admin.Build();
39+
Org o = new Org(_name, _admin.Build());
40+
41+
admin.Org = o;
42+
o.Members.Add(admin);
43+
44+
foreach (var member in _members.BuildModels())
45+
{
46+
member.Org = o;
47+
o.Members.Add(member);
48+
}
49+
50+
return o;
51+
}
52+
53+
protected override OrgBuilder Set(Action action) => Set<OrgBuilder>(action);
54+
}
55+

tests/Examples/Relations/President.cs

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace Queo.Commons.Builders.Model.Examples.Relations;
2+
3+
public class President(string name, Country country)
4+
{
5+
public string Name => name;
6+
public Country Country => country;
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
2+
using System;
3+
4+
using Queo.Commons.Builders.Model.Builder;
5+
using Queo.Commons.Builders.Model.Factory;
6+
7+
namespace Queo.Commons.Builders.Model.Examples.Relations;
8+
9+
public class PresidentBuilder : ModelBuilder<President>
10+
{
11+
private string _name;
12+
private IBuilder<Country> _country;
13+
14+
public PresidentBuilder(IBuilderFactory factory) : base(factory)
15+
{
16+
_name = $"President-{BuilderIndex}";
17+
_country = factory.Create<CountryBuilder>();
18+
}
19+
20+
public PresidentBuilder WithName(string name) => Set(() => _name = name);
21+
public PresidentBuilder WithCountry(IBuilder<Country> country) => Set(() => _country = country);
22+
public PresidentBuilder WithCountry(Action<CountryBuilder> buildAction) => Set(() =>
23+
{
24+
_country = FromAction<CountryBuilder, Country>(buildAction);
25+
});
26+
27+
protected override President BuildModel()
28+
{
29+
President p = new(_name, _country.Build());
30+
_country.Build().President = p;
31+
32+
return p;
33+
}
34+
protected override PresidentBuilder Set(Action action) => Set<PresidentBuilder>(action);
35+
}
36+

tests/Examples/Relations/User.cs

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace Queo.Commons.Builders.Model.Examples.Relations;
2+
3+
public class User(string name)
4+
{
5+
public string Name = name;
6+
public Org? Org { set; get; }
7+
}
+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
2+
using System;
3+
4+
using Queo.Commons.Builders.Model.Builder;
5+
using Queo.Commons.Builders.Model.Factory;
6+
7+
namespace Queo.Commons.Builders.Model.Examples.Relations;
8+
9+
public class UserBuilder : ModelBuilder<User>
10+
{
11+
private string _name;
12+
private IBuilder<Org>? _org;
13+
14+
public UserBuilder(IBuilderFactory factory) : base(factory)
15+
{
16+
_name = $"User-{BuilderIndex}";
17+
_org = null;
18+
}
19+
20+
public UserBuilder WithName(string name) => Set(() => _name = name);
21+
public UserBuilder WithOrg(IBuilder<Org> org) => Set(() => _org = org);
22+
public UserBuilder WithOrg(Action<OrgBuilder> buildAction) => Set(() =>
23+
{
24+
_org = FromAction<OrgBuilder, Org>(buildAction);
25+
});
26+
27+
protected override User BuildModel() => new(_name);
28+
29+
protected override void AfterModel(User model)
30+
{
31+
if (_org is not null)
32+
{
33+
var org = _org.Build();
34+
model.Org = org;
35+
org.Members.Add(model);
36+
}
37+
}
38+
39+
protected override UserBuilder Set(Action action) => Set<UserBuilder>(action);
40+
}

0 commit comments

Comments
 (0)