Let's suppose we want to provide a form for editing the records of a database table called 'language'. We can create an ErlyDB module for working with this table as follows:
language.erl:
-module(language).
When we call erlyweb:compile() (or when ErlyWeb auto compiles our modules becase we have turned 'auto_compile' on, as one should do during development), ErlyDB reads the metadata for the 'language' database table and generates a large number of functions in the 'language' module covering the whole exciting CRUD gamut.
Now let's create a basic controller implementing the data access logic for the 'language edit' page. I will only show the most stripped-down logic necessary for rendering the 'edit' form, which is the focus of this tutorial.
language_controller.erl:
-module(language_controller).
-export([edit/2]).
edit(A, Id) ->
case language:find_id(Id) of
undefined ->
{data, {error, no_such_record}};
Language ->
{data, Language}
end.
When ErlyWeb receives a request such as "http://myapp.com/language/edit/1", it will invoke the 'edit/2' function in language_controller. This function will look up the database record using a SQL query such as "SELECT * FROM language WHERE id=1", and send the resulting record directly to the view function, or the tuple {error, no_such_record} if no record matches the requested id.
Now let's take a first pass at making a the view for this component (note that in this tutorial, we'll avoid any fancy automatic form-generation methods and write most of the HTML by hand).
language_view.et:
<%@ edit({error, no_such_record}) %>
ouch, the record doesn't exist
<%@ edit(Language) %>
paradigm:
....
Looks good so far? Well, it's not really supposed to, because this code has some problems.
The most urgent problem is that this code doesn't check that the record's fields are formatted as iolists. If any of the record's values is 'undefined', for example, this code will cause the process to crash (by 'crash' I don't mean the catastrophic definition of 'crash' that is used with most programming languages, but the Erlang definition, which means the lightweight process in which the crash occurs has died -- an isolated event inside the VM, nothing more).
We can address this issue in a few different ways. I'll show you one simple way, in which we create a function in the view module to do the proper conversion. Using this method, this would be the modified code for the new language.et:
<%@ edit(Language) %>
paradigm:
....
<%@ show(Language, Field) %>
<% erlydb_base:field_to_iolist(language:Field(Language)) %>
Note that this code uses the Erlang way of doing dynamic function dispatch: you use a variable in place of the module's function (and/or the module itself) you want to call. This is an extremely useful feature -- know it and it will serve you well.
The code works, but it irks me somewhat. The issue is that I don't want the view logic to know anything about ErlyDB records and how to access and format their fields. Views are supposed to be simple, and should only use the most rudimentary logic required to render their parameters. Putting ErlyDB logic in the view violates this rule.
So, how do we fix this issue? We have a few options. One option is for the controller to pass the view a list of pre-rendered fields instead of the Language record itself. This could be done in the controller by returning the following:
{data, [erlydb_base:field_to_iolist(language:Field(Language)) ||
Field <- language:db_field_names()]}
To handle this list, the view function would have to change, too:
<%@ edit([Name, Paradigm, ....]) %>