62
35

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Elixir or Phoenix Advent Calendar 2017

Day 18

ExcelからElixir入門③:WebにDBデータ表示

Last updated at Posted at 2018-05-07

【本コラムは、5分で読めて、15分くらいでお試しいただけます】
piacereです、ご覧いただいてありがとございます :bow:

前回は、データ列の抽出を行った後、Webフレームワーク「Phoenix」をインストールし、Web上でのデータ表示切り替えを行いました

今回は、Web上にDBデータを表示します

■「ExcelからElixir入門」シリーズの目次
①データ並替え/絞り込み
|> ②データ列抽出、Web表示
|> ③WebにDBデータ表示
|> ④Webに外部APIデータ表示
|> ⑤Webにグラフ表示
|> ⑥SPAからPhoenix製APIを呼び出す(表示編)【LiveView版】
|> ⑦SPAからPhoenix製APIを呼び出す(更新編)【LiveView版】
|> ⑧Gigalixirに本番リリース
|> ⑨ElixirサーバサイドのみでReactと同じSPA/リアルタイムUIが作れる「LiveView」
|> ⑩ElixirサーバサイドSPAをスマホで見るためにGigalixirリリース
|> ⑪Gigalixir上のLiveViewアプリに独自ドメイン名を付与して正式なアプリ公開
|> ⑫Elixir/PhoenixのCRUD Webアプリをリリース

「Mnesia」…Elixirにバンドルされている分散DB

通常、DBと言えば、MySQLやPostgreSQL、Oracle等のRDBMSを思い浮かべますが、最近のWeb+DB開発では、下記クラウドDBや、Heroku/GigalixirのようなDB付きPaaSを利用することが増えており、自前でRDBMSをインストールする機会は減っています

  • GCP:Cloud SQL、BigQuery
  • AWS:RDS、Aurora、Redshift
  • Azure:SQL Database、Cosmos DB

そのため、Elixirに標準搭載の「Mnesia(エムネジア)」と呼ばれる分散DBを利用します

SQLiteも使えるのですが、Windows実機環境だとC++ビルド環境のセッティングが必要なので、ここはMnesiaを使います

なお、MySQLやPostgreSQLで構築したい場合は、こちらに手順があります ので、ご覧ください(ただし、ローカルDBインストールに慣れていない方はハマる可能性あります)

Mnesiaを使う前準備

前回作ったPhoenix PJmix.exsextra_applications に、:mnesia を追加します

mix.exs
defmodule Basic.MixProject do
  use Mix.Project

  def application do
    [
      mod: {Basic.Application, []},
      extra_applications: [:logger, :runtime_tools, :mnesia]

Mnesia用スキーマを作成

MnesiaのDBを配置するスキーマ(実態はフォルダ)を作成します

iex> :mnesia.create_schema([node()])
:ok
iex> :mnesia.start
:ok

カレンドフォルダ配下を見ると、「Mnesia.nonode@nohost」というフォルダが出来ており、Mnesiaのデータはここに保存されます

iex> ls
.formatter.exs              .git                        .gitignore
Mnesia.nonode@nohost        README.md                   _build
assets                      config                      deps
elixir_buildpack.config     lib                         mix.exs
mix.lock                    priv                        test

複数列データと同じ構造のテーブル作成

前回の複数列データと同じデータ構造を、テーブルにて作成するため、以下をiex内で入力します

iex> :mnesia.create_table(:members, [attributes: [:id, :name, :age, :team, :position], disc_copies: [node()]])

もし、作成に失敗したときは、以下で一度テーブルを削除して、作り直してください

iex> :mnesia.delete_table(:members)

データ投入

続けて、データ投入するため、以下のコードを入力してください

lib/basic.ex
defmodule Basic do
 …
  def inserts() do
    :mnesia.start
    :mnesia.dirty_write({:members, 1, "enぺだーし", 54, "有限会社デライトシステムズ", "代表取締役、性能探求者"})
    :mnesia.dirty_write({:members, 2, "ざっきー", 50, "公立大学法人 北九州市立大学", "准教授、カーネルハッカー"})
    :mnesia.dirty_write({:members, 3, "たくと", 40, "カラビナテクノロジー株式会社", "リードエンジニア"})
    :mnesia.dirty_write({:members, 4, "piacere", 48, "株式会社DigiDockConsulting", "常務取締役CTO、技術顧問、重力プログラマ"})
  end
end

上記コードをコンパイルして、実行します

iex> recompile
iex> Basic.inserts

投入したデータを確認します(どちらもqキーで終了します)

iex> :ets.i(:members)
<1   > {members,2,
         <<227,129,150,227,129,163,227,129,141,227,131,  ...
<2   > {members,4,<<"piacere">>,44,
         <<227,130,171,227,131,169,227  ...
<3   > {members,3,
         <<227,129,164,227,129,161,227,130,141,227,131,  ...
<4   > {members,1,
         <<101,110,227,129,186,227,129,160,227,131,188,  ...
EOT  (q)uit (p)Digits (k)ill /Regexp -->

データが投入した件数だけ入っていることは確認できましたが、日本語がバイナリリストで確認できないので、qキーで抜け、以下で確認します

iex> :mnesia.transaction(fn -> :mnesia.read(:members, 1) end)
{:atomic,
 [
   {:members, 1, "enぺだーし", 54,
    "有限会社デライトシステムズ",
    "代表取締役、性能探求者"}
 ]}
iex> :mnesia.transaction(fn -> :mnesia.read(:members, 4) end)
{:atomic,
 [
   {:members, 4, "piacere", 48, "株式会社DigiDockConsulting",
    "常務取締役CTO、技術顧問、重力プログラマ"}
 ]}

日本語も無事、投入されていることが確認できました

なお、以下でも確認できます

iex> :mnesia.dirty_read(:members, 1)

DBアクセスモジュールを作る

PJフォルダ内のlibフォルダ配下にutilフォルダを掘り、DBアクセスする以下2モジュールを作ります

まず DbMnesia モジュールで、select は、:mnesia.wait_for_tables でMnesiaテーブルが利用可になるまで待ち、Mnesiaでデータ取得するための各設定を行った後、取得して、Ectoの返却と同じ形に整形して返します

lib/util/db_mnesia.ex
defmodule DbMnesia do
  def select(table_name) do
    :mnesia.start
    table_atom = String.to_atom(table_name)
    :mnesia.wait_for_tables([table_atom], 1000)
    columns = :mnesia.table_info(table_atom, :attributes) |> Enum.map(& Atom.to_string(&1))
    specs = [table_atom] ++ Enum.map(1..Enum.count(columns), & :"$#{&1}") |> List.to_tuple
    rows = :mnesia.transaction(fn ->
      :mnesia.select(table_atom, [{specs, [], [:"$$"]}]) end) |> elem(1)
    %{
      columns: columns,
      command: :select,
      connection_id: 0,
      num_rows: Enum.count(rows),
      rows: rows
   }
  end
end

次に Db モジュールで、query は、SQL構文を受け付け、今回は「select」のみカバーします

select は、select文をパースし、DbMnesia.select を呼びます(今回はテーブル名抽出のみ)

パースに使っている Regex.named_captures は、かなり強力で便利な関数なので、どこかの別回で解説したいと思いますが、今は正規表現で文字列中から狙った部分だけ抽出できる関数くらいで捉えておいてください

columns_rowsrowscolumns は、DbMnesia.select の戻り値を分解するために使います

lib/util/db.ex
defmodule Db do
  def query(sql) when sql != "" do
    cond do
      String.match?(sql, ~r/select.*/) -> select(sql)
      String.match?(sql, ~r/insert.*/) -> {:error, "insert unsupported"}
      String.match?(sql, ~r/update.*/) -> {:error, "update unsupported"}
      String.match?(sql, ~r/delete.*/) -> {:error, "delete unsupported"}
    end
  end
  def select(sql) do
    Regex.named_captures(~r/select( *)(?<columns>.*)( *)from( *)(?<tables>.*)/, sql)["tables"]
    |> DbMnesia.select
  end
  def columns_rows(result) do
    result
    |> rows
    |> Enum.map(fn row -> Enum.into(List.zip([columns(result), row]), %{}) end)
  end
  def rows(%{rows: rows} = _result), do: rows
  def columns(%{columns: columns} = _result), do: columns
end

DBデータをWeb表示

さて、準備が整いましたので、DBデータをWeb表示してみます

以下のように、Webページの元となるファイルを書き換えます

lib/basic_web/templates/page/index.html.heex
<%
datas = Db.query("select * from members") |> Db.columns_rows
%>
<table>
<%= for n <- datas do %>
<tr>
  <td><%= n["name"] %></td>
  <td><%= n["age"] %></td>
  <td><%= n["team"] %></td>
  <td><%= n["position"] %></td>
</tr>
<% end %>
</table>

以下のようなWebページが表示されるようになります
image.png

なお、datas の作成部分以外は、前回のマップリストと全く同じコードです(つまり、Elixir内部のデータ構造が全く同じ、なので、それを処理する側も全く同じ書き方で良い、ということを意味しています)

前回マップリスト版のindex.html.heex
<%
datas = [
  %{ "name" => "enぺだーし", "age" => 54, "team" => "有限会社デライトシステムズ", "position" => "代表取締役、性能探求者" }, 
  %{ "name" => "ざっきー", "age" => 50, "team" => "公立大学法人 北九州市立大学", "position" => "准教授、カーネルハッカー" }, 
  %{ "name" => "たくと", "age" => 40, "team" => "カラビナテクノロジー株式会社", "position" => "リードエンジニア" }, 
  %{ "name" => "piacere", "age" => 48, "team" => "株式会社DigiDockConsulting", "position" => "常務取締役CTO、技術顧問、重力プログラマ" }
]
%>
<table>
<%= for n <- datas do %>
<tr>
    <td><%= n["name"] %></td>
    <td><%= n["age"] %></td>
    <td><%= n["team"] %></td>
    <td><%= n["position"] %></td>
</tr>
<% end %>
</table>

【参考】本コラムの検証環境

本コラムは、以下環境で検証しています(恐らくUbuntu実機やMacでも動きます)

  • Windows 10

    • 実機*Elixir 1.14.2 (Erlang/OTP 25)
      • Phoenix 1.6.15
    • WSL2/Ubuntu 20.04+Elixir 1.14.2 (Erlang/OTP 25) ※最新版のインストール手順はコチラ
      • Phoenix 1.6.15
    • Docker/Debian 11.6+Elixir 1.14.2 (Erlang/OTP 25)
      • Phoenix 1.6.15
    • WSL2/Ubuntu 18.04+Elixir 1.13.0 (Erlang/OTP 24)
      • Phoenix 1.6.6
      • Phoenix 1.6.2
    • 実機+Elixir 1.11.3
      • Phoenix 1.5.x
    • 実機+Elixir 1.8.1
      • Phoenix 1.4.2
    • 実機+Elixir 1.6.x
      • Phoenix 1.3.x
    • Docker+Elixir 1.6.x
      • Phoenix 1.3.x
  • Windows11

    • WSL2/Ubuntu 22.04+Docker Compose+Elixir 1.13.4 (Erlang/OTP 25)
      • Phoenix 1.6.15

終わり

今回で、DBデータをWebで表示できるようになりました

関数型言語によるWeb+DB開発が、こんなカンタンに出来ちゃうんだ … と感じていただけたら幸いです

データ元がDBに変わっても、画面側のコードが変わらなかったことに着目すると、関数型言語の威力がじょじょに見えてきます

次回は、Webに外部APIデータ表示 を行い、やはり画面側は、一切手直しがいらないことを更に実感していただきます

62
35
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
62
35

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?