スポンサーリンク

2015年10月10日

[Elixir+Phoenix]FullCalendar with Phoenix-Framework

Goal

Phoenix-Framework上でFullCalendar(js)を動作させる。

Dev-Environment

OS: Windows8.1
Erlang: Eshell V7.1, OTP-Version 18.1
Elixir: v1.1.1
Phoenix Framework: v1.0.3
PostgreSQL: postgres (PostgreSQL) 9.4.4
FullCalendar: v2.4.0

Wait a minute

FullCalendar(js)を使って、カレンダーを表示してみます。

Index

FullCalendar with Phoenix-Framework
|> Before you start
|> Placement of files
|> Let’s run!!

Before you start

プロジェクトの準備とFullCalendarのダウンロードを行います。
プロジェクトの準備。

Example:

>cd path/to/create/project
>mix phoenix.new full_calendar_sample
>cd full_calendar_sample
>mix ecto.create
>mix phoenix.server
Ctrl+C
FullCalendarのダウンロード。

以下の公式サイトよりダウンロードして下さい。

FullCalendar Download: http://fullcalendar.io/download/

私がダウンロードしたのは、v2.4.0です。

Placement of files

FullCalendarのファイルをPhoenix-Frameworkへ配置します。
配置するファイルは5つです。
  • fullcalendar-2.4.0/fullcalendar.js
  • fullcalendar-2.4.0/fullcalendar.min.css
  • fullcalendar-2.4.0/lib/jquery.min.js
  • fullcalendar-2.4.0/lib/moment.min.js
  • fullcalendar-2.4.0/lang/ja.js (任意、日本語表示したい場合)
プロジェクトに以下のように配置して下さい。

Example:

full_calendar_sample
|
priv
  |
  static
    |
    |-css
    |  |-fullcalendar.min.css
    |
    |-js
       |-fullcalendar.js
       |-jquery.min.js
       |-moment.min.js
       |-ja.js (任意、日本語表示したい場合)
これで配置完了です。

Let’s run!!

お待ちかねのカレンダーを出してみましょう。
cssとjsの読み込みをレイアウトテンプレートで行います。

web/templates/layout/app.html.eex

<!DOCTYPE html>
<html lang="en">
  <head>
    ...
    <link rel="stylesheet" href="<%= static_path(@conn, "/css/fullcalendar.min.css") %>">

    <script src="<%= static_path(@conn, "/js/jquery.min.js") %>"></script>
    <script src="<%= static_path(@conn, "/js/moment.min.js") %>"></script>
    <script src="<%= static_path(@conn, "/js/fullcalendar.js") %>"></script>
    <script src="<%= static_path(@conn, "/js/ja.js") %>"></script>
  </head>

  ...
</html>
デフォルトで配置されているindexテンプレートを以下のように変更します。

web/templates/page/index.html.eex

<script language="JavaScript">
$(document).ready(function() {
  $('#calendar').fullCalendar({

  });
});
</script>

<div id="calendar"></div>
基本的な使い方は公式サイトのUsageを見て下さい。
参考: http://fullcalendar.io/docs/usage/
試しにカレンダーでイベント(予定)を表示してみます。

web/templates/page/index.html.eex

<script language="JavaScript">
$(document).ready(function() {
  $('#calendar').fullCalendar({
    events: [
        {
          title: 'サンプル1',
          start: '2015-10-15'
        },
        {
          title: 'サンプル2',
          start: '2015-10-16',
          end: '2015-10-18'
        },
        {
          title: 'サンプル3',
          start: '2015-10-18',
          url: "http://www.google.co.jp"
        },
        {
          title: 'サンプル4',
          start: '2015-10-01',
          end: '2015-10-07'
        }
      ]
  });
});
</script>

<div id="calendar"></div>

Speaking to oneself

割かし簡単に動いた。
取り急ぎ最初の使い方まで。

Bibliography

2015年10月2日

[Elixir]マクロだってテストしたい!

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

Goal

ExUnitからのマクロのテスト方法を習得する。

Dev-Environment

OS: Windows8.1
Erlang: Eshell V7.1, OTP-Version 18.1
Elixir: v1.1.1

Wait a minute

マクロをはどうやってExUnitでテストしたらいいのでしょうか?
丁度良く、友人から強だゲフンゲフン・・・快くお貸し頂いたMetaprogramming Elixirにある方法を、試してみようと思います。

Index

Macro test with ExUnit
|> Before you start
|> Macro test with ExUnit

Before you start

始める前に準備をしましょう!

Example:

>cd path/to/project
>mix new macro_sample
>cd macro_sample
>mix test
よし、ではやっていきましょう!!

Macro test with ExUnit

自作したマクロをExUnitでテストするにはどうすればいいのでしょうか?
結論から言えば、別に難しいことはないです。
難しかったら、誰も使わない気がしますが(笑)
まずは、テスト用の適当なマクロを作成してしまいましょう。

File: lib/macro_sample.ex

defmodule MacroSample do
  defmacro sample(string) do
    quote do
      unquote(string)
    end
  end
end
続いてテストコードです。

File: test/macro_sample_test.exs

ExUnit.start
Code.require_file("lib/macro_sample3.ex")

defmodule MacroSampleTest do
  use ExUnit.Case
  import MacroSample

  test "Ensure loaded?" do
    assert Code.ensure_loaded?(MacroSample3)
  end
end
Code.require_file/2で対象のファイルをrequireしています。
その後、importしています。
Code.ensure_loaded?/1では、読み込んだモジュールがロードされているか確認しています。
ロードされていればtrueを返しています。
テストを実行してみましょう。

Example:

>mix test
.

Finished in 0.09 seconds (0.08s on load, 0.01s on tests)
1 test, 0 failures

Randomized with seed 738000
最初のテストはパスしました。
それでは、本題のマクロをテストコードで使ってみましょう。
といっても、既に読み込みもできているのが確認できているので、後は普通の関数と変わりませんね。
テストコードを追加します。

File: test/macro_sample_test.exs

...

defmodule MacroSampleTest do
  ...

  test "Macro test sample" do
    assert MacroSample.sample("hoge") == "hoge"
  end
end
テスト用に作ったマクロは、渡した引数をそのままunquoteしているだけです。
なので文字列:hogeを渡したら、文字列:hogeが返ってくるはずです。
テストを実行しましょう!

Example:

>mix test
..

Finished in 0.07 seconds (0.07s on load, 0.00s on tests)
2 tests, 0 failures

Randomized with seed 622000
テストが通過しました。

Speaking to oneself

もっと難しいマクロのテストをどうするんだ?とかとか色々とまだあるのですが、
最初の足掛かりとしてはこれで良いです。
気が向いたら続きの記事作ります。

Bibliography

2015年10月1日

[Elixir]alias, require, import, useの使い方を学ぶ

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

Goal

alias、require、import、useの使い方を学ぶ。

Dev-Environment

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

Wait a minute

今更ですが、alias、require、import、useの違いを学びます。
それぞれ、どんな機能なのか把握すれば特段難しいこともないですね。

Index

alias, require, import, use
|> alias
|> import
|> require
|> use

alias

モジュールへ別名を付ける時に使います。

Exmaple:

defmodule AliasSample.Sample.Hoge do
  ...
end

defmodule AliasSample.Sample.Huge do
  alias AliasSample.Sample.Hoge, as: Hoge
end

Note:

asオプションをなしで別名定義をすると、モジュール名の最後の部分が自動的に設定されます。
以下は同じ意味になります。
alias AliasSample.Sample.Hoge, as: Hoge == alias AliasSample.Sample.Hoge

import

他のモジュールにある関数やマクロを修飾名をつけないで呼び出す時に使います。

Exmaple:

iex> count([1,2,3])
** (RuntimeError) undefined function: count/1
iex> import Enum, only: [count: 1]
nil
iex> count([1,2,3])
3

Note:

onlyオプションを使えば特定の関数だけ対象にできる。
ない場合は、importしたモジュール全ての関数が対象になる。

require

マクロを展開させる時に使います。

Exmaple:

defmodule RequireSample do
  defmacro sample(arg1, arg2) do
    quote do
      ...
    end
  end
end

iex> RequireSample.sample(arg1, arg2)
エラーが発生し、requireしろとメッセージが表示される。
iex> require RequireSample
nil
iex> RequireSample.sample(arg1, arg2)
正常に実行される。

Note:

マクロを使うためには、コンパイル時にそのモジュールと実装が用意されている必要がある。

use

モジュールのusingを展開させます。

Exmaple:

defmodule UsingSample do
  defmacro __using__(_options) do
    quote do
      ...
      import unquote(__MODULE__)
      ...
    end
  end

  ...
end

defmodule UseSample do
  use UsingSample
end
上記の場合、UsingSample.using/1がUseSampleのuseしている場所で展開されます。

Speaking to oneself

散々ここら辺の機能を使っておいて…よく分かってなかったので、
簡単にまとめてみました。

Bibliography

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

人気の投稿