Сохранение данных JSON в БД в Zotonic

Я пытаюсь написать небольшое приложение, которое извлекает файл JSON (он содержит список элементов, каждый из которых имеет некоторые свойства), сохраняет его содержимое в БД, а затем отображает некоторые из них позже. У меня Zotonic запущен и работает, и генерирование некоторого HTML не проблема.
ATM Я застрял, пытаясь выяснить, как определить пользовательский ресурс и как получить данные из JSON в БД. Когда данные есть, я должен быть в порядке, эта часть, кажется, покрыта документацией в порядке.
Я написал несколько автономных скриптов erlang, которые извлекают данные, и заметил, что в Zotonic есть библиотека для декодирования JSON, так что часть должна быть в порядке. Любые советы о том, где поставить какой код или где искать дальше?

1 ответ

Решение

Модуль z_db позволяет создавать собственные таблицы, используя:

z_db:create_table(Table, Cols, Context).

Переменная Table - это имя вашей таблицы, которое может быть либо атомом, либо списком, содержащим один атом.
Cols - это список определений столбцов, которые определяются записями. В настоящее время определение записи (вы можете найти это в include/zotonic.hrl):

-record(column_def, {name, type, length, is_nullable=true, default, primary_key}).

Смотрите Erlang документы на записи для получения дополнительной информации о записях

Пример кода, который я помещаю в users/sites/[sitename]/models/m_[sitename].erl:

init(Context) ->
        case z_db:table_exists(?table,Context) of
        false ->
                z_db:create_table(tablename,
                [
                #column_def{name=id, type="serial"},
                #column_def{name=gid, type="integer", is_nullable=false},
                #column_def{name=magnitude, type="real"},
                #column_def{name=depth, type="real"},
                #column_def{name=location, type="character varying"},
                #column_def{name=time, type="integer"},
                #column_def{name=date, type="integer"}
                ], Context);
        true -> ok
        end,
        ok.

Обратите внимание на то, какие параметры записи вы указали. Большинство ошибок, которые я получил, были, например, от указания длины в целочисленных полях.

models/m_sitename:init/1 не вызывается при запуске сайта. sitename:init/1 действительно вызывается, поэтому я вызываю там функцию init, чтобы убедиться, что таблица существует. Пример:

init(Context) ->
        m_sitename:init(Context).

Он вызывается Zotonic с переменной Context сайта автоматически. Вы можете также получить эту переменную вручную с помощью z:c(sitename)., Так что если вы позвоните m_sitename:init(Context). из другого места вы бы сделали:

m_sitename:init(z:c(sitename)).  

Далее, вставка в БД может быть сделана с помощью:

z_db:insert(Table, PropList, Context).

Где Table снова является атомом или списком, содержащим один атом, представляющий имя таблицы. Контекст такой же, как и выше.
PropList - это список свойств, который представляет собой список, содержащий кортежи, состоящие из двух элементов, где первый является атомом, а второй - его связанным значением / свойством. Пример:

PropList = [
            {row, Value},
            {anotherrow, AnotherValue}
           ].

Table = tablename.
Context = z:c(sitename).
z_db:insert(Table, PropList, Context).

См. Erlang docs в Списках свойств для получения дополнительной информации о списках свойств.

=== Зависимости были обновлены, так что если вы собираете из исходного кода, шаг ниже, больше не нужен ===

Часть JSON немного сложнее. В состав Zotonic входят mochijson2 и, как вторичная зависимость, также указывается, что есть. Последняя версия jiffy содержит jiffy:decode/2, которая позволяет указывать карты в качестве возвращаемого типа. Гораздо удобнее для чтения, чем стандарт {struct, {struct, <<"">>}}монстр. Для обновления до последней версии отредактируйте строку вdeps/twerl/rebar.configэто говорит

{jiffy, ".*", {git, "https://github.com/davisp/jiffy.git", {tag, "0.8.3"}}},

в

{jiffy, ".*", {git, "https://github.com/davisp/jiffy.git", {tag, "0.14.3"}}},

Теперь бегиz:m().в Zotonic оболочке. (вы должны делать это после каждого изменения в вашем коде).
Теперь проверьте в оболочке Zotonic, есть ли jiffy:decode/2, доступный, набравjiffy: <tab>, он покажет список доступных функций и их арность.

Чтобы получить файл JSON из Интернета, выполните:

{ok, {{_, 200, _}, _, Body}} = httpc:request(get, {"url-to-JSON-here", []}, [], [])

Который даст переменную Body с содержимым. См. Документацию Erlang на http-клиенте для получения дополнительной информации об этом вызове.

Затем преобразуйте содержимое тела в термины Эрланга с помощью:

JsonData = jiffy:decode(Body, [return_maps]).

Что вам нужно делать дальше, во многом зависит от структуры вашего ресурса JSON. Имейте в виду, что теперь все в двоичных строках в кодировке UTF-8! Если вы печатаете JsonData на экран (просто введитеJsonData.в вашей оболочке Zotonic/Erlang) вы увидите много#map{<<"key"", <<"Value">>}этот.
Мои данные были вложены, поэтому я должен был извлечь необходимые данные, как это:

[{_,ItemList}|_] = ListData.

Это дало мне список карт, и для того, чтобы рассматривать их как отдельные элементы, я использовал следующую функцию:

 get_maps([]) ->
     done; 
 get_maps([First|Rest]) ->
     Map = maps:get(<<"properties">>, First),
     case is_map(Map) of
             true ->
                     map_to_proplist(Map),
                     get_maps(Rest);
             false -> done
             end,
     done; 
get_maps(_) ->
     done.

Как вы помните,z_db:insert/3Функция нуждается в списке свойств для заполнения строк, так что вызовmap_to_proplist/1для. То, как эта функция выглядит, полностью зависит от того, как выглядят ваши данные, но в качестве примера вот что сработало для меня:

map_to_proplist(Map) ->
        case is_map(Map) of
                true ->
                        {Value1,_}         = string:to_integer(binary_to_list(maps:get(<<"key1">>, Map))),
                        {Value2,_}   = string:to_float(binary_to_list(maps:get(<<"key2">>, Map))),
                        {Value3,_}       = string:to_float(binary_to_list(maps:get(<<"key3">>, Map))),
                        Value4        = binary_to_list(maps:get(<<"key4">>, Map)),
                        {Value5,_}        = string:to_integer(binary_to_list(maps:get(<<"key5">>, Map))),
                        {Value6,_}        = string:to_integer(binary_to_list(maps:get(<<"key6">>, Map))),
                        PropList = [{rowname1, Value1}, {rowname2, Value2}, {rowname3, Value3}, {rowname4, Value4}, {rowname5, Value5}, {rowname6, Value6}],
                        m_sitename:insert_items(PropList,z:c(sitename)),
                        ok;
                false ->
                        ok
                end.

Смотрите документацию по строке:to_list/1, чтобы узнать, почему кортежи нужны при приведении. Призыв к m_sitename:insert_items(PropList,z:c(sitename)) вызывает z_db:insert/3 в models/m_sitename.erl но завернутый в улов:

insert_items(PropList,Context) ->
        (catch z_db:insert(?table, PropList, Context)).

Хорошо, довольно длинный пост, но это должно помочь вам начать работу, если вы ищете этот ответ.

Выше было сделано с Zotonic 0.13.2 на Erlang/OTP 18.
Репост (кроме части JSON) моего поста в группе разработчиков Zotonic.

Другие вопросы по тегам