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; 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); 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); void unregister_relation_endpoint(const std::type_index &type);

View File

@ -98,7 +98,7 @@ private:
return; 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)) { if (auto result = repo_.attach_node(node)) {
foreign_node_completer<Type>::complete(result.value()); foreign_node_completer<Type>::complete(result.value());
} }

View File

@ -2,12 +2,28 @@
#define OBJECT_INFO_HPP #define OBJECT_INFO_HPP
#include "matador/object/basic_object_info.hpp" #include "matador/object/basic_object_info.hpp"
#include "matador/object/observer.hpp"
#include <functional> #include <functional>
namespace matador::object { namespace matador::object {
class repository_node; 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> template<typename Type>
class object_info final : public basic_object_info { class object_info final : public basic_object_info {
public: public:
@ -27,9 +43,25 @@ public:
const Type &prototype() const { return prototype_; } const Type &prototype() const { return prototype_; }
std::unique_ptr<Type> create() const { return creator_(); } 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: private:
Type prototype_; Type prototype_;
create_func creator_{[]{ return std::make_unique<Type>(); }}; create_func creator_{[]{ return std::make_unique<Type>(); }};
std::vector<observer_ptr<Type>> observers_;
}; };
template<typename Type> 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/error_code.hpp"
#include "matador/object/foreign_node_completer.hpp" #include "matador/object/foreign_node_completer.hpp"
#include "matador/object/object_info.hpp" #include "matador/object/object_info.hpp"
#include "matador/object/observer.hpp"
#include "matador/object/relation_completer.hpp" #include "matador/object/relation_completer.hpp"
#include "matador/object/repository_node.hpp" #include "matador/object/repository_node.hpp"
#include "matador/object/repository_node_iterator.hpp" #include "matador/object/repository_node_iterator.hpp"
@ -37,14 +38,37 @@ public:
*/ */
explicit repository(std::string name = ""); explicit repository(std::string name = "");
template<typename Type> template<typename Type, typename... Observers>
[[nodiscard]] utils::result<void, utils::error> attach(const std::string &name, const std::string &parent = "") { [[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, 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<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 (const auto it = nodes_by_type_.find(typeid(Type)); it == nodes_by_type_.end() ) {
// if the type was not found // if the type was not found
auto node = repository_node::make_node<Type>(*this, name); 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) { if (auto result = attach_node(node, parent); !result) {
return utils::failure(result.err()); return utils::failure(result.err());
} }
node->on_attach();
foreign_node_completer<Type>::complete(node); foreign_node_completer<Type>::complete(node);
relation_completer<Type>::complete(node); relation_completer<Type>::complete(node);
} else if (!has_node(name)) { } else if (!has_node(name)) {
@ -56,25 +80,12 @@ public:
relation_completer<Type>::complete(it->second); 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()); log_.info("attach: update node name to '%s' (type: %s)", it->second->name().c_str(), it->second->type_index().name());
} else { } else {
// remove_node( )
return utils::failure(make_error(error_code::NodeAlreadyExists, "Node '" + name + "' already exists")); return utils::failure(make_error(error_code::NodeAlreadyExists, "Node '" + name + "' already exists"));
} }
return utils::ok<void>(); return utils::ok<void>();
} }
template<typename Type, typename SuperType>
[[nodiscard]] utils::result<void, utils::error> attach(const std::string &name) {
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());
}
/** /**
* Detaches a given node from the schema. If the * Detaches a given node from the schema. If the
* node is a parent of other nodes, these nodes are * node is a parent of other nodes, these nodes are

View File

@ -20,8 +20,8 @@ public:
template< typename Type> template< typename Type>
using creator_func = std::function<std::unique_ptr<Type>()>; using creator_func = std::function<std::unique_ptr<Type>()>;
template < typename Type > template < typename Type, typename... Observers >
static std::shared_ptr<repository_node> make_node(repository& repo, const std::string& name, creator_func<Type> creator = []{ return std::make_unique<Type>(); }) { 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)); const std::type_index ti(typeid(Type));
auto node = std::shared_ptr<repository_node>(new repository_node(repo, name, ti)); auto node = std::shared_ptr<repository_node>(new repository_node(repo, name, ti));
@ -31,6 +31,7 @@ public:
std::move(obj), std::move(obj),
std::forward<creator_func<Type>>(creator) 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); node->info_ = std::move(info);
return node; return node;
@ -63,6 +64,9 @@ public:
[[nodiscard]] bool has_children() const; [[nodiscard]] bool has_children() const;
void on_attach() const;
void on_detach() const;
private: private:
explicit repository_node(repository& repo); explicit repository_node(repository& repo);
repository_node(repository& repo, const std::type_index& ti); repository_node(repository& repo, const std::type_index& ti);

View File

@ -33,14 +33,14 @@ public:
explicit schema(sql::connection_pool &pool); explicit schema(sql::connection_pool &pool);
schema(sql::connection_pool &pool, const std::string &name); schema(sql::connection_pool &pool, const std::string &name);
template<typename Type> template<typename Type, template <typename> class ObserverType>
[[nodiscard]] utils::result<void, utils::error> attach(const std::string &name, const std::string &parent = "") { [[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); return repo_.attach<Type>(name, parent, observer);
} }
template<typename Type, typename SuperType> template<typename Type, typename SuperType, template <typename> class ObserverType>
[[nodiscard]] utils::result<void, utils::error> attach(const std::string &name) { [[nodiscard]] utils::result<void, utils::error> attach(const std::string &name, std::initializer_list<ObserverType<Type>*> observer = {}) {
return repo_.attach<Type, SuperType>(name); return repo_.attach<Type, SuperType>(name, observer);
} }
[[nodiscard]] utils::result<void, utils::error> create() const; [[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_info.hpp
../../include/matador/object/object_proxy.hpp ../../include/matador/object/object_proxy.hpp
../../include/matador/object/object_ptr.hpp ../../include/matador/object/object_ptr.hpp
../../include/matador/object/observer.hpp
../../include/matador/object/primary_key_resolver.hpp ../../include/matador/object/primary_key_resolver.hpp
../../include/matador/object/relation_completer.hpp ../../include/matador/object/relation_completer.hpp
../../include/matador/object/relation_endpoint.hpp ../../include/matador/object/relation_endpoint.hpp
@ -83,6 +84,7 @@ add_library(matador-core STATIC
object/internal/shadow_repository.cpp object/internal/shadow_repository.cpp
object/object.cpp object/object.cpp
object/object_generator.cpp object/object_generator.cpp
object/observer.cpp
object/relation_endpoint.cpp object/relation_endpoint.cpp
object/repository.cpp object/repository.cpp
object/repository_node.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 { bool repository_node::has_children() const {
return first_child_->next_sibling_ != last_child_; 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 { repository_node::node_ptr repository_node::next() const {
// if we have a child, child is the next iterator to return // if we have a child, child is the next iterator to return
// (if we don't iterate over the siblings) // (if we don't iterate over the siblings)

View File

@ -4,6 +4,7 @@
#include "../../models/department.hpp" #include "../../models/department.hpp"
#include "../../models/recipe.hpp" #include "../../models/recipe.hpp"
#include "../../models/person.hpp"
struct node { struct node {
}; };
@ -105,14 +106,14 @@ TEST_CASE("Test one to many", "[relation][one-to-many]") {
REQUIRE(repo.contains("departments")); REQUIRE(repo.contains("departments"));
REQUIRE(repo.contains("employees")); REQUIRE(repo.contains("employees"));
auto info = repo.basic_info("departments")->get(); auto result = repo.basic_info("departments");
REQUIRE(!info.endpoints_empty()); REQUIRE(!result->get().endpoints_empty());
REQUIRE(info.endpoints_size() == 1); REQUIRE(result->get().endpoints_size() == 1);
std::cout << *info.object(); std::cout << *result->get().object();
info = repo.basic_info("employees")->get(); result = repo.basic_info("employees");
REQUIRE(!info.endpoints_empty()); REQUIRE(!result->get().endpoints_empty());
REQUIRE(info.endpoints_size() == 1); REQUIRE(result->get().endpoints_size() == 1);
std::cout << *info.object(); std::cout << *result->get().object();
} }
TEST_CASE("Test one to many reverse", "[relation][one-to-many][reverse]") { 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("departments"));
REQUIRE(repo.contains("employees")); REQUIRE(repo.contains("employees"));
auto info = repo.basic_info("departments")->get(); auto result = repo.basic_info("departments");
REQUIRE(!info.endpoints_empty()); REQUIRE(result.is_ok());
REQUIRE(info.endpoints_size() == 1); REQUIRE(!result->get().endpoints_empty());
std::cout << *info.object(); REQUIRE(result->get().endpoints_size() == 1);
info = repo.basic_info("employees")->get(); std::cout << *result->get().object();
REQUIRE(!info.endpoints_empty()); result = repo.basic_info("employees");
REQUIRE(info.endpoints_size() == 1); REQUIRE(!result->get().endpoints_empty());
std::cout << *info.object(); REQUIRE(result->get().endpoints_size() == 1);
std::cout << *result->get().object();
} }
TEST_CASE("Test many to many relation", "[relation][many-to-many]") { 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()); 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"); }); .and_then([&repo] { return repo.attach<test::ingredient>("ingredients"); });
REQUIRE(result); REQUIRE(res);
REQUIRE(repo.size() == 3); REQUIRE(repo.size() == 3);
REQUIRE(repo.contains("ingredients")); 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(); std::cout << *repo.basic_info("recipe_ingredients")->get().object();
auto info = repo.basic_info("ingredients")->get(); auto result = repo.basic_info("ingredients");
REQUIRE(!info.endpoints_empty()); REQUIRE(!result->get().endpoints_empty());
REQUIRE(info.endpoints_size() == 1); REQUIRE(result->get().endpoints_size() == 1);
std::cout << *info.object(); std::cout << *result->get().object();
info = repo.basic_info("recipes")->get(); result = repo.basic_info("recipes");
REQUIRE(!info.endpoints_empty()); REQUIRE(!result->get().endpoints_empty());
REQUIRE(info.endpoints_size() == 1); REQUIRE(result->get().endpoints_size() == 1);
std::cout << *info.object(); std::cout << *result->get().object();
info = repo.basic_info("recipe_ingredients")->get(); result = repo.basic_info("recipe_ingredients");
REQUIRE(!info.endpoints_empty()); REQUIRE(!result->get().endpoints_empty());
REQUIRE(info.endpoints_size() == 2); REQUIRE(result->get().endpoints_size() == 2);
std::cout << *info.object(); 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());
} }