added observer to repository and updated tests

This commit is contained in:
Sascha Kühl 2025-12-18 16:13:36 +01:00
parent 6307850721
commit 5e2d2ddde5
11 changed files with 286 additions and 66 deletions

View File

@ -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);

View File

@ -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());
}

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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());
}