-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #315 from inaka/ferigis.313.creating_your_own_store
[#313] Own store example
- Loading branch information
Showing
9 changed files
with
295 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, []} | ||
]} | ||
]. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"}}} | ||
]}. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, []} | ||
]}. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}}. |