[erlang-questions] What about this?

Ivan Carmenates Garcia <>
Sat Oct 10 20:38:57 CEST 2015


Hi fellows,

 

Here I show some examples of how to work with the little framework for
cowboy that I am making, I will be glad of hear some opinions from the
community in order to help improve it. I must say that I will release it to
the community as soon as it is ready for shown (the first version).

 

(This will be about database stuffs)

 

First you configure the backends in a config file app.config. (you can
configure as many backends as you want, even in runtime you can update and
install database backends). I'll show you how.

 

%% Database backends configuration.
{database_manager, [
    {main_backend, [
        {backend, postgres_backend},
        {server, "localhost"},
        {username, "postgres"},
        {password, "server"},
        {database, "my_main_db"},
        %% max amount of database connections in the connection pool.
        {max_reusable_connections, 10}, % 10 connections.
        %% max time it will wait for an available connection.
        {wait_for_reusable_connection_timeout, 5000} % 5 seconds.
    ]},
    {test_backend, [
        {backend, postgres_backend},
        {server, "localhost"},
        {username, "postgres"},
        {password, "server"},
        {database, "test_db"},
        %% max amount of database connections in the connection pool.
        {max_reusable_connections, 50}, % 10 connections.
        %% max time it will wait for an available connection.
        {wait_for_reusable_connection_timeout, 3000} % 3 seconds.
    ]}
]}

 

To install a backend at runtime use database_manager:install_backend/2,
passing the backend name and config, also has update_backend/2.

 

Later you will use this in a simple way:

 

database_manager:connection_session(fun(DBSession)-> . end, [{backend,
test_backend}]).

 

'connection_session/1' give us an available connection from the connection
pool. Each configured backend has its own connection pool, so we can use
they as we like having the possibility of use multiple backend in runtime
and at the same time.

 

Let's see some examples of the current database functionalities the
framework has:

 

database_manager:connection_session(fun(DBSession) ->

    database_manager:insert(DBSession, {users, #{username => "John", age =>
31}})

end).

 

If no backend option is defined in 'connection_session', 'main_backend' will
be used.

 

database_manager:connection_session_transaction(fun(DBSession) ->

    {ok, #{id := UserId}} = database_manager:insert(DBSession, {users,
#{username => "John", age => 31}}, [{return_fields, [id]}]),

    {ok, 1} = database_manager:insert(DBSession, {roles, #{user_id =>
UserId, role_level => 1}})

end).

 

We also have 'database_manager:transaction/1' for use with
'connection_session' as we like.

 

It has more functions like update, delete, find. i.e.:

 

database_manager:connection_session(fun(DBSession) ->

    {ok, DataMap} = database_manager:find(DBSession, users, [{username,
'==', "John"}])

end).

 

We can use 'return_fields' option also to select just the fields we want.

 

That was the low level API, it also have a high level API for model
validation and of course you can mix between them as you wish.

 

Example:

 

To use the high level model API you need to create a module file that
implement a behavior of 'model'. The nice thing here is that you can use
validation functions by context, using tags.

 

-module(users_model).
-behavior(model).

-export([
    validation_tests/1,
    after_validate/1]).

validation_tests(ModelDataMap) ->
    {username := Username, online := Online,
    password := Password} = ModelDataMap,
    [

        %% tag for common validation tests.
        {common, [
            %% username is required
            {fun() ->
                size(Username) =/= 0
             end, "username cannot be empty"}]},

        {new, [
            %% password cannot be empty
            {fun() ->
                size(Password) =/= 0
             end, "password cannot be empty"}]},
        {update, [
            %% online must be true or false
            {fun() ->
                (Online == true) or (Online == false)
             end, "online is not true or false"}]}
       ].

 

%% optional

after_validate(ModelDataMap) ->
    ok.

 

%% optional

id_field_name() ->

    id.

 

Then you can write

 

{ok, ModelInfo} = model_manager:new_model(users, #{

                      username => "John",

                      password => "server213*-+",

                      password_old => password},

                          [common, new]),

 

{ok, UserId} = model_manager:store(ModelInfo, [return_id]),

 

{ok, ModelInfo2} = model_manager:update_model(roles, UserId, #{role_level =>
1}, [common, update]),

ok = model_manager:store_model(ModelInfo2).

 

Note three things here:

- first you can use tags to separate validation tests by context, for
example, here I use 'common' to use that validation function for both new
and update, and one for each of them. You can choose the name you want. But
'always' is a special name that is used when you don't specify any tag in
'new_model' or 'update_model' functions, them 'always' tag will be used.

- second you can use 'return_id' option to return just the id of the
inserted record but you can also use 'return_fields' to return any other
field. In other to make the framework knows which is the id for 'return_id'
option we must use 'id' for the name in the database or specify which is the
name of the id in the model module using 'id_field_name/0' function.

- third we can do stuffs like this #{username => "John", password =>
"server213*-+", password_old => password}, meaning that 'passworld_old' will
take the value of 'password'.

 

There are many other options and functions in the API, for example you can
mix between low level API and High level API using

model_manager:store_session(fun(DBSession) -> . end). You of course can use
all functionalities form the low level API in the High one, like backend
options and many other.

 

Using 'model_manager:store_session/1/2' when using multiple 'store_model' in
the same function is recommended because with it you hold the same
connection for all 'store_model' and low level API operations.

 

So this will be ok:

 

model_manager:store_session_transaction(fun(_) ->

    {ok, ModelInfo} = model_manager:new_model(users, #{

                          username => "John",

                          password => "server213*-+",

                          password_old => password},

                              [common, new]),

 

    {ok, UserId} = model_manager:store(ModelInfo, [return_id]),

 

    {ok, ModelInfo2} = model_manager:update_model(roles, UserId,
#{role_level => 1}, [common, update]),

    ok = model_manager:store_model(ModelInfo2)

end, [{backend, test_backend}]).

 

If any of the functions fail or the pattern matching fails the hold
transaction will rollback.

 

 

 

Cheers,

Ivan (son of Gilberio).

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20151010/7acb7d7f/attachment.htm>


More information about the erlang-questions mailing list