Skip to content

Commit

Permalink
Merge pull request #315 from inaka/ferigis.313.creating_your_own_store
Browse files Browse the repository at this point in the history
[#313] Own store example
  • Loading branch information
Brujo Benavides authored Jul 10, 2017
2 parents 0392903 + 0e45d99 commit 1a15318
Show file tree
Hide file tree
Showing 9 changed files with 295 additions and 1 deletion.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,13 +169,15 @@ Changeset = [pipe](people,
```


## Example
## Examples

See: [**examples/blog**][example-blog] for a full example. To run it, while
being in the top level directory:

make all blog

See: [**examples/custom_store**](examples/custom_store) for creating your own Store. To run it, follow the instructions in this README


## Running Dialyzer

Expand Down
124 changes: 124 additions & 0 deletions examples/custom_store/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Custom Sumo Store Example

This example is about creating a store in order to add some functionality.
We are going to use `PostgreSQL` as a backend and we want to store people (only with _name_ and _city_ fields).

# Requirements
- `PostgreSQL` installed and a database created (in our code we called it _store_example_)

## The Problem

`SumoDB` provides us a great bunch of functions for CRUD operations but we need a special one. We would like
to have a `count_by_city/1` function which returns us the number of people from a given city.

With the standard `SumoDB` API we could filter by city and then return the size of the array but we want to use the native power of `PostgreSQL`.

## Creating our own store

As we are using `PostgreSQL` we are going to use our `sumo_db_pgsql` adapter for sumo, you can find it [here](https://github.com/inaka/sumo_db_pgsql).
This Adapter provides us a store called `sumo_db_pgsql` which provides us the standard `SumoDB` API. We want to extend that module and add our custom `count_by_city/1` function. In order to achive that we create our new `our_pgsql_store` which gets all the functionality from `sumo_db_pgsql` using `mixer`.

```erlang
-mixin([sumo_store_pgsql]).
```

Then we can create our function:

```erlang
count_by_city(City, DocName, State) ->
#{conn := Conn} = State,
Query = [
"SELECT Count(city) FROM ", escape(DocName),
" GROUP BY CITY ",
" HAVING ",
" city = $1"

],
parse_result(epgsql:equery(Conn, stringify(Query), [City]), State).
```

We are using `epgsql` directly from here because it is the driver we are using in the adapter.
Don't worry about `escape/1`, `stringify/1` and `parse_result/1`, you can check the real code in the [source](src/our_pgsql_store.erl).

Now we have to use this new store on our config file.

## Calling to our new function

We have added the function to our store but now we have to call that function integrated with `SumoDB`, we are going to create a new function in `store_example` module:

```erlang
count_by_city(City) ->
sumo:call(people, count_by_city, [City]).
```

This is how we call directly to a store. The first parameter is the model (defined in the config), the second one is the name of the store's function (our new created function) and the last one is a list with the parameters the that new function, in this case only one parameter.

and thats all!

## Running the example

- First clone the code to your local system.
- You must create a new database in `PostgreSQL`.
- Review the `config/sys.config` info about your database, by default the database configuration is:

```erlang
{host, "127.0.0.1"},
{port, 5432},
{database, "store_example"},
{username, "ferigis"},
{password, "123456"}
```
replace what you need
- Compile the code

```
rebar3 compile
```

- Run

```
erl -pa _build/default/lib/*/ebin -config config/sys.config -s store_example
```

## Play!

Lets create some people:
```erlang
2> sumo:persist(people, person:new("Felipe", "San Jose")).
#{city => "San Jose",id => 1,name => "Felipe"}
3> sumo:persist(people, person:new("Carlos", "Cali")).
#{city => "Cali",id => 2,name => "Carlos"}
4> sumo:persist(people, person:new("Brujo", "Buenos Aires")).
#{city => "Buenos Aires",id => 3,name => "Brujo"}
5> sumo:persist(people, person:new("Marcos", "Buenos Aires")).
#{city => "Buenos Aires",id => 4,name => "Marcos"}
6> sumo:persist(people, person:new("Euen", "Buenos Aires")).
#{city => "Buenos Aires",id => 5,name => "Euen"}
```

Lets find one of them (here we are checking we have extended from the default PostgreSQL store correctly):

```erlang
7> sumo:find_by(people, [{name, "Brujo"}]).
[#{city => <<"Buenos Aires">>,id => 3,name => <<"Brujo">>}]
```

Great!

Now lets use our new function:

```erlang
8> store_example:count_by_city("Buenos Aires").
3
9> store_example:count_by_city("San Jose").
1
10> store_example:count_by_city("Cali").
1
11> store_example:count_by_city("Namek").
0
```

It worked!

This is the idea behind creating your own store
26 changes: 26 additions & 0 deletions examples/custom_store/config/sys.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[
{sumo_db, [
{wpool_opts, [{overrun_warning, 100}]},
{log_queries, true},
{query_timeout, 30000},
{storage_backends, [
{our_backend_pgsql, sumo_backend_pgsql, [
{host, "127.0.0.1"},
{port, 5432},
{database, "store_example"},
{username, "ferigis"},
{password, "123456"}
]}
]},
{stores, [
{pgsql_store, our_pgsql_store, [
{storage_backend, our_backend_pgsql},
{workers, 10}
]}
]},
{docs, [
{people, pgsql_store, #{module => person}}
]},
{events, []}
]}
].
27 changes: 27 additions & 0 deletions examples/custom_store/rebar.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
%% == Erlang Compiler ==

{erl_opts, [
warn_unused_vars,
warn_export_all,
warn_shadow_vars,
warn_unused_import,
warn_unused_function,
warn_bif_clash,
warn_unused_record,
warn_deprecated_function,
warn_obsolete_guard,
strict_validation,
warn_export_vars,
warn_exported_vars,
warn_untyped_record,
warn_as_error,
debug_info,
{parse_transform, lager_transform}
]}.

%% == Dependencies ==

{deps, [
{mixer, "0.1.5", {pkg, inaka_mixer}},
{sumo_db_pgsql, {git, "git://github.com/inaka/sumo_db_pgsql.git", {ref, "bc768fb"}}}
]}.
39 changes: 39 additions & 0 deletions examples/custom_store/src/our_pgsql_store.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
-module(our_pgsql_store).
-author("Felipe Ripoll <[email protected]>").

-include_lib("epgsql/include/epgsql.hrl").
-include_lib("mixer/include/mixer.hrl").

-mixin([sumo_store_pgsql]). % we want to mantain all the sumo_store_pgsql functions and adding ours

-export([count_by_city/3]).

count_by_city(City, DocName, State) ->
#{conn := Conn} = State,
Query = [
"SELECT Count(city) FROM ", escape(DocName),
" GROUP BY CITY ",
" HAVING ",
" city = $1"

],
parse_result(epgsql:equery(Conn, stringify(Query), [City]), State).

%%% Internal

%% @private
escape(Name) when is_atom(Name) ->
["\"", atom_to_list(Name), "\""];
escape(String) ->
["\"", String, "\""].

%% @private
stringify(Sql) -> binary_to_list(iolist_to_binary(Sql)).

%% @private
parse_result({ok, _, []}, State) ->
{ok, {raw, 0}, State};
parse_result({ok, _, [{Count}]}, State) ->
{ok, {raw, Count}, State};
parse_result({error, Error}, State) ->
{error, Error, State}.
22 changes: 22 additions & 0 deletions examples/custom_store/src/person.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
-module(person).
-author("Felipe Ripoll <[email protected]>").

-behaviour(sumo_doc).

-export([sumo_schema/0, sumo_wakeup/1, sumo_sleep/1]).
-export([new/2]).

new(Name, City) ->
#{name => Name, city => City}.

sumo_schema() ->
Fields = [
sumo:new_field(id, integer, [id, not_null, auto_increment]),
sumo:new_field(name, string, [not_null]),
sumo:new_field(city, string, [not_null])
],
sumo:new_schema(people, Fields).

sumo_wakeup(Response) -> Response.

sumo_sleep(Person) -> Person.
13 changes: 13 additions & 0 deletions examples/custom_store/src/store_example.app.src
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{application, store_example, [
{description, "An example of creating your own store"},
{vsn, "0.1.0"},
{registered, []},
{applications, [
kernel,
stdlib,
sumo_db,
sumo_db_pgsql
]},
{mod, {store_example, []}},
{modules, []}
]}.
21 changes: 21 additions & 0 deletions examples/custom_store/src/store_example.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
-module(store_example).
-author("Felipe Ripoll <[email protected]>").

-behaviour(application).

-export([count_by_city/1]).
-export([start/0, start/2, stop/0, stop/1]).

start() -> application:ensure_all_started(store_example).

stop() -> application:stop(store_example).

start(_Type, _Args) ->
ok = sumo:create_schema(),
store_example_sup:start_link().

stop(_State) ->
ok.

count_by_city(City) ->
sumo:call(people, count_by_city, [City]).
20 changes: 20 additions & 0 deletions examples/custom_store/src/store_example_sup.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
-module(store_example_sup).
-author("Felipe Ripoll <[email protected]>").

-behaviour(supervisor).

-export([start_link/0]).
-export([init/1]).

start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, noarg).

init(noarg) ->
SupFlags = #{ strategy => one_for_one
, intensity => 1000
, period => 3600
},

Children = [],

{ok, {SupFlags, Children}}.

0 comments on commit 1a15318

Please sign in to comment.