スポンサーリンク

2015年9月29日

[Rails Tutorial for Phoenix]Phoenix Tutorial - 目次

バージョン: v1.0.0

Goal

Ruby on Rails TutorialのサンプルアプリケーションをPhoenix-Frameworkで作成する。

Dev-Environment

OS: Windows8.1
Erlang: Eshell V7.1, OTP-Version 18.1
Elixir: v1.1.1
Node.js: v0.12.4
Phoenix Framework: v1.0.3
Safetybox: v0.1.2
Scrivener: v1.0.0
Bootstrap: v3.3.5
PostgreSQL: postgres (PostgreSQL) 9.4.4

Wait a minute

Ruby on Rails Tutorial作者様、日本語訳を実施して下さった訳者様方に感謝を捧げます。
Ruby on Rails TutorialをPhoenix-Frameworkで実施する企画です。
内容は、Phoenix-Frameworkの初心者、入門者を対象としてものです。
アプリケーション作成部分を主眼に置き実施していきます。
そのため、Elixirの記法であったり、TDD / BDDの部分は端折ります。
また、Gitは使いますが、herokuは使いません。
何故、Ruby on Rails Tutorialをやるのか?
理由は三つ・・・
  • Web開発における基本的な知識が足りないから習得する
  • Phoenix-Frameworkのチュートリアルがないから自分で作る (公式のGuideくらい?)
  • ElixirはRubyライク、Phoenix-FrameworkはRailsライクなので変換が比較的容易
ソースコード一式はGithubへアップしています。
Github: darui00kara/phoenix_tutorial (master)

Index

Roadmap

  • [x] v0.1 ・・・ Rails Tutorialを一通りやり通す
  • [x] v0.2 ・・・ ソースコードのリファクタリング、変更点の記事
  • [x] v0.3 ・・・ v1.0.0へバージョンアップ
  • [x] v0.4 ・・・ デザイン(CSS)の改善
  • [x] v0.5 ・・・ 記事の改修(ほぼ書き直し)
  • [x] v1.0 ・・・ Phoenix v1.0.3アップグレード and 記事の説明や不足を追加
  • [x] v0.? ・・・ RSS(フィード)の実装
  • [ ] v?.? ・・・ 新機能の実装
  • [x] v?.? ・・・ 自作のページネーションライブラリを組込む

v0.1の詳細

Ruby on Rails TutorialをPhoenix-Frameworkで実施して記事にする。

v0.2の詳細

ソースコードのリファクタリングを実施します。
具体的には、機能の重複を排除、粒度が大きい関数を分割、抽象度の向上..etc
主体としては上記の三つになります。それ以外にも細かいところがある感じですね。
記事の回収と並行するのが難しいので、変更点の記事をアップしていきます。
現在、着手する所ですので暫し待たれよ。

v0.3の詳細

v1.0.0へバージョンアップします。

v0.4 (デザインの改善)

さて今のままでは、デザインがクソです。
せめて、styleで直書きしている部分をCSSに書き直すくらいはしないといけません。
デザイン(CSS)の改善します。

v0.5の詳細

ソースコードを改善したら、記事へと反映しないと分かり辛くなってしまいますね。
記事の改修には、以前目次に書いていた改修内容も含まれています。
これは約束(コミット)している内容ですね。
ほぼ書き直しに近い形になりますかね・・・

v1.0の詳細

現在のところ何をするか決めていません。
実装していない機能の実装をするかもしれませんし、新機能の実装をするかもしれません。
Phoenix v1.0.3アップグレードを行いチュートリアルの一通りの実施を行う。
上記の実施に伴い、記事の説明や不足分を加筆修正する。

v0.? (RSSフィード)

フィードは実装していませんでしたね。
そうTutorialを一通り実施したら、RSSフィードを別で実装すると言いました。
(忘れてませんよ?)
追記: 20151026
作成済み。

v?.? (Pagination)

利用していたページネーションライブラリですが・・・非常に役に立ちました。
しかし、機能が足りないと感じる部分もありました。
なので、あのライブラリをベースにして自分用のページネーションライブラリの実装を考えています。
どの段階で着手するかは分かりません。
しかし、着手する前により良い改善や新しいライブラリが見つかればやらない可能性もあります。
その時の状況次第ですね。
Railsで使えるWillPaginateみたいなライブラリを誰かが実装してくれれば、
私が作る必要もないのですが(笑)
追記: 20151026
ScrivenerとScrivener_HTMLを使えば問題ないため、この企画は破棄!!

言うだけなら無料

Channelを使って書き直してみる?

Bibliography

2015年9月26日

[Elixir]HTMLを生成するモジュールを作りたい (失敗編)

とある錬金術師の万能薬(Elixir)

Goal

HTML生成モジュールを作成する。

Dev-Environment

OS: Windows8.1
Erlang: Eshell V6.4, OTP-Version 17.5
Elixir: v1.0.5

Wait a minute

最初に書いておきます。失敗談の記事です。
Phoenix_HTMLライブラリを読んで、
HTMLの生成を自分で実装してみようと思った。
今回の内容だと、何かの役に立つって内容ではないです。
検証用なのでかなり大雑把に作っています。

Index

Generate HTML

Generate HTML

最初のステップは、ただpタグの文字列を返すだけです。

first step

defmodule GenerateHtml do
  def p_tag do
    "<p>"
  end
end

iex> GenerateHtml.p_tag
"<p>"
次は、アトムを文字列に変換して出力します。

2th step

defmodule GenerateHtml do
  def tag(:p) do
    "<#{:p}>"
  end
end

iex> GenerateHtml.tag(:p)
"<p>"
少し汎用的にタグの生成をできるようにします。

3th step

defmodule GenerateHtml do
  def tag(name) when is_atom(name) do
    "<#{name}>"
  end

  def close_tag(name) when is_atom(name) do
    "</#{name}>"
  end
end

iex> GenerateHtml.tag(:p)
"<p>"
iex> GenerateHtml.close_tag(:p)
"</p>"
CSSのclassを指定できるようにします。

4th step

defmodule GenerateHtml do
  def tag(name) when is_atom(name) do
    tag(name, [])
  end

  def tag(name, attrs) when is_atom(name) and is_list(attrs) do
    "<#{name} #{:class}=\"#{Keyword.get_values(attrs, :class)}\">"
  end

  def close_tag(name) when is_atom(name) do
    "</#{name}>"
  end
end

iex> GenerateHtml.tag(:p, class: "hoge")
"<p class=\"hoge\">"
このままでは、classの指定しかできません。
タグのオプションやidなど他にも指定できた方が良いものが多くありますね。

5th step

defmodule GenerateHtml do
  def tag(name) when is_atom(name) do
    tag(name, [])
  end

  def tag(name, attrs) when is_atom(name) and is_list(attrs) do
    "<#{name}#{build_attrs(attrs)}>"
  end

  def close_tag(name) when is_atom(name) do
    "</#{name}>"
  end

  defp build_attrs(attrs) when is_list(attrs) do
    Enum.reduce(attrs, "", &attrs_mapper/2)
  end

  defp attrs_mapper({key, value}, acc) when is_atom(value) or is_binary(value),
    do: acc <> " #{key}=\"#{value}\""
  defp attrs_mapper({key, value}, acc) when is_list(value),
    do: attrs_mapper({key, Enum.join(value, " ")}, acc)
end

iex> GenerateHtml.tag(:p, class: ["hoge", "huge"], id: :foo)
"<p class=\"hoge huge\" id=\"foo\">"
data-[name]と言ったように-(ハイフン)で区切られたオプションを指定したい場合はどうしましょう?
実は変数名やアトムで-(ハイフン)を使うとエラーになります。
こんな感じに…
iex> data-type = "value"
** (CompileError) iex:3: illegal pattern

iex> data_type = :data-type
** (RuntimeError) undefined function: type/0
キーとバリューの値にあたる部分をタプルにして、ネストとして処理してみます。

6th step

defmodule GenerateHtml do
  def tag(name) when is_atom(name) do
    tag(name, [])
  end

  def tag(name, attrs) when is_atom(name) and is_list(attrs) do
    "<#{name}#{build_attrs(attrs)}>"
  end

  def close_tag(name) when is_atom(name) do
    "</#{name}>"
  end

  defp build_attrs(attrs) when is_list(attrs) do
    Enum.reduce(attrs, "", &attrs_mapper/2)
  end

  defp attrs_mapper({key, value}, acc) when is_atom(value) or is_binary(value),
    do: acc <> " #{key}=\"#{value}\""
  defp attrs_mapper({key, value}, acc) when is_list(value),
    do: attrs_mapper({key, Enum.join(value, " ")}, acc)
  defp attrs_mapper({key, {nest_key, value}}, acc),
    do: attrs_mapper({"#{key}-#{nest_key}", value}, acc)
end

iex> GenerateHtml.tag(:p, class: ["hoge", "huge"], data: {:type, "value"})
"<p class=\"hoge huge\" data-type=\"value\">"
valueの部分にtrue、false、nilが来た場合の処理を追加します。
関数の定義している順番にパターンマッチしているようなので、定義する順番に気を付けて下さい。

7th step

defmodule GenerateHtml do
  def tag(name) when is_atom(name) do
    tag(name, [])
  end

  def tag(name, attrs) when is_atom(name) and is_list(attrs) do
    "<#{name}#{build_attrs(attrs)}>"
  end

  def close_tag(name) when is_atom(name) do
    "</#{name}>"
  end

  defp build_attrs(attrs) when is_list(attrs) do
    Enum.reduce(attrs, "", &attrs_mapper/2)
  end

  defp attrs_mapper({key, true}, acc),
    do: attrs_mapper({key, key}, acc)
  defp attrs_mapper({_key, false}, acc),
    do: acc
  defp attrs_mapper({_key, nil}, acc),
    do: acc
  defp attrs_mapper({key, value}, acc) when is_atom(value) or is_binary(value),
    do: acc <> " #{key}=\"#{value}\""
  defp attrs_mapper({key, value}, acc) when is_list(value),
    do: attrs_mapper({key, Enum.join(value, " ")}, acc)
  defp attrs_mapper({key, {nest_key, value}}, acc),
    do: attrs_mapper({"#{key}-#{nest_key}", value}, acc)
end

iex> GenerateHtml.tag(:p, class: ["hoge", "huge"], data: {:type, "value"}, hoge: true, huge: false, foo: nil, bar: true)
"<p class=\"hoge huge\" data-type=\"value\" hoge=\"hoge\" bar=\"bar\">"
ここまでやって、ようやく気が付きました。
再帰で処理しないとだめだこれ…っとorz
とりあえず今回はここまで~
値の部分がネストのネストになっていたらどうするかなど、対応しないといけないパターンが他にもある。
それにリストとタプルが混在していて分かり辛いので、リストの方へ統一しようと思います。
ちなみに、以下のようなパターンに対応していない。
iex> GenerateHtml.tag(:p, class: ["hoge", "huge"], data: {:type, "value", "value2"}, hoge: true, huge: false, foo: nil, bar: true)
** (FunctionClauseError) no function clause matching in GenerateHtml.attrs_mapper/2
    (generate_html_dsl) lib/generate_html.ex:18: GenerateHtml.attrs_mapper({:data, {:type, "value", "value2"}}, " class=\"hoge huge\"")
               (elixir) lib/enum.ex:1261: Enum."-reduce/3-lists^foldl/2-0-"/3
    (generate_html_dsl) lib/generate_html.ex:7: GenerateHtml.tag/2

iex> GenerateHtml.tag(:p, class: ["hoge", "huge"], data: {:type, "value", :input, "value2"}, hoge: true, huge: false, foo: nil, bar: true)
** (FunctionClauseError) no function clause matching in GenerateHtml.attrs_mapper/2
    (generate_html_dsl) lib/generate_html.ex:18: GenerateHtml.attrs_mapper({:data, {:type, "value", :input, "value2"}}, " class=\"hoge huge\"")
               (elixir) lib/enum.ex:1261: Enum."-reduce/3-lists^foldl/2-0-"/3
    (generate_html_dsl) lib/generate_html.ex:7: GenerateHtml.tag/2
そういうわけで結局、再帰的に処理をする形に修正すると思います。

Speaking to oneself

とりあえず、やってみたが…
何故、Phoenix_HTMLライブラリでは再帰で処理しているのか理解した。
設計思想の一部でも理解できたので、悪くはないでしょう。
最終的に何がやりたいのかって話ですが…
ページネーション用のHTML生成に使えたらな~ってのと、EExのEngineについて知りたかった。
追々、記事にできたらします。ノシ

Bibliography

2015年9月20日

[Elixir]ezファイルを作成して、"mix archive.install"する

とある錬金術師の万能薬(Elixir)

Goal

.ezファイルを作成して、”mix archive.install”する。

Dev-Environment

OS: Windows8.1
Erlang: Eshell V6.4, OTP-Version 17.5
Elixir: v1.0.5

Wait a minute

カスタムタスクを作成した後、そのプロジェクト内もしくはダウンロードしたプロジェクトでしか使えないのは不便ですね。
mixが使えれば、どこからでも呼べるコマンドにしたいですね。
具体的には、”archive.install”でezファイルをインストールして、
mixコマンドからどのディレクトリにいても呼べるようにしたいです。
というわけで、やっていきましょう!!

Index

mix archive.build
|> Preparation
|> mix archive.build
|> Extra

Preparation

プロジェクトの準備をします。
以前、こちらの記事をやっているからは飛ばしても構いません。
プロジェクトを作成します。

Example:

>cd path/to/create/directory
>mix new ez_sample
* creating README.md
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/ez_sample.ex
* creating test
* creating test/test_helper.exs
* creating test/ez_sample_test.exs

Your mix project was created successfully.
You can use mix to compile it, test it, and more:

    cd ez_sample
    mix test

Run `mix help` for more commands.

>cd ez_sample

>mix test

Directory: lib/mix

ディレクトリを作成。

Directory: lib/mix/tasks

ディレクトリを作成。
適当なカスタムタスクを作成します。
ただ、引数を表示するだけのカスタムタスク。

File: mix/tasks/ez_sample.ex

defmodule Mix.Tasks.EzSample do
  use Mix.Task

  @shortdoc "ez sample"

  def run(args) do
    IO.puts (inspect args)
  end
end

mix archive.build

それでは、.ezファイルに固めてmixにインストールできるようにします。

Example:

>mix archive.build
Generated archive ez_sample-0.0.1.ez with MIX_ENV=dev
.exファイルを作成するのは、これだけ(笑)
次は、mixにインストールします。
>mix archive.install ez_sample-0.0.1.ez
Are you sure you want to install archive ez_sample-0.0.1.ez? [Yn] y
* creating c:/Users/user_name/.mix/archives/ez_sample-0.0.1.ez
今回使っているプロジェクト以外の適当なディレクトリに移動してからカスタムタスクを試す。
>cd appropriate/path

>mix ez_sample hoge
["hoge"]

>mix ez_sample hoge huge foo:bar
["hoge", "huge", "foo:bar"]

Extra

手を動かすことはない。
依存関係のライブラリがある場合注意が必要。
ex_mark2pdfと言うライブラリを作成していたのだが、
そのライブラリは、earmarkライブラリに依存している。
ex_mark2pdfを.ezファイルに固めて使ってみたところ、
earmarkの関数がないと言われる。
方法があるのか分からないが、オプションなしで使うと、
依存関係先の機能(関数)は使えない模様。

Speaking to oneself

今回は、.ezファイルへの固め方でした。
探す必要がないくらい簡単でしたorz
ex_mark2pdfは…
作ったはいいけど、あまり使えないライブラリに仕上がってしまった。(まだ、v0.0.1だが…)
今のままだと、プロジェクト内でしか利用ができない!!
自分でmarkdownのparserを作るしかないのか!?

Bibliography

特になし。

2015年9月19日

[Elixir]入出力の使い方を習得する

とある錬金術師の万能薬(Elixir)

Goal

Elixirの入出力を習得する。

Dev-Environment

OS: Windows8.1
Erlang: Eshell V6.4, OTP-Version 17.5
Elixir: v1.0.5

Wait a minute

今回は、IO、File、Pathモジュールについてやります。
簡単に使い方だけまとめて、必要になったらドキュメント見る程度です。

Index

IO, File and Path
|> IO
|> File
|> Path
|> Extra

IO

What is IO module

IOモジュールとはどんな機能を提供してくれるモジュールなのか?
IOのハンドリングを行うモジュールです。
標準入出力(:stdio)、標準エラー(:stderr)、ファイル、
他の入出力デバイスを読み書きするための基本となる機能を提供します。
多くの関数が、IOデバイスを必要とします。
引数の多くは、文字列、文字または文字列のリストを必要とします。
また、バイナリ、バイトまたはバイナリのリストである場合もあります。
それ以外のデータ型が与えられた場合、String.Charsプロトコルにより文字列に変換されるようです。
文字(列)を扱える型を寄越せってのが概ねのようですね。

Note:

IOデバイスはpidかアトム。
アトムならpid(プロセス)を登録したアトムである必要があります。
3つの例外
  • :standard_ioが与えられるとショートカットとして扱われる
  • :stdioは:standard_ioのショートカット
  • :stderrは:standard_errorのショートカット
便宜上、Elixirは、Erlangの:standard_ioと:standard_errorの ショートカットとして、:stdioと:stderrを提供します。

Example:

よく使うIO.puts/2もIOモジュールの関数ですね。
iex> IO.puts "hello world!!"
hello world!!
:ok
iex> IO.gets "yes of no?"
yes of no?yes
"yes\n"
詳しく知りたい方は、公式のドキュメントを読んで下さい。
Document: hexdocs - Elixir (v1.0.5) - IO

File

What is File module

Fileモジュールとはどんな機能を提供してくれるモジュールなのか?
そのままファイル関連を扱えるモジュールですが(笑)
Fileモジュールはファイルを操作するための機能を提供します。
関数は、UNIXのコマンド名であるものが多いです。
ファイルを扱う時、デフォルトではバイナリモードで開かれます。

Example:

defmodule FileSample do
  def write(path, write_data, opts) do
    file = open(path, opts)
    write_data(file, write_data)
    close(file)
  end

  def read(path) do
    File.read!(path)
  end

  defp open(path, opts) do
    case File.open(path, opts) do
      {:ok, file} -> file
      _ -> nil
    end
  end

  defp write_data(file, write_data) do
    IO.binwrite(file, write_data)
  end

  defp close(file) do
    File.close(file)
  end
end

Result:

ファイルに書き込みを行い、内容を確認します。
iex> FileSample.read "lib/tmp.txt"
""
iex> FileSample.write "lib/tmp.txt", "hogehoge", [:write]
:ok
iex> FileSample.read "lib/tmp.txt"
"hogehoge"
書き込んだファイルへ追記します。
iex> FileSample.write "lib/tmp.txt", "hugehuge", [:append]
:ok
iex> FileSample.read "lib/tmp.txt"
"hogehogehugehuge"
ファイルに関する大体の機能があるので、紹介できるのは極々一部です。
詳しく知りたい方は、公式のドキュメントを読んで下さい。
Document: hexdocs - Elixir (v1.0.5) - File

Path

What is Path module

Pathモジュールは、ファイル / ディレクトリのパスを操作できます。
また、パスから要素を取り出したりすることができます。
Pathで扱われる文字データは常にUTF-8でエンコードされます。

Example:

iex> Path.join("hoge", "huge")
"hoge/huge"
iex> Path.expand("./")
"path/to/current/directory"
iex> Path.expand("../")
"path/to/on/one"
iex(13)> Path.expand("~/")
"path/to/user"

Extra

まずこれを見て下さい。

Example:

iex> {:ok, file} = File.open "lib/tmp.txt", [:write]
{:ok, #PID<0.107.0>}
pidが返ってきていますね。これはどういうことを示しているのでしょうか?
Elixirでは、ファイルを開く度に新しいプロセスを生み出します。
つまり、ファイルへ何かを書き込む、もしくはファイルから何かを読み込むと言う処理は、
プロセスでメッセージをやり取りすることに等しいと言うことですね。
これはやろうと思えば、プロセスでファイルのやり取りが可能と言うことを示しています。
IOの関数では、IOデバイスが必要なことが多いと書きました。
先ほどの、ファイルはプロセスで扱える(扱っている)のを考えると、IOデバイスはプロセスであると予想がつきます。
IO.puts/2の定義を見てみます。

Quoted:

puts(device \\ group_leader(), item)
group_leader/0関数(?)とは何でしょう?
全ての入出力デバイスで,どのプロセスにも1つだけグループリーダーと呼ばれる特別なプロセスがあります。

Example:

iex> Process.group_leader
#PID<0.9.0>
つまりgroup_leader/0は、グループリーダのプロセスを取得する関数のようですね。
puts/2は、デフォルト値でプロセス(IOデバイス)を送っていたんですね。
IOデバイスもFileもプロセスで扱っていると言うことを覚えておけば、どこかで役に立つでしょう~

Speaking to oneself

結構長くなってしまった。
まぁいいや、後で見返した時に役に立つだろうし…

Bibliography

人気の投稿