added observer to repository and updated tests
This commit is contained in:
parent
6307850721
commit
5e2d2ddde5
|
|
@ -36,6 +36,9 @@ public:
|
|||
|
||||
void update_name(const std::string& name) const;
|
||||
|
||||
virtual void on_attach() const = 0;
|
||||
virtual void on_detach() const = 0;
|
||||
|
||||
endpoint_iterator register_relation_endpoint(const std::type_index &type, const std::shared_ptr<relation_endpoint> &endpoint);
|
||||
void unregister_relation_endpoint(const std::type_index &type);
|
||||
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ private:
|
|||
return;
|
||||
}
|
||||
|
||||
const auto node = repository_node::make_node<Type>(repo_.repo(), "");
|
||||
const auto node = repository_node::make_node<Type>(repo_.repo(), "", []{ return std::make_unique<Type>(); });
|
||||
if (auto result = repo_.attach_node(node)) {
|
||||
foreign_node_completer<Type>::complete(result.value());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,28 @@
|
|||
#define OBJECT_INFO_HPP
|
||||
|
||||
#include "matador/object/basic_object_info.hpp"
|
||||
#include "matador/object/observer.hpp"
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace matador::object {
|
||||
class repository_node;
|
||||
|
||||
template<typename Type>
|
||||
class observer_ptr {
|
||||
public:
|
||||
explicit observer_ptr(std::unique_ptr<observer<Type>> &&observer)
|
||||
: observer(std::move(observer)) {}
|
||||
|
||||
operator bool() const { return observer != nullptr; }
|
||||
|
||||
observer<Type> *get() const { return observer.get(); }
|
||||
observer<Type> &operator*() const { return *observer; }
|
||||
observer<Type> *operator->() const { return observer.get(); }
|
||||
|
||||
private:
|
||||
std::unique_ptr<observer<Type>> observer;
|
||||
};
|
||||
template<typename Type>
|
||||
class object_info final : public basic_object_info {
|
||||
public:
|
||||
|
|
@ -27,9 +43,25 @@ public:
|
|||
const Type &prototype() const { return prototype_; }
|
||||
std::unique_ptr<Type> create() const { return creator_(); }
|
||||
|
||||
void on_attach() const override {
|
||||
for (auto &observer : observers_) {
|
||||
observer->on_attach(*node_, prototype_);
|
||||
}
|
||||
}
|
||||
|
||||
void on_detach() const override {
|
||||
for (auto &observer : observers_) {
|
||||
observer->on_detach(*node_, prototype_);
|
||||
}
|
||||
}
|
||||
|
||||
void register_observer(observer_ptr<Type> observer) {
|
||||
observers_.push_back(std::move(observer));
|
||||
}
|
||||
private:
|
||||
Type prototype_;
|
||||
create_func creator_{[]{ return std::make_unique<Type>(); }};
|
||||
std::vector<observer_ptr<Type>> observers_;
|
||||
};
|
||||
|
||||
template<typename Type>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,124 @@
|
|||
#ifndef MATADOR_OBSERVER_HPP
|
||||
#define MATADOR_OBSERVER_HPP
|
||||
|
||||
#include <typeindex>
|
||||
|
||||
namespace matador::object {
|
||||
class repository_node;
|
||||
|
||||
class abstract_observer {
|
||||
public:
|
||||
virtual ~abstract_observer() = default;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Constructs a basic_object_store_observer for the
|
||||
* give std::type_index.
|
||||
*
|
||||
* @param ti type index of the observer
|
||||
*/
|
||||
explicit abstract_observer(const std::type_index &ti);
|
||||
|
||||
public:
|
||||
/**
|
||||
* Return the type index of the observer.
|
||||
*
|
||||
* @return The type index of the observer.
|
||||
*/
|
||||
[[nodiscard]] const std::type_index& index() const;
|
||||
|
||||
private:
|
||||
std::type_index type_index_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @class observer
|
||||
* @tparam Type Type of the observer
|
||||
* @brief Base class for typed observer classes
|
||||
*
|
||||
* When interested in observe
|
||||
* - attach (prototype_node)
|
||||
* - detach (prototype_node)
|
||||
* - insert (an object)
|
||||
* - update (an object)
|
||||
* - delete (an object)
|
||||
* actions an observer class instance must be
|
||||
* registered within the repository.
|
||||
* Use this class as a base class for all observer classes.
|
||||
*/
|
||||
template < class Type >
|
||||
class observer : public abstract_observer {
|
||||
public:
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
observer() : abstract_observer(std::type_index(typeid(Type))) {}
|
||||
|
||||
/**
|
||||
* @brief Called on repository_node attachment
|
||||
*
|
||||
* When a prototype node is attached to the repository,
|
||||
* this is called after the attaching succeeded.
|
||||
*
|
||||
* @param node The attached repository_node
|
||||
* @param prototype The prototype object of the attached node
|
||||
*/
|
||||
virtual void on_attach(repository_node &node, const Type &prototype) = 0;
|
||||
|
||||
/**
|
||||
* @brief Called on repository_node detached
|
||||
*
|
||||
* When a prototype node is detached from the repository,
|
||||
* this is called before the detaching succeeded.
|
||||
*
|
||||
* @param node The to be detached repository_node
|
||||
* @param prototype The prototype object of the detached node
|
||||
*/
|
||||
virtual void on_detach(repository_node &node, const Type &prototype) = 0;
|
||||
|
||||
/**
|
||||
* @brief Called on object insertion.
|
||||
*
|
||||
* Called when an object is inserted
|
||||
* into the repository.
|
||||
*
|
||||
* @param obj The proxy of the inserted object.
|
||||
*/
|
||||
virtual void on_insert(Type &obj) = 0;
|
||||
|
||||
/**
|
||||
* @brief Called on object update.
|
||||
*
|
||||
* Called when an object is updated
|
||||
* in the repository.
|
||||
*
|
||||
* @param obj The proxy of the updated object.
|
||||
*/
|
||||
virtual void on_update(Type &obj) = 0;
|
||||
|
||||
/**
|
||||
* @brief Called on object deletion.
|
||||
*
|
||||
* Called when an object is deleted
|
||||
* from the repository.
|
||||
*
|
||||
* @param obj The proxy of the deleted object.
|
||||
*/
|
||||
virtual void on_delete(Type &obj) = 0;
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
template < class Type >
|
||||
class null_observer : public observer<Type> {
|
||||
public:
|
||||
template < class OtherType >
|
||||
explicit null_observer(const null_observer<OtherType> *) {}
|
||||
void on_attach(repository_node &, Type &) override {}
|
||||
void on_detach(repository_node &, Type &) override {}
|
||||
void on_insert(Type &) override {}
|
||||
void on_update(Type &) override {}
|
||||
void on_delete(Type &) override {}
|
||||
};
|
||||
}
|
||||
}
|
||||
#endif //MATADOR_OBSERVER_HPP
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
#include "matador/object/error_code.hpp"
|
||||
#include "matador/object/foreign_node_completer.hpp"
|
||||
#include "matador/object/object_info.hpp"
|
||||
#include "matador/object/observer.hpp"
|
||||
#include "matador/object/relation_completer.hpp"
|
||||
#include "matador/object/repository_node.hpp"
|
||||
#include "matador/object/repository_node_iterator.hpp"
|
||||
|
|
@ -37,43 +38,53 @@ public:
|
|||
*/
|
||||
explicit repository(std::string name = "");
|
||||
|
||||
template<typename Type>
|
||||
[[nodiscard]] utils::result<void, utils::error> attach(const std::string &name, const std::string &parent = "") {
|
||||
if (const auto it = nodes_by_type_.find(typeid(Type)); it == nodes_by_type_.end() ) {
|
||||
// if the type was not found
|
||||
auto node = repository_node::make_node<Type>(*this, name);
|
||||
if (auto result = attach_node(node, parent); !result) {
|
||||
return utils::failure(result.err());
|
||||
}
|
||||
foreign_node_completer<Type>::complete(node);
|
||||
relation_completer<Type>::complete(node);
|
||||
} else if (!has_node(name)) {
|
||||
it->second->update_name(name);
|
||||
nodes_by_name_[name] = it->second;
|
||||
if (const auto i = nodes_by_name_.find(""); i != nodes_by_name_.end()) {
|
||||
nodes_by_name_.erase(i);
|
||||
}
|
||||
relation_completer<Type>::complete(it->second);
|
||||
log_.info("attach: update node name to '%s' (type: %s)", it->second->name().c_str(), it->second->type_index().name());
|
||||
} else {
|
||||
// remove_node( )
|
||||
return utils::failure(make_error(error_code::NodeAlreadyExists, "Node '" + name + "' already exists"));
|
||||
}
|
||||
|
||||
return utils::ok<void>();
|
||||
template<typename Type, typename... Observers>
|
||||
[[nodiscard]] utils::result<void, utils::error> attach(const std::string &name, Observers&&... observers) {
|
||||
return attach_type<Type, Observers...>(name, std::string{}, std::forward<Observers>(observers)...);
|
||||
}
|
||||
|
||||
template<typename Type, typename SuperType>
|
||||
[[nodiscard]] utils::result<void, utils::error> attach(const std::string &name) {
|
||||
template<typename Type, typename SuperType, typename... Observers>
|
||||
[[nodiscard]] utils::result<void, utils::error> attach(const std::string &name, Observers&&... observers) {
|
||||
const auto ti = std::type_index(typeid(SuperType));
|
||||
auto result = find_node(ti);
|
||||
if (!result) {
|
||||
return utils::failure(make_error(error_code::NodeNotFound, "Parent node '" + std::string(ti.name()) + "' not found"));
|
||||
}
|
||||
|
||||
return attach<Type>(name, (*result)->name());
|
||||
return attach_type<Type, Observers...>(name, (*result)->name(), std::forward<Observers>(observers)...);
|
||||
}
|
||||
|
||||
template<typename Type, typename... Observers>
|
||||
[[nodiscard]] utils::result<void, utils::error> attach(const std::string &name, const std::string &parent, Observers&&... observers) {
|
||||
return attach_type<Type, Observers...>(name, parent, std::forward<Observers>(observers)...);
|
||||
}
|
||||
template<typename Type, typename... Observers>
|
||||
[[nodiscard]] utils::result<void, utils::error> attach_type(const std::string &name, const std::string &parent, Observers&&... observers) {
|
||||
if (const auto it = nodes_by_type_.find(typeid(Type)); it == nodes_by_type_.end() ) {
|
||||
// if the type was not found
|
||||
auto node = repository_node::make_node<Type>(*this, name, []{ return std::make_unique<Type>(); }, std::forward<Observers>(observers)...);
|
||||
if (auto result = attach_node(node, parent); !result) {
|
||||
return utils::failure(result.err());
|
||||
}
|
||||
|
||||
node->on_attach();
|
||||
|
||||
foreign_node_completer<Type>::complete(node);
|
||||
relation_completer<Type>::complete(node);
|
||||
} else if (!has_node(name)) {
|
||||
it->second->update_name(name);
|
||||
nodes_by_name_[name] = it->second;
|
||||
if (const auto i = nodes_by_name_.find(""); i != nodes_by_name_.end()) {
|
||||
nodes_by_name_.erase(i);
|
||||
}
|
||||
relation_completer<Type>::complete(it->second);
|
||||
log_.info("attach: update node name to '%s' (type: %s)", it->second->name().c_str(), it->second->type_index().name());
|
||||
} else {
|
||||
return utils::failure(make_error(error_code::NodeAlreadyExists, "Node '" + name + "' already exists"));
|
||||
}
|
||||
|
||||
return utils::ok<void>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Detaches a given node from the schema. If the
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ public:
|
|||
template< typename Type>
|
||||
using creator_func = std::function<std::unique_ptr<Type>()>;
|
||||
|
||||
template < typename Type >
|
||||
static std::shared_ptr<repository_node> make_node(repository& repo, const std::string& name, creator_func<Type> creator = []{ return std::make_unique<Type>(); }) {
|
||||
template < typename Type, typename... Observers >
|
||||
static std::shared_ptr<repository_node> make_node(repository& repo, const std::string& name, creator_func<Type> creator, Observers&&... observers) {
|
||||
const std::type_index ti(typeid(Type));
|
||||
auto node = std::shared_ptr<repository_node>(new repository_node(repo, name, ti));
|
||||
|
||||
|
|
@ -31,6 +31,7 @@ public:
|
|||
std::move(obj),
|
||||
std::forward<creator_func<Type>>(creator)
|
||||
);
|
||||
(info->register_observer(observer_ptr<Type>(std::unique_ptr<Observers>(new Observers(std::forward<Observers>(observers))))), ...);
|
||||
node->info_ = std::move(info);
|
||||
|
||||
return node;
|
||||
|
|
@ -63,6 +64,9 @@ public:
|
|||
|
||||
[[nodiscard]] bool has_children() const;
|
||||
|
||||
void on_attach() const;
|
||||
void on_detach() const;
|
||||
|
||||
private:
|
||||
explicit repository_node(repository& repo);
|
||||
repository_node(repository& repo, const std::type_index& ti);
|
||||
|
|
|
|||
|
|
@ -33,14 +33,14 @@ public:
|
|||
explicit schema(sql::connection_pool &pool);
|
||||
schema(sql::connection_pool &pool, const std::string &name);
|
||||
|
||||
template<typename Type>
|
||||
[[nodiscard]] utils::result<void, utils::error> attach(const std::string &name, const std::string &parent = "") {
|
||||
return repo_.attach<Type>(name, parent);
|
||||
template<typename Type, template <typename> class ObserverType>
|
||||
[[nodiscard]] utils::result<void, utils::error> attach(const std::string &name, const std::string &parent = "", std::initializer_list<ObserverType<Type>*> observer = {}) {
|
||||
return repo_.attach<Type>(name, parent, observer);
|
||||
}
|
||||
|
||||
template<typename Type, typename SuperType>
|
||||
[[nodiscard]] utils::result<void, utils::error> attach(const std::string &name) {
|
||||
return repo_.attach<Type, SuperType>(name);
|
||||
template<typename Type, typename SuperType, template <typename> class ObserverType>
|
||||
[[nodiscard]] utils::result<void, utils::error> attach(const std::string &name, std::initializer_list<ObserverType<Type>*> observer = {}) {
|
||||
return repo_.attach<Type, SuperType>(name, observer);
|
||||
}
|
||||
|
||||
[[nodiscard]] utils::result<void, utils::error> create() const;
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ add_library(matador-core STATIC
|
|||
../../include/matador/object/object_info.hpp
|
||||
../../include/matador/object/object_proxy.hpp
|
||||
../../include/matador/object/object_ptr.hpp
|
||||
../../include/matador/object/observer.hpp
|
||||
../../include/matador/object/primary_key_resolver.hpp
|
||||
../../include/matador/object/relation_completer.hpp
|
||||
../../include/matador/object/relation_endpoint.hpp
|
||||
|
|
@ -83,6 +84,7 @@ add_library(matador-core STATIC
|
|||
object/internal/shadow_repository.cpp
|
||||
object/object.cpp
|
||||
object/object_generator.cpp
|
||||
object/observer.cpp
|
||||
object/relation_endpoint.cpp
|
||||
object/repository.cpp
|
||||
object/repository_node.cpp
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
#include "matador/object/observer.hpp"
|
||||
|
||||
namespace matador::object {
|
||||
abstract_observer::abstract_observer(const std::type_index& ti)
|
||||
: type_index_(ti) {}
|
||||
|
||||
const std::type_index& abstract_observer::index() const {
|
||||
return type_index_;
|
||||
}
|
||||
} // namespace matador::object
|
||||
|
|
@ -55,7 +55,11 @@ const repository& repository_node::schema() const {
|
|||
bool repository_node::has_children() const {
|
||||
return first_child_->next_sibling_ != last_child_;
|
||||
}
|
||||
void repository_node::on_attach() const {
|
||||
info_->on_attach();
|
||||
}
|
||||
|
||||
void repository_node::on_detach() const {}
|
||||
repository_node::node_ptr repository_node::next() const {
|
||||
// if we have a child, child is the next iterator to return
|
||||
// (if we don't iterate over the siblings)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include "../../models/department.hpp"
|
||||
#include "../../models/recipe.hpp"
|
||||
#include "../../models/person.hpp"
|
||||
|
||||
struct node {
|
||||
};
|
||||
|
|
@ -105,14 +106,14 @@ TEST_CASE("Test one to many", "[relation][one-to-many]") {
|
|||
REQUIRE(repo.contains("departments"));
|
||||
REQUIRE(repo.contains("employees"));
|
||||
|
||||
auto info = repo.basic_info("departments")->get();
|
||||
REQUIRE(!info.endpoints_empty());
|
||||
REQUIRE(info.endpoints_size() == 1);
|
||||
std::cout << *info.object();
|
||||
info = repo.basic_info("employees")->get();
|
||||
REQUIRE(!info.endpoints_empty());
|
||||
REQUIRE(info.endpoints_size() == 1);
|
||||
std::cout << *info.object();
|
||||
auto result = repo.basic_info("departments");
|
||||
REQUIRE(!result->get().endpoints_empty());
|
||||
REQUIRE(result->get().endpoints_size() == 1);
|
||||
std::cout << *result->get().object();
|
||||
result = repo.basic_info("employees");
|
||||
REQUIRE(!result->get().endpoints_empty());
|
||||
REQUIRE(result->get().endpoints_size() == 1);
|
||||
std::cout << *result->get().object();
|
||||
}
|
||||
|
||||
TEST_CASE("Test one to many reverse", "[relation][one-to-many][reverse]") {
|
||||
|
|
@ -127,14 +128,15 @@ TEST_CASE("Test one to many reverse", "[relation][one-to-many][reverse]") {
|
|||
REQUIRE(repo.contains("departments"));
|
||||
REQUIRE(repo.contains("employees"));
|
||||
|
||||
auto info = repo.basic_info("departments")->get();
|
||||
REQUIRE(!info.endpoints_empty());
|
||||
REQUIRE(info.endpoints_size() == 1);
|
||||
std::cout << *info.object();
|
||||
info = repo.basic_info("employees")->get();
|
||||
REQUIRE(!info.endpoints_empty());
|
||||
REQUIRE(info.endpoints_size() == 1);
|
||||
std::cout << *info.object();
|
||||
auto result = repo.basic_info("departments");
|
||||
REQUIRE(result.is_ok());
|
||||
REQUIRE(!result->get().endpoints_empty());
|
||||
REQUIRE(result->get().endpoints_size() == 1);
|
||||
std::cout << *result->get().object();
|
||||
result = repo.basic_info("employees");
|
||||
REQUIRE(!result->get().endpoints_empty());
|
||||
REQUIRE(result->get().endpoints_size() == 1);
|
||||
std::cout << *result->get().object();
|
||||
}
|
||||
|
||||
TEST_CASE("Test many to many relation", "[relation][many-to-many]") {
|
||||
|
|
@ -142,9 +144,9 @@ TEST_CASE("Test many to many relation", "[relation][many-to-many]") {
|
|||
|
||||
REQUIRE(repo.empty());
|
||||
|
||||
auto result = repo.attach<test::recipe>("recipes")
|
||||
auto res = repo.attach<test::recipe>("recipes")
|
||||
.and_then([&repo] { return repo.attach<test::ingredient>("ingredients"); });
|
||||
REQUIRE(result);
|
||||
REQUIRE(res);
|
||||
|
||||
REQUIRE(repo.size() == 3);
|
||||
REQUIRE(repo.contains("ingredients"));
|
||||
|
|
@ -156,16 +158,44 @@ TEST_CASE("Test many to many relation", "[relation][many-to-many]") {
|
|||
std::cout << *repo.basic_info("recipe_ingredients")->get().object();
|
||||
|
||||
|
||||
auto info = repo.basic_info("ingredients")->get();
|
||||
REQUIRE(!info.endpoints_empty());
|
||||
REQUIRE(info.endpoints_size() == 1);
|
||||
std::cout << *info.object();
|
||||
info = repo.basic_info("recipes")->get();
|
||||
REQUIRE(!info.endpoints_empty());
|
||||
REQUIRE(info.endpoints_size() == 1);
|
||||
std::cout << *info.object();
|
||||
info = repo.basic_info("recipe_ingredients")->get();
|
||||
REQUIRE(!info.endpoints_empty());
|
||||
REQUIRE(info.endpoints_size() == 2);
|
||||
std::cout << *info.object();
|
||||
auto result = repo.basic_info("ingredients");
|
||||
REQUIRE(!result->get().endpoints_empty());
|
||||
REQUIRE(result->get().endpoints_size() == 1);
|
||||
std::cout << *result->get().object();
|
||||
result = repo.basic_info("recipes");
|
||||
REQUIRE(!result->get().endpoints_empty());
|
||||
REQUIRE(result->get().endpoints_size() == 1);
|
||||
std::cout << *result->get().object();
|
||||
result = repo.basic_info("recipe_ingredients");
|
||||
REQUIRE(!result->get().endpoints_empty());
|
||||
REQUIRE(result->get().endpoints_size() == 2);
|
||||
std::cout << *result->get().object();
|
||||
}
|
||||
|
||||
template<typename Type>
|
||||
class test_observer : public matador::object::observer<Type> {
|
||||
public:
|
||||
void on_attach(object::repository_node& node, const Type& prototype) override {
|
||||
int i = 0;
|
||||
}
|
||||
void on_detach(object::repository_node& node, const Type& prototype) override {
|
||||
|
||||
}
|
||||
void on_insert(Type& obj) override {
|
||||
|
||||
}
|
||||
void on_update(Type& obj) override {
|
||||
|
||||
}
|
||||
void on_delete(Type& obj) override {
|
||||
|
||||
}
|
||||
};
|
||||
TEST_CASE("Test repository observer", "[repository][observer]") {
|
||||
object::repository repo;
|
||||
|
||||
REQUIRE(repo.empty());
|
||||
|
||||
const auto result = repo.attach<test::person>("person", test_observer<test::person>());
|
||||
REQUIRE(result.is_ok());
|
||||
}
|
||||
Loading…
Reference in New Issue