From Rails to Erlyweb - Part III
3. The Magic Behind Erlyweb
With dynamic typing, hot code swapping, built-in parsing and compilation tools, Erlang is also suitable for dynamic meta-programming. Erlyweb uses a small convenient tool smerl to generate db record CRUD code automatically.
The music example on erlyweb.org shows the magic:
Define a simple musician.erl module (just one line here):
Then, you will get a long list functions for musician module after erlyweb:compile("apps/music"). Sounds similar to Rails.
I like to watch magic show, but I can't stand that I do not know the things behind magic in programming. For Rails, the magic behind it always makes me headache, it's too difficult to follow who, where, some code are injected into a class or module. But in Erlang, it's super easy.
I add a simple function to erlyweb_app.erl (please see my previous post):
Now type erlyweb_decompile(music, musician) under the Erlang shell, I get a file: music_musician.erl under folder myproject/apps/(I put these decompiled source files under/myproject/apps/to avoid that they are auto-compiled to beams by erlyweb again ):
With this decompiled file, you get all things clearly behind the magic, such as, you have pair getter/setter functions for each field, for example:
Finally, some notices for new comer to Erlang and Erlyweb: In Erlang, the Variable can only be bound(set value) once , so, only Musician1 and Musician2 have the "new name", Musician will keep the original value For efficiency reason, if the field is varchar/text type, the getter will return a binary rather than string, which can be printed on browser directly in Erlyweb, but, if you want to use it as a string, you can apply binary_to_list(Name) on it.
With dynamic typing, hot code swapping, built-in parsing and compilation tools, Erlang is also suitable for dynamic meta-programming. Erlyweb uses a small convenient tool smerl to generate db record CRUD code automatically.
The music example on erlyweb.org shows the magic:
Define a simple musician.erl module (just one line here):
-module(musician).
Then, you will get a long list functions for musician module after erlyweb:compile("apps/music"). Sounds similar to Rails.
I like to watch magic show, but I can't stand that I do not know the things behind magic in programming. For Rails, the magic behind it always makes me headache, it's too difficult to follow who, where, some code are injected into a class or module. But in Erlang, it's super easy.
I add a simple function to erlyweb_app.erl (please see my previous post):
-export([decompile/2]).
decompile(AppName, Beam) when is_atom(AppName) ->
decompile(atom_to_list(AppName), Beam);
decompile(AppName, Beam) when is_list(AppName) ->
BinFilename = "./apps/" ++ AppName ++ "/ebin/" ++ atom_to_list(Beam),
io:format("Beam file: ~s~n", [BinFilename]),
{ok, {_, [{abstract_code, {_, AC}}]}} = beam_lib:chunks(BinFilename, [abstract_code]),
SrcFilename = "./apps/" ++ AppName ++ "_" ++ atom_to_list(Beam),
{ok, S} = file:open(SrcFilename ++ ".erl", write),
io:fwrite(S, "~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).
Now type erlyweb_decompile(music, musician) under the Erlang shell, I get a file: music_musician.erl under folder myproject/apps/(I put these decompiled source files under/myproject/apps/to avoid that they are auto-compiled to beams by erlyweb again ):
-file("musician", 1).
-module(musician).
-export([relations/0, fields/0, table/0, type_field/0,
db_table/0, db_field/1, is_new/1, is_new/2,
get_module/1, to_iolist/1, to_iolist/2,
field_to_iolist/2, new/0, new_with/1, new_with/2,
new_from_strings/1, set_fields/2, set_fields/3,
set_fields_from_strs/2, field_from_string/2, save/1,
insert/1, update/1, update/2, delete/1, delete_id/1,
delete_where/1, delete_all/0, transaction/1,
before_save/1, after_save/1, before_delete/1,
after_delete/1, after_fetch/1, find/2, find_first/2,
find_max/3, find_range/4, find_id/1, aggregate/4,
count/0, get/2, set_related_one_to_many/2,
find_related_one_to_many/2, find_related_many_to_one/4,
aggregate_related_many_to_one/6,
add_related_many_to_many/3,
remove_related_many_to_many/3,
remove_related_many_to_many_all/5,
find_related_many_to_many/5,
aggregate_related_many_to_many/7,
find_related_many_first/4, find_related_many_max/5,
find_related_many_range/6, aggregate_related_many/6,
do_save/1, do_delete/1, field_names_for_query/0,
field_names_for_query/1, field_to_iolist/1, set/3,
db_pk_fields/0, get_pk_fk_fields/0, get_pk_fk_fields2/0,
db_fields/0, db_field_names/0, db_field_names_str/0,
db_field_names_bin/0, db_num_fields/0, id/1, id/2,
name/1, name/2, birth_date/1, birth_date/2,
instrument/1, instrument/2, bio/1, bio/2, new/4,
driver/0, count/3, count/1, count/2, count_with/2,
avg/3, avg/1, avg/2, avg_with/2, min/3, min/1, min/2,
min_with/2, max/3, max/1, max/2, max_with/2, sum/3,
sum/1, sum/2, sum_with/2, stddev/3, stddev/1, stddev/2,
stddev_with/2, find/0, find/1, find_with/1,
find_first/0, find_first/1, find_first_with/1,
find_max/1, find_max/2, find_max_with/2, find_range/2,
find_range/3, find_range_with/3]).
relations() -> erlydb_base:relations().
fields() -> erlydb_base:fields().
table() -> erlydb_base:table().
type_field() -> erlydb_base:type_field().
db_table() -> erlydb_base:db_table(musician).
db_field(FieldName) ->
erlydb_base:db_field(musician, FieldName).
is_new(Rec) -> erlydb_base:is_new(Rec).
is_new(Rec, Val) -> erlydb_base:is_new(Rec, Val).
get_module(Rec) -> erlydb_base:get_module(Rec).
to_iolist(Recs) ->
erlydb_base:to_iolist(musician, Recs).
to_iolist(Recs, ToIolistFun) ->
erlydb_base:to_iolist(musician, Recs, ToIolistFun).
field_to_iolist(Val, _Field) ->
erlydb_base:field_to_iolist(Val, _Field).
new() -> erlydb_base:new(musician).
new_with(Fields) ->
erlydb_base:new_with(musician, Fields).
new_with(Fields, ToFieldFun) ->
erlydb_base:new_with(musician, Fields, ToFieldFun).
new_from_strings(Fields) ->
erlydb_base:new_from_strings(musician, Fields).
set_fields(Record, Fields) ->
erlydb_base:set_fields(musician, Record, Fields).
set_fields(Record, Fields, ToFieldFun) ->
erlydb_base:set_fields(musician, Record, Fields,
ToFieldFun).
set_fields_from_strs(Record, Fields) ->
erlydb_base:set_fields_from_strs(musician, Record,
Fields).
field_from_string(ErlyDbField, undefined) ->
erlydb_base:field_from_string(ErlyDbField, undefined).
save(Rec) -> erlydb_base:save(Rec).
insert(Recs) -> erlydb_base:insert(Recs).
update(Props) -> erlydb_base:update(musician, Props).
update(Props, Where) ->
erlydb_base:update(musician, Props, Where).
delete(Rec) -> erlydb_base:delete(Rec).
delete_id(Id) -> erlydb_base:delete_id(musician, Id).
delete_where(Where) ->
erlydb_base:delete_where(musician, Where).
delete_all() -> erlydb_base:delete_all(musician).
transaction(Fun) ->
erlydb_base:transaction(musician, Fun).
before_save(Rec) -> erlydb_base:before_save(Rec).
after_save(Rec) -> erlydb_base:after_save(Rec).
before_delete(Rec) -> erlydb_base:before_delete(Rec).
after_delete({_Rec, Num}) ->
erlydb_base:after_delete({_Rec, Num}).
after_fetch(Rec) -> erlydb_base:after_fetch(Rec).
find(Where, Extras) ->
erlydb_base:find(musician, Where, Extras).
find_first(Where, Extras) ->
erlydb_base:find_first(musician, Where, Extras).
find_max(Max, Where, Extras) ->
erlydb_base:find_max(musician, Max, Where, Extras).
find_range(First, Max, Where, Extras) ->
erlydb_base:find_range(musician, First, Max, Where,
Extras).
find_id(Id) -> erlydb_base:find_id(musician, Id).
aggregate(AggFunc, Field, Where, Extras) ->
erlydb_base:aggregate(musician, AggFunc, Field, Where,
Extras).
count() -> erlydb_base:count(musician).
get(Idx, Rec) -> erlydb_base:get(Idx, Rec).
set_related_one_to_many(Rec, Other) ->
erlydb_base:set_related_one_to_many(Rec, Other).
find_related_one_to_many(OtherModule, Rec) ->
erlydb_base:find_related_one_to_many(OtherModule, Rec).
find_related_many_to_one(OtherModule, Rec, Where,
Extras) ->
erlydb_base:find_related_many_to_one(OtherModule, Rec,
Where, Extras).
aggregate_related_many_to_one(OtherModule, AggFunc, Rec,
Field, Where, Extras) ->
erlydb_base:aggregate_related_many_to_one(OtherModule,
AggFunc, Rec, Field, Where,
Extras).
add_related_many_to_many(JoinTable, Rec, OtherRec) ->
erlydb_base:add_related_many_to_many(JoinTable, Rec,
OtherRec).
remove_related_many_to_many(JoinTable, Rec, OtherRec) ->
erlydb_base:remove_related_many_to_many(JoinTable, Rec,
OtherRec).
remove_related_many_to_many_all(JoinTable, OtherTable,
Rec, Where, Extras) ->
erlydb_base:remove_related_many_to_many_all(JoinTable,
OtherTable, Rec, Where, Extras).
find_related_many_to_many(OtherModule, JoinTable, Rec,
Where, Extras) ->
erlydb_base:find_related_many_to_many(OtherModule,
JoinTable, Rec, Where, Extras).
aggregate_related_many_to_many(OtherModule, JoinTable,
AggFunc, Rec, Field, Where, Extras) ->
erlydb_base:aggregate_related_many_to_many(OtherModule,
JoinTable, AggFunc, Rec, Field,
Where, Extras).
find_related_many_first(Func, Rec, Where, Extras) ->
erlydb_base:find_related_many_first(Func, Rec, Where,
Extras).
find_related_many_max(Func, Rec, Num, Where, Extras) ->
erlydb_base:find_related_many_max(Func, Rec, Num, Where,
Extras).
find_related_many_range(Func, Rec, First, Last, Where,
Extras) ->
erlydb_base:find_related_many_range(Func, Rec, First,
Last, Where, Extras).
aggregate_related_many(Func, AggFunc, Rec, Field, Where,
Extras) ->
erlydb_base:aggregate_related_many(Func, AggFunc, Rec,
Field, Where, Extras).
do_save(Rec) -> erlydb_base:do_save(Rec).
do_delete(Rec) -> erlydb_base:do_delete(Rec).
field_names_for_query() ->
erlydb_base:field_names_for_query(musician).
field_names_for_query(UseStar) ->
erlydb_base:field_names_for_query(musician, UseStar).
field_to_iolist(Val) ->
erlydb_base:field_to_iolist(Val).
set(Idx, Rec, Val) -> setelement(Idx, Rec, Val).
db_pk_fields() ->
erlydb_base:db_pk_fields([{erlydb_field, id, "id",
<<105, 100>>, int, 11, integer, text_field,
false, primary, undefined, identity}]).
get_pk_fk_fields() ->
erlydb_base:get_pk_fk_fields([{id, musician_id}]).
get_pk_fk_fields2() ->
erlydb_base:get_pk_fk_fields2([{id, musician_id1,
musician_id2}]).
db_fields() ->
erlydb_base:db_fields([{erlydb_field, id, "id",
<<105, 100>>, int, 11, integer, text_field, false,
primary, undefined, identity},
{erlydb_field, name, "name", <<110, 97, 109, 101>>,
varchar, 20, binary, text_field, true, undefined,
undefined, undefined},
{erlydb_field, birth_date, "birth_date",
<<98, 105, 114, 116, 104, 95, 100, 97, 116, 101>>,
date, undefined, date, text_field, true, undefined,
undefined, undefined},
{erlydb_field, instrument, "instrument",
<<105, 110, 115, 116, 114, 117, 109, 101, 110,
116>>,
enum,
[<<103, 117, 105, 116, 97, 114>>,
<<112, 105, 97, 110, 111>>,
<<100, 114, 117, 109, 115>>,
<<118, 111, 99, 97, 108, 115>>],
binary, select, true, undefined, undefined,
undefined},
{erlydb_field, bio, "bio", <<98, 105, 111>>, text,
undefined, binary, text_area, true, undefined,
undefined, undefined}]).
db_field_names() ->
erlydb_base:db_field_names([id, name, birth_date,
instrument, bio]).
db_field_names_str() ->
erlydb_base:db_field_names_str(["id", "name",
"birth_date", "instrument", "bio"]).
db_field_names_bin() ->
erlydb_base:db_field_names_bin([<<105, 100>>,
<<110, 97, 109, 101>>,
<<98, 105, 114, 116, 104, 95, 100, 97, 116,
101>>,
<<105, 110, 115, 116, 114, 117, 109, 101,
110, 116>>,
<<98, 105, 111>>]).
db_num_fields() -> erlydb_base:db_num_fields(5).
id(Rec) -> erlydb_base:get(3, Rec).
id(Rec, Val) -> setelement(3, Rec, Val).
name(Rec) -> erlydb_base:get(4, Rec).
name(Rec, Val) -> setelement(4, Rec, Val).
birth_date(Rec) -> erlydb_base:get(5, Rec).
birth_date(Rec, Val) -> setelement(5, Rec, Val).
instrument(Rec) -> erlydb_base:get(6, Rec).
instrument(Rec, Val) -> setelement(6, Rec, Val).
bio(Rec) -> erlydb_base:get(7, Rec).
bio(Rec, Val) -> setelement(7, Rec, Val).
new(name, birth_date, instrument, bio) ->
{musician, true, undefined, name, birth_date,
instrument, bio}.
driver() ->
erlydb_base:driver({erlydb_mysql,
[{last_compile_time, {{1980, 1, 1}, {0, 0, 0}}},
{outdir, "apps/music/ebin"}, debug_info, report_errors,
report_warnings, {erlydb_driver, mysql}]}).
count(Field, Where, Extras) ->
erlydb_base:aggregate(musician, count, Field, Where,
Extras).
count(Field) ->
erlydb_base:aggregate(musician, count, Field, undefined,
undefined).
count(Field, Where) ->
erlydb_base:aggregate(musician, count, Field, Where,
undefined).
count_with(Field, Extras) ->
erlydb_base:aggregate(musician, count, Field, undefined,
Extras).
avg(Field, Where, Extras) ->
erlydb_base:aggregate(musician, avg, Field, Where,
Extras).
avg(Field) ->
erlydb_base:aggregate(musician, avg, Field, undefined,
undefined).
avg(Field, Where) ->
erlydb_base:aggregate(musician, avg, Field, Where,
undefined).
avg_with(Field, Extras) ->
erlydb_base:aggregate(musician, avg, Field, undefined,
Extras).
min(Field, Where, Extras) ->
erlydb_base:aggregate(musician, min, Field, Where,
Extras).
min(Field) ->
erlydb_base:aggregate(musician, min, Field, undefined,
undefined).
min(Field, Where) ->
erlydb_base:aggregate(musician, min, Field, Where,
undefined).
min_with(Field, Extras) ->
erlydb_base:aggregate(musician, min, Field, undefined,
Extras).
max(Field, Where, Extras) ->
erlydb_base:aggregate(musician, max, Field, Where,
Extras).
max(Field) ->
erlydb_base:aggregate(musician, max, Field, undefined,
undefined).
max(Field, Where) ->
erlydb_base:aggregate(musician, max, Field, Where,
undefined).
max_with(Field, Extras) ->
erlydb_base:aggregate(musician, max, Field, undefined,
Extras).
sum(Field, Where, Extras) ->
erlydb_base:aggregate(musician, sum, Field, Where,
Extras).
sum(Field) ->
erlydb_base:aggregate(musician, sum, Field, undefined,
undefined).
sum(Field, Where) ->
erlydb_base:aggregate(musician, sum, Field, Where,
undefined).
sum_with(Field, Extras) ->
erlydb_base:aggregate(musician, sum, Field, undefined,
Extras).
stddev(Field, Where, Extras) ->
erlydb_base:aggregate(musician, stddev, Field, Where,
Extras).
stddev(Field) ->
erlydb_base:aggregate(musician, stddev, Field,
undefined, undefined).
stddev(Field, Where) ->
erlydb_base:aggregate(musician, stddev, Field, Where,
undefined).
stddev_with(Field, Extras) ->
erlydb_base:aggregate(musician, stddev, Field,
undefined, Extras).
find() ->
erlydb_base:find(musician, undefined, undefined).
find(Where) ->
erlydb_base:find(musician, Where, undefined).
find_with(Extras) ->
erlydb_base:find(musician, undefined, Extras).
find_first() ->
erlydb_base:find_first(musician, undefined, undefined).
find_first(Where) ->
erlydb_base:find_first(musician, Where, undefined).
find_first_with(Extras) ->
erlydb_base:find_first(musician, undefined, Extras).
find_max(Max) ->
erlydb_base:find_max(musician, Max, undefined,
undefined).
find_max(Max, Where) ->
erlydb_base:find_max(musician, Max, Where, undefined).
find_max_with(Max, Extras) ->
erlydb_base:find_max(musician, Max, undefined, Extras).
find_range(First, Max) ->
erlydb_base:find_range(musician, First, Max, undefined,
undefined).
find_range(First, Max, Where) ->
erlydb_base:find_range(musician, First, Max, Where,
undefined).
find_range_with(First, Max, Extras) ->
erlydb_base:find_range(musician, First, Max, undefined,
Extras).
With this decompiled file, you get all things clearly behind the magic, such as, you have pair getter/setter functions for each field, for example:
Musician = musician:find({name, 'like' "Caoyuan Mus"}),
%% get the 'name' field of record Musician
Name = musician:name(Musician),
%% set the 'name' field to "new name" and bind to a new record Musician1.
Musician1 = musician:name(Musician, "new name"),
%% Or,
Musician2 = musician:set_fields(Musician, {name, "new name"}, {birth_day, "1940/10/9"}),
%% then save one of them
musician:save(Musician2).
Finally, some notices for new comer to Erlang and Erlyweb: