Skip to content

Commit

Permalink
Make Bean check columns in Getters (#25)
Browse files Browse the repository at this point in the history
* Add ValidateGetColumns flag on Bean along with logic to throw if requesting a column not in the Bean

* Break Bean creation out into BeanFactory, add BeanConfiguration layer for factory. Interfaces updated all round

* Provision BeanFactory in tests for dispensing beans

* Totally handle Bean configuration within Factory

* Fix documentation and move BeanConfiguration to more appropriate part of Api

* Add BeanFactoryTests class to check the effects of optional flags

* Minor fix for website text

* Rename BeanConfiguration getter to BeanOptions getter

* Add BeanOptions to website body

* Rename BeanConfiguration and BeanOptions and update all references

* Fix broken test for BeanFactory

* Slight refactor on Bean

* Prevent Bean.Get from validating get columns twice

closes #25
  • Loading branch information
Nick-Lucas authored Jul 30, 2016
1 parent bb6ac11 commit 8f9101b
Show file tree
Hide file tree
Showing 18 changed files with 275 additions and 23 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ packages/
project.lock.json
LimeBean.NuGet/*.nupkg
LimeBean.NuGet/nuget.exe
index.html
index.html
*.stackdump
28 changes: 21 additions & 7 deletions LimeBean.Tests/BeanCrudTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,26 @@
using System.Text;
using Xunit;

using LimeBean.Interfaces;

namespace LimeBean.Tests {

public class BeanCrudTests {

[Fact]
public void Dispense_Default() {
var crud = new BeanCrud(null, null, null);
IBeanFactory factory = new BeanFactory();
var crud = new BeanCrud(null, null, null, factory);
var bean = crud.Dispense("test");
Assert.Equal("test", bean.GetKind());
Assert.Equal(typeof(Bean), bean.GetType());
}

[Fact]
public void Dispense_Hooks() {
var crud = new BeanCrud(null, null, null);
IBeanFactory factory = new BeanFactory();
factory.Options.ValidateGetColumns = false;
var crud = new BeanCrud(null, null, null, factory);
var observer = new TracingObserver();
crud.AddObserver(observer);

Expand All @@ -32,7 +37,9 @@ public void Dispense_Hooks() {

[Fact]
public void Store() {
var crud = new BeanCrud(new InMemoryStorage(), null, new KeyUtil());
IBeanFactory factory = new BeanFactory();
factory.Options.ValidateGetColumns = false;
var crud = new BeanCrud(new InMemoryStorage(), null, new KeyUtil(), factory);
var observer = new TracingObserver();
crud.AddObserver(observer);

Expand All @@ -48,7 +55,9 @@ public void Store() {

[Fact]
public void Load() {
var crud = new BeanCrud(new InMemoryStorage(), null, new KeyUtil());
IBeanFactory factory = new BeanFactory();
factory.Options.ValidateGetColumns = false;
var crud = new BeanCrud(new InMemoryStorage(), null, new KeyUtil(), factory);
var observer = new TracingObserver();
crud.AddObserver(observer);

Expand All @@ -72,7 +81,9 @@ public void Load() {

[Fact]
public void Trash() {
var crud = new BeanCrud(new InMemoryStorage(), null, new KeyUtil());
IBeanFactory factory = new BeanFactory();
factory.Options.ValidateGetColumns = false;
var crud = new BeanCrud(new InMemoryStorage(), null, new KeyUtil(), factory);
var observer = new TracingObserver();
crud.AddObserver(observer);

Expand All @@ -96,7 +107,9 @@ public void Trash() {

[Fact]
public void RowToBean() {
var crud = new BeanCrud(new InMemoryStorage(), null, null);
IBeanFactory factory = new BeanFactory();
factory.Options.ValidateGetColumns = false;
var crud = new BeanCrud(new InMemoryStorage(), null, null, factory);
var observer = new TracingObserver();
crud.AddObserver(observer);

Expand Down Expand Up @@ -126,7 +139,8 @@ public void RowToBean() {

[Fact]
public void PreventDirectInstantiation() {
var crud = new BeanCrud(null, null, null);
IBeanFactory factory = new BeanFactory();
var crud = new BeanCrud(null, null, null, factory);

Assert.Throws<InvalidOperationException>(delegate() {
crud.Store(new Tracer());
Expand Down
61 changes: 61 additions & 0 deletions LimeBean.Tests/BeanFactoryTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Xunit;

using LimeBean.Interfaces;
using LimeBean.Exceptions;

namespace LimeBean.Tests {

public class BeanFactoryTests {

[Fact]
public void Dispense_ValidateGetColumns_Test() {
IBeanFactory factory = new BeanFactory();
object one;
object two;
Bean bean;

Func<bool, Bean> make = validateColumns => {
factory.Options.ValidateGetColumns = false;
Bean b = factory.Dispense("test");
Assert.Equal(typeof(Bean), b.GetType());
Assert.Equal(false, b.ValidateGetColumns);
b.Put("one", 1);
return b;
};

// With ValidateGetColumns switched off
bean = make(false);
one = (int)bean["one"];
Assert.Equal(1, one);
one = bean.Get<int>("one");
Assert.Equal(1, one);
two = bean.Get<int>("two");
Assert.Equal(0, two);
two = bean["two"];
Assert.Equal(null, two);

// With ValidateGetColumns switched on
bean = make(true);
one = (int)bean["one"];
Assert.Equal(1, one);
one = bean.Get<int>("one");
Assert.Equal(1, one);
try {
two = bean["two"];
} catch (Exception e) {
Assert.IsType(typeof(ColumnNotFoundException), e);
}
try {
two = bean.Get<int>("two");
} catch (Exception e) {
Assert.IsType(typeof(ColumnNotFoundException), e);
}
}

}

}
3 changes: 2 additions & 1 deletion LimeBean.Tests/DatabaseBeanFinderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ public DatabaseBeanFinderTests() {
IDatabaseAccess db = new DatabaseAccess(_conn, details);
IKeyAccess keys = new KeyUtil();
IStorage storage = new DatabaseStorage(details, db, keys);
IBeanCrud crud = new BeanCrud(storage, db, keys);
IBeanFactory factory = new BeanFactory();
IBeanCrud crud = new BeanCrud(storage, db, keys, factory);
IBeanFinder finder = new DatabaseBeanFinder(details, db, crud);

db.Exec("create table foo(x)");
Expand Down
3 changes: 2 additions & 1 deletion LimeBean.Tests/IntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ public void ImplicitTransactionsOnStoreAndTrash() {
IDatabaseAccess db = new DatabaseAccess(conn, details);
IKeyAccess keys = new KeyUtil();
DatabaseStorage storage = new DatabaseStorage(details, db, keys);
IBeanCrud crud = new BeanCrud(storage, db, keys);
IBeanFactory factory = new BeanFactory();
IBeanCrud crud = new BeanCrud(storage, db, keys, factory);

storage.EnterFluidMode();

Expand Down
1 change: 1 addition & 0 deletions LimeBean.Tests/LimeBean.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="AssertExtensions.cs" />
<Compile Include="BeanFactoryTests.cs" />
<Compile Include="CustomKeysTests.cs" />
<Compile Include="DatabaseStorageTests_MsSql.cs" />
<Compile Include="DatabaseStorageTests_PgSql.cs" />
Expand Down
19 changes: 18 additions & 1 deletion LimeBean.Website/Body.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ void CRUD(BeanApi api) {
// Store() will Create or Update a record intelligently
var id = api.Store(bean);

// Store also returns the Primary Key for the saved Bean, even for [multi-column/compound keys](#primary-keys)
// Store also returns the Primary Key for the saved Bean, even for multi-column/compound keys
Console.WriteLine(id);
#endif
/// **Read** and **Update**
Expand Down Expand Up @@ -146,6 +146,23 @@ void TypedAccessors(Bean bean) {
/// See also: [Custom Bean Classes](#custom-bean-classes)
}

void BeanOptions(BeanApi api) {
/// ## Bean Options
/// You can configure the BeanAPI to dispense new Beans with some default options
///
/// **.ValidateGetColumns**
#if CODE
// Sets whether a Bean throws `ColumnNotFoundException` if
// you request a column which isn't stored in the Bean. True by default
api.BeanOptions.ValidateGetColumns = true;

Bean bean = api.Dispense("books");
bean.Put("ColumnOne", 1); // Add a single column
int one = bean.Get<int>("ColumnOne"); // OK
int two = bean.Get<int>("ColumnTwo"); // throws ColumnNotFoundException
#endif
}

void FluidMode(BeanApi api) {
/// ## Fluid Mode
/// LimeBean mitigates the common inconvenience associated with relational databases,
Expand Down
21 changes: 19 additions & 2 deletions LimeBean/Bean.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,11 @@ public override string ToString() {
/// </summary>
/// <param name="name">Name of the Column to Get or Set</param>
public object this[string name] {
get { return _props.GetSafe(name); }
get {
if (ValidateGetColumns)
ValidateColumnExists(name);

return _props.GetSafe(name); }
set {
SaveDirtyBackup(name, value);
_props[name] = value;
Expand Down Expand Up @@ -94,6 +98,19 @@ public IEnumerable<string> Columns {
}


// Bean Options

internal bool ValidateGetColumns {
get;
set;
}

private void ValidateColumnExists(string name) {
if (_props.ContainsKey(name) == false)
throw Exceptions.ColumnNotFoundException.New(this, name);
}


// Import / Export

internal IDictionary<string, object> Export() {
Expand All @@ -109,7 +126,7 @@ internal void Import(IDictionary<string, object> data) {
// Dirty tracking

void SaveDirtyBackup(string name, object newValue) {
var currentValue = this[name];
var currentValue = _props.GetSafe(name);
if(Equals(newValue, currentValue))
return;

Expand Down
18 changes: 17 additions & 1 deletion LimeBean/BeanApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public partial class BeanApi : IBeanApi {
KeyUtil _keyUtil;
DatabaseStorage _storage;
IBeanCrud _crud;
IBeanFactory _factory;
IBeanFinder _finder;

public BeanApi(string connectionString, DbProviderFactory factory) {
Expand Down Expand Up @@ -74,13 +75,21 @@ DatabaseStorage Storage {
IBeanCrud Crud {
get {
if(_crud == null) {
_crud = new BeanCrud(Storage, Db, KeyUtil);
_crud = new BeanCrud(Storage, Db, KeyUtil, Factory);
_crud.AddObserver(new BeanApiLinker(this));
}
return _crud;
}
}

IBeanFactory Factory {
get {
if (_factory == null)
_factory = new BeanFactory();
return _factory;
}
}

IBeanFinder Finder {
get {
if(_finder == null)
Expand Down Expand Up @@ -137,6 +146,13 @@ public void Dispose() {
_connectionContainer.Dispose();
}

/// <summary>
/// Provides configuration of default Bean
/// options, for Beans created by Limebean
/// </summary>
public IBeanOptions BeanOptions {
get { return Factory.Options; }
}

// IBeanCrud

Expand Down
12 changes: 7 additions & 5 deletions LimeBean/BeanCrud.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ class BeanCrud : IBeanCrud {
ITransactionSupport _transactionSupport;
IKeyAccess _keyAccess;
IList<BeanObserver> _observers;
IBeanFactory _factory;

public BeanCrud(IStorage storage, ITransactionSupport transactionSupport, IKeyAccess keys) {
public BeanCrud(IStorage storage, ITransactionSupport transactionSupport, IKeyAccess keys, IBeanFactory factory) {
_storage = storage;
_transactionSupport = transactionSupport;
_keyAccess = keys;
_observers = new List<BeanObserver>();
_factory = factory;
DirtyTracking = true;
}

Expand All @@ -32,11 +34,13 @@ public void RemoveObserver(BeanObserver observer) {
}

public Bean Dispense(string kind) {
return ContinueDispense(new Bean(kind));
Bean bean = _factory.Dispense(kind);
return ContinueDispense(bean);
}

public T Dispense<T>() where T : Bean, new() {
return ContinueDispense(new T());
T bean = _factory.Dispense<T>();
return ContinueDispense(bean);
}

public Bean RowToBean(string kind, IDictionary<string, object> row) {
Expand Down Expand Up @@ -109,8 +113,6 @@ public void Trash(Bean bean) {
}

T ContinueDispense<T>(T bean) where T : Bean {
bean.Dispensed = true;

bean.AfterDispense();
foreach(var observer in _observers)
observer.AfterDispense(bean);
Expand Down
40 changes: 40 additions & 0 deletions LimeBean/BeanFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using LimeBean.Interfaces;

namespace LimeBean {

internal class BeanFactory : IBeanFactory {
internal BeanFactory() { }

IBeanOptions _config;
public IBeanOptions Options {
get {
if (_config == null)
_config = new BeanOptions();
return _config;
}
}

public Bean Dispense(string kind) {
Bean bean = new Bean(kind);
return ConfigureBean(bean);
}

public T Dispense<T>() where T : Bean, new() {
T bean = new T();
return ConfigureBean(bean);
}

private T ConfigureBean<T>(T bean) where T : Bean {
bean.Dispensed = true;
bean.ValidateGetColumns = Options.ValidateGetColumns;
return bean;
}

}

}
Loading

0 comments on commit 8f9101b

Please sign in to comment.