任意で渡ってきた変数が特定のStructかどうか判定する方法
is_struct
みたいな便利メソッドがないようなので以下のように判定する必要があった。
割と面倒だったのでまとめておく。
例として、任意で受け取った引数 args
が User
のStructかどうかを判定する。
StructはMapの拡張になっている
まずStructは Mapを拡張した拡張であるので、 is_map(args)
がtrueになる。
Structs are extensions on top of maps that bring default values, compile-time guarantees and polymorphism into Elixir.
ref: Structs - Elixir
iex(1)> user_struct = %User{} %User{...} iex(2)> string = "test" "test" iex(3)> is_map(user_struct) true iex(4)> is_map(string) false
Structは __struct__
というkeyを保持している
In the example above, pattern matching works because underneath structs are bare maps with a fixed set of fields. As maps, structs store a “special” field named struct that holds the name of the struct:
次に、Structの場合は __struct__
というkeyを保持しているので、「MapであるがStructでない変数」の場合は Map.has_key?(args, :__struct)
で弾くことが出来る。
iex(5)> map = %{hoge: "huga"} %{hoge: "huga"} iex(6)> map |> Map.has_key?(:__struct__) false iex(7)> user_struct |> Map.has_key?(:__struct__) true
__struct__
にはStructの名前が入っている
args.__struct__
が User
であればtrueになるので、あとはこれを比較すれば良い。
iex(8)> not_user_struct = %NotUser{} %NotUser{...} iex(9)> user_struct.__struct__ == User true iex(10)> not_user_struct.__struct__ == User false
まとめると
def is_user?(args) do is_map(args) && Map.has_key?(args, :__struct__) && args.__struct__ == User end
ansibleでrubyのArray#zipみたいなことをやる
varsで定義した2つの同じ要素数のリストをrubyのArray#zipみたいに結合してよしなに何かする場合のサンプル。
template module で利用するJinja2というpythonのテンプレートエンジンが、yamlでも展開して利用できる。
- name: "hogehoge" vars: list1: - "1" - "2" - "3" list2: - "a" - "b" - "c" list3: | {% set o = [] %} {% for i in list1 %} {% set _ = o.append({ 'hoge': i, 'huga': list2[loop.index0]}) %} {% endfor %} {{ o }} # list3 = [ {hoge: 1, huga: a}, {hoge: 2, huga: b}, {hoge: 3, huga: c} ] のようになる
参考
Template Designer Documentation — Jinja2 Documentation (2.8-dev)
PhoenixのテンプレートではMap |> Enum.eachでイテレートを回した中身が表示されない
ERBと同じように書こうとしたら詰まったのでメモ。
簡単な例で書いてみる。
Ruby
@grouped_users = User.all.group_by(&:group)
とcontrollerで定義しておくと、erbだと以下のようにループを回せる。
<% @grouped_users.each do |group, users| %> <!-- 何か処理 --> <% users.each do |user| %> <!-- 何か処理 --> <% end %> <% end %>
Elixir
NG
@grouped_users = User |> Repo.all |> Enum.group_by(&(&1.group))
<%= @grouped_users |> Enum.each(fn(group, users) -> %> <!-- 何か処理 --> <%= for user <- users do %> <!-- 何か処理 --> <% end %> <%= end) %>
こう書いても、テンプレート上では ok
としか出ない。 Enum.each
の返り値を表示しようとしているのでこうなっているのだと思われる。
OK
@grouped_users = User |> Repo.all |> Enum.group_by(&(&1.group)) |> Enum.map(fn {key, value} -> {key, value} end)
<%= for {group, users} <- @grouped_users do %> <!-- 何か処理 --> <%= for user <- users do %> <!-- 何か処理 --> <% end %> <% end %>
一度keyword listに変換してあげて、for文で回せば問題なく表示される。
Mac OSXで立ち上げたVagrant内で、dockerのcontainerを立ち上げる時の小技
課題
$ docker run -p 8080:80 --name some_container -it some_env:latest /bin/bash
このように記述すると、containerを立ち上げた後に、開発用のユーザアカウントを用意してSSH接続する想定の場合は、sshdの起動の設定をする必要がある。
2回目以降の起動時に、このcontainerを docker start
しようとすると、特定のユーザのアカウントでMac -> vagrant -> dockerとSSH接続するときに不便。
解決策
monit
というツールを利用すると、docker start時にsshdを自動で立ち上がるようにすることができる。
Dockerでは、内部で動かせるサービスは1つだけなので、 docker run [options] /bin/bash
と指定すると起動時にbashしか立ち上げることが出来ない。
monit
は、本来様々なサービスやプロセスの死活監視に利用されているが、 docker
から動かすサービスに指定すると各サービスを動かすためのコンテナとして利用することが出来る。
なお、今回使うconatinerは ubuntu/trusty64
を前提として話を進める。
事前準備
monitのインストール
/bin/bashを起動プロセスに指定したsome_container内で、以下を実行してmonitをインストールする。
[docker] $ sudo apt-get install monit
confの設定
monitインストール後のデフォルトの状態では、監視したいサービスの設定は /etc/monit/conf.d/
配下を読み込むようになっている。
このため、以下の内容でconfファイルを追加する。
[docker] $ sudo emacs /etc/monit/conf.d/sshd.conf --- check process sshd with pidfile /var/run/sshd.pid start program = "/etc/init.d/ssh start" stop program = "/etc/init.d/ssh stop"
ここまでの内容をsome_envにcommit
一旦dockerから抜けて、以下をVagrant内で実行する。
[vagrant] $ docker stop some_container $ docker commit some_container some_env
containerの立ち上げ
ここまで事前準備が終わったら、一度some_containerを捨てて、起動プロセスにmonit
を指定して docker run
でcontainerを立ち上げ直す。
-I
オプションを付けておかないとバックグラウンドで monit
が立ち上がり、docker上でフォアグラウンドで起動しているタスクがなくなってしまうため起動後即終了してしまう点に注意。
[vagrant] $ docker rm some_container $ docker run -p 8081:80 --name some_container -it some_env:latest /usr/bin/monit -I -c /etc/monit/monitrc
containerの終了
Mac OSXの電源を落とすときに、このcontainerを落としておかないと、次回以降同じcontainerを立ち上げ直すことができなくなる。
これはvagrant上で docker stop some_container
を行わずに電源が落ちると、monitのPIDが残るらしく、以下の様なエラーが出ることがあることが原因の模様。
monit daemon with PID 1 awakened
これを避けるには、Mac OSXの電源を落とすときに必ず docker stop some_container
を行うようにすると良い。
ただ毎度手動でやるのは手間であるのと忘れることもあるため、以下で自動化すると楽なのでお勧め。
/Library/StartupItems/配下に自動起動するデーモンを追加する
必要なのは以下の2つのファイル。
- /Library/StartupItems/SomeDaemon/SomeDaemon : スクリプト本体
- /Library/StartupItems/SomeDaemon/StartupParameters.plist : スクリプトに関する説明を記述するためのplistファイル
追加方法は以下を参考にすると良さそう。