diff --git a/README.md b/README.md index ec39234..a41c270 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,48 @@ These three concepts have a specific meaning in the context of sumo_db. TO implement an adapter, you must implement two behaviors: `sumo_backend` and `sumo_store`. +## Events + +Sumo dispatches events when things happen. An Event has this structure: +```erlang +{EventId, Model, Event, Args} +``` +- `EventId` is a `reference()` which identifies the event +- `Model` is the model where the event happend, for example, if we are creating a new entitiy in the model `people` the value of `Model` would be `people`. +- `Event` is the type of the event. +- `Args` extra data sent. + +Supported types of events: + +- `pre_persisted` just before persisting some entity. This event has the entity we want to persist as `Args`. It is dispatched on this function: + - `sumo:persist/2` +- `persisted` just after persisting some entity. This event has the persisted entity as `Args`. This Event has the same `EventId` as its `pre_persisted` event. It is dispatched on this function: + - `sumo:persist/2` +- `pre_delete_all` just before deleting all entities for a model. This event has no `Args`. It is dispatched on this function: + - `sumo:delete_all/1` +- `deleted_all` just after deleting all entities for a model. This event has no `Args`. This Event has the same `EventId` as its `pre_delete_all` event. It is dispatched on this function: + - `sumo:delete_all/1` +- `pre_deleted` just before deleting an entity. This event has the entity id as `Args`. It is dispatched on this function: + - `sumo:delete/2` +- `deleted` just after deleting an entity. This event has the entity id as `Args`. This Event has the same `EventId` as its `pre_deleted` event. It is dispatched on this function: + - `sumo:delete/2` +- `pre_deleted_total` just before deleting by some delete conditions. This event has the sumo conditions as `Args`. It is dispatched on this function: + - `sumo:delete_by/2` +- `deleted_total` just after deleting by some delete conditions. This event has a list with the number of entities deleted and the delete conditions as `Args`. This Event has the same `EventId` as its `pre_deleted_total` event. It is dispatched on this function: + - `sumo:delete_by/2` +- `pre_schema_created` just before creating a sumo schema. This event has no `Args`. It is dispatched on this function: + - `sumo:create_schema/2` +- `schema_created` just after creating a sumo schema. This event has no `Args`. This Event has the same `EventId` as its `pre_schema_created` event. It is dispatched on this function: + - `sumo:create_schema/2` + +Sumo requires users to add their own `gen_event`'s in order to handle those events. In order to add them Users have to configure sumo properly. In the `config` file we can add them like this under `sumo_db` configuration: +```erlang +{events, [ + {'_', sumo_test_people_events_manager}, + {people, sumo_test_people_events_manager} + ]} +``` +Sumo allows us to add a `gen_event` to one type of model (i.e. `people`) or for all (`'_'`). ## Example diff --git a/src/adapter_test_helpers/sumo_basic_test_helper.erl b/src/adapter_test_helpers/sumo_basic_test_helper.erl index 0f96c2e..b138698 100644 --- a/src/adapter_test_helpers/sumo_basic_test_helper.erl +++ b/src/adapter_test_helpers/sumo_basic_test_helper.erl @@ -115,8 +115,8 @@ delete_all(Config) -> {_, Name} = lists:keyfind(name, 1, Config), sumo:delete_all(Name), - pick_up_event({Name, pre_delete_all, []}), - pick_up_event({Name, delete_all, []}), + {EventId, Name, pre_delete_all, []} = pick_up_event(), + {EventId, Name, deleted_all, []} = pick_up_event(), [] = sumo:find_all(Name), 0 = sumo:count(Name), ok. @@ -131,8 +131,8 @@ delete(Config) -> 2 = sumo:delete_by(Name, Conditions), 6 = sumo:count(Name), - ok = pick_up_event({Name, pre_deleted_total, [Conditions]}), - ok = pick_up_event({Name, deleted_total, [2, Conditions]}), + {EventId, Name, pre_deleted_total, [Conditions]} = pick_up_event(), + {EventId, Name, deleted_total, [2, Conditions]} = pick_up_event(), Results = sumo:find_by(Name, Conditions), [] = Results, @@ -142,11 +142,12 @@ delete(Config) -> Id = Module:id(First), sumo:delete(Name, Id), - ok = pick_up_event({Name, pre_deleted, [Id]}), - ok = pick_up_event({Name, deleted, [Id]}), + % sumo:delete/2 uses internally sumo:delete_by/2, we handle those events too IdField = sumo_internal:id_field_name(Name), - ok = pick_up_event({Name, pre_deleted_total, [[{IdField, Id}]]}), - ok = pick_up_event({Name, deleted_total, [1, [{IdField, Id}]]}), + {EventId2, Name, pre_deleted, [Id]} = pick_up_event(), + {EventId4, Name, pre_deleted_total, [[{IdField, Id}]]} = pick_up_event(), + {EventId4, Name, deleted_total, [1, [{IdField, Id}]]} = pick_up_event(), + {EventId2, Name, deleted, [Id]} = pick_up_event(), NewAll = sumo:find_all(Name), [_] = All -- NewAll, @@ -178,12 +179,8 @@ check_proper_dates(Config) -> -spec init_store(atom()) -> ok. init_store(Name) -> sumo:create_schema(Name), - ok = pick_up_event({Name, pre_schema_created, []}), - ok = pick_up_event({Name, schema_created, []}), Module = sumo_config:get_prop_value(Name, module), sumo:delete_all(Name), - ok = pick_up_event({Name, pre_delete_all, []}), - ok = pick_up_event({Name, deleted_all, []}), DT = {Date, _} = calendar:universal_time(), @@ -223,17 +220,22 @@ init_store(Name) -> _:no_workers -> ok end, + clean_events(), ok. %%%============================================================================= %%% Internal functions %%%============================================================================= -pick_up_event(Event) -> - sumo_test_people_events_manager:pick_up_event(Event). +pick_up_event() -> + sumo_test_people_events_manager:pick_up_event(). + +clean_events() -> + sumo_test_people_events_manager:clean_events(). create(Name, Args) -> + clean_events(), Res = sumo:persist(Name, Args), - ok = pick_up_event({Name, pre_persisted, [Args]}), - ok = pick_up_event({Name, persisted, [Res]}), + {EventId, Name, pre_persisted, [Args]} = pick_up_event(), + {EventId, Name, persisted, [Res]} = pick_up_event(), Res. diff --git a/src/adapter_test_helpers/sumo_test_people_events_manager.erl b/src/adapter_test_helpers/sumo_test_people_events_manager.erl index 6e41f7e..9ab3579 100644 --- a/src/adapter_test_helpers/sumo_test_people_events_manager.erl +++ b/src/adapter_test_helpers/sumo_test_people_events_manager.erl @@ -11,14 +11,21 @@ handle_event/2 ]). --export([pick_up_event/1]). +-export([ + pick_up_event/0, + clean_events/0 +]). -record(state, {event_list = [] :: list()}). -type state() :: #state{}. --spec pick_up_event(tuple()) -> ok | no_event. -pick_up_event(Event) -> - gen_event:call(?MODULE, ?MODULE, {pick_up_event, Event}). +-spec pick_up_event() -> tuple() | no_event. +pick_up_event() -> + gen_event:call(?MODULE, ?MODULE, pick_up_event). + +-spec clean_events() -> ok. +clean_events() -> + gen_event:call(?MODULE, ?MODULE, clean_events). -spec init([]) -> {ok, state()}. init([]) -> @@ -31,12 +38,15 @@ handle_info(_Info, State) -> -spec handle_call(Event, State) -> Result when Event :: term(), State :: state(), - Result :: {ok, ok | no_event | not_implemented, state()}. -handle_call({pick_up_event, Event}, #state{event_list = EventList} = State) -> - case lists:member(Event, EventList) of - true -> - {ok, ok, #state{event_list = lists:delete(Event, EventList)}}; - false -> + Result :: {ok, ok | no_event | not_implemented | term(), state()}. +handle_call(clean_events, _State) -> + {ok, ok, #state{event_list = []}}; +handle_call(pick_up_event, #state{event_list = EventList} = State) -> + try + Event = lists:last(EventList), + {ok, Event, #state{event_list = lists:delete(Event, EventList)}} + catch + error:function_clause -> {ok, no_event, State} end; handle_call(_, State) -> diff --git a/src/sumo.erl b/src/sumo.erl index 269e1c7..55cfd40 100644 --- a/src/sumo.erl +++ b/src/sumo.erl @@ -117,11 +117,11 @@ persist(DocName, UserDoc) -> Module = sumo_config:get_prop_value(DocName, module), DocMap = Module:sumo_sleep(UserDoc), Store = sumo_config:get_store(DocName), - sumo_event:dispatch(DocName, pre_persisted, [UserDoc]), + EventId = sumo_event:dispatch(DocName, pre_persisted, [UserDoc]), case sumo_store:persist(Store, sumo_internal:new_doc(DocName, DocMap)) of {ok, NewDoc} -> Ret = sumo_internal:wakeup(NewDoc), - sumo_event:dispatch(DocName, persisted, [Ret]), + EventId = sumo_event:dispatch(DocName, EventId, persisted, [Ret]), Ret; Error -> throw(Error) @@ -218,11 +218,11 @@ find_by(DocName, Conditions, SortFields, Limit, Offset) -> -spec delete_all(schema_name()) -> non_neg_integer(). delete_all(DocName) -> Store = sumo_config:get_store(DocName), - sumo_event:dispatch(DocName, pre_delete_all), + EventId = sumo_event:dispatch(DocName, pre_delete_all), case sumo_store:delete_all(Store, DocName) of {ok, NumRows} -> - case NumRows > 0 of - true -> sumo_event:dispatch(DocName, deleted_all); + _ = case NumRows > 0 of + true -> EventId = sumo_event:dispatch(DocName, EventId, deleted_all, []); _ -> ok end, NumRows; @@ -234,9 +234,9 @@ delete_all(DocName) -> -spec delete(schema_name(), user_doc()) -> boolean(). delete(DocName, Id) -> IdField = sumo_internal:id_field_name(DocName), - sumo_event:dispatch(DocName, pre_deleted, [Id]), + EventId = sumo_event:dispatch(DocName, pre_deleted, [Id]), case delete_by(DocName, [{IdField, Id}]) of - 1 -> sumo_event:dispatch(DocName, deleted, [Id]), true; + 1 -> EventId = sumo_event:dispatch(DocName, EventId, deleted, [Id]), true; 0 -> false end. @@ -244,12 +244,15 @@ delete(DocName, Id) -> -spec delete_by(schema_name(), conditions()) -> non_neg_integer(). delete_by(DocName, Conditions) -> Store = sumo_config:get_store(DocName), - sumo_event:dispatch(DocName, pre_deleted_total, [Conditions]), + EventId = sumo_event:dispatch(DocName, pre_deleted_total, [Conditions]), case sumo_store:delete_by(Store, DocName, Conditions) of {ok, 0} -> 0; {ok, NumRows} -> - sumo_event:dispatch(DocName, deleted_total, [NumRows, Conditions]), + EventId = sumo_event:dispatch(DocName, + EventId, + deleted_total, + [NumRows, Conditions]), NumRows; Error -> throw(Error) @@ -294,10 +297,13 @@ create_schema(DocName) -> %% @end -spec create_schema(schema_name(), atom()) -> ok. create_schema(DocName, Store) -> - sumo_event:dispatch(DocName, pre_schema_created), + EventId = sumo_event:dispatch(DocName, pre_schema_created), case sumo_store:create_schema(Store, sumo_internal:get_schema(DocName)) of - ok -> sumo_event:dispatch(DocName, schema_created), ok; - Error -> throw(Error) + ok -> + EventId = sumo_event:dispatch(DocName, EventId, schema_created, []), + ok; + Error -> + throw(Error) end. %% @doc Returns a new schema. diff --git a/src/sumo_event.erl b/src/sumo_event.erl index f390e86..a82272e 100644 --- a/src/sumo_event.erl +++ b/src/sumo_event.erl @@ -22,25 +22,39 @@ -license("Apache License 2.0"). %%% API --export([dispatch/2, dispatch/3]). +-export([dispatch/2, dispatch/3, dispatch/4]). + +%%% Types +-type event_id() :: reference(). + +-export_type([event_id/0]). %%%============================================================================= %%% API %%%============================================================================= %% @doc Dispatch an event through gen_event:notify/2. --spec dispatch(sumo:schema_name(), term()) -> ok. +-spec dispatch(sumo:schema_name(), term()) -> event_id() | no_event_managers. dispatch(DocName, Event) -> dispatch(DocName, Event, []). %% @doc Dispatch an event through gen_event:notify/2. --spec dispatch(sumo:schema_name(), term(), term()) -> ok. +-spec dispatch(sumo:schema_name(), term(), term()) -> + event_id() | no_event_managers. dispatch(DocName, Event, Args) -> + EventId = make_ref(), + dispatch(DocName, EventId, Event, Args). + +%% @doc Dispatch an event through gen_event:notify/2. +-spec dispatch(sumo:schema_name(), event_id(), term(), term()) -> + event_id() | no_event_managers. +dispatch(DocName, EventId, Event, Args) -> case sumo_config:get_event_managers(DocName) of [] -> - ok; + no_event_managers; EventManagers -> lists:foreach(fun(EventManager) -> - gen_event:notify(EventManager, {DocName, Event, Args}) - end, EventManagers) + gen_event:notify(EventManager, {EventId, DocName, Event, Args}) + end, EventManagers), + EventId end.