tag:crieit.net,2005:https://crieit.net/tags/Elixir/feed 「Elixir」の記事 - Crieit Crieitでタグ「Elixir」に投稿された最近の記事 2021-05-15T10:54:38+09:00 https://crieit.net/tags/Elixir/feed tag:crieit.net,2005:PublicArticle/17129 2021-05-15T10:54:38+09:00 2021-05-15T10:54:38+09:00 https://crieit.net/posts/Elixir-3-Elixir Elixir学習日記3: Elixir、最初の一歩 <h2 id="背景"><a href="#%E8%83%8C%E6%99%AF">背景</a></h2> <p>Elixirの入門書を読んでいる。</p> <h2 id="学んだこと"><a href="#%E5%AD%A6%E3%82%93%E3%81%A0%E3%81%93%E3%81%A8">学んだこと</a></h2> <ul> <li><code>iex</code>でElixirのREPL(Read-Eval Print Loop)に入れる。</li> </ul> <p><a href="https://crieit.now.sh/upload_images/20363b2be8aecfaff854ef1ca344f249609f22d8a9d78.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/20363b2be8aecfaff854ef1ca344f249609f22d8a9d78.png?mw=700" alt="image.png" /></a></p> <ul> <li>Elixirにおいて変数に代入するというのは、名前に一時的に値を縛りつけること。(Binding)</li> <li>Cのような手続き型の言語においては、変数に実体的な値が代入されている。これを破壊的代入という。</li> <li><p>Elixirにおいてはそのような形ではなく、名前に値を参照させている。上の例では、<code>n</code>に<code>1</code>を参照させている。のであって、この場合、<code>n</code>に<code>1</code>が入っているという箱のイメージは実態とずれていることになるのかな?</p></li> <li><p>「Elixirでは値はイミュータブル」という宣言があったけれど(書籍中)、これは値の再代入が不可能であるという意味ではないようす。上の例(画像)では<code>n</code>に<code>1</code>を代入したあと<code>2</code>を代入してもエラーになっていない。</p></li> <li>これこそは、上の、「変数に実質的な値を代入しているのではなく、参照先を指定しているだけ」ということの現れではないかな。と思った。</li> <li>しかしその場合、データの<code>2</code>はどこに存在し続けているのだろう???????</li> <li>2が空中にぽっかり浮いていて、その2を名前が随時参照する...ということかしら????? それはおかしい...気がする。....</li> </ul> <h3 id="関数、モジュール"><a href="#%E9%96%A2%E6%95%B0%E3%80%81%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB">関数、モジュール</a></h3> <ul> <li>引用。</li> </ul> <blockquote> <p>コンピュータプログラミングにおいて、プログラムの断片をひとつのまとまりとしてとらえたものを一般に「サブルーチン」と呼びます。このサブルーチンを具体的にどう呼ぶかは言語によって異なります。RubyやJavaのようなオブジェクト指向言語ではメソッド(method)と呼ばれることが多いのですが(例外もあります)、Erlang、Haskell、Elixirのような関数型言語では関数(function)と呼ばれます。PHPのように関数とメソッドを使い分ける言語もあります。また、プロシージャ(procedure)という用語を用いる言語もあります(Pascalなど)。</p> <p>Tsutomu Kuroda. Elixir/Phoenix Primer Volume 1 Third Edition: The first step (OIAX BOOKS) (Japanese Edition) (Kindle の位置No.723-727). Coregenik. Kindle 版.</p> </blockquote> <h4 id="気になったこと"><a href="#%E6%B0%97%E3%81%AB%E3%81%AA%E3%81%A3%E3%81%9F%E3%81%93%E3%81%A8">気になったこと</a></h4> <ul> <li><strong>JavaScriptはどこにはいるの???</strong></li> <li>サブルーチンっていう言葉の意味を初めて知った(ちゃんと見た)気がして嬉しい。</li> <li>Rubyでも、Pythonでも「関数」っていう呼び名はある気がするけど、それは便宜上みたいなこと?</li> <li>実際にはRubyでは全てがオブジェクトっていうのは見たことあって、それは例えば、 <code>puts</code>をいきなり書いたとしても、</li> </ul> <pre><code class="ruby">puts('hello!') </code></pre> <ul> <li>これは実は何かしらのグローバルオブジェクトにメッセージを送っているという意味になるのだよ、ということをどこかで見た気がする。</li> <li>実行時にその見えないグローバルオブジェクトに渡されているのだよ、と。</li> <li>これはやっぱり、object orientedな言語においては、何らかのオブジェクトが常にいて、そいつにメッセージを送っているというのに対し、関数型言語ではそうではないということなのかしら。</li> <li><em>method</em>という言葉は、「a particular form of procedure for accomplishing or approaching something, especially a systematic or established one」(New Oxford American Dictionary)なので、establised、つまりもともとすでに出来上がっているもの(オブジェクトに対し設定されているもの)という意味が強いと理解している。functionはそれではないので...ということか。</li> <li>A function, or a method? That is the question...な感じ</li> </ul> <h4 id="Elixirのモジュールは名前空間です"><a href="#Elixir%E3%81%AE%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%81%AF%E5%90%8D%E5%89%8D%E7%A9%BA%E9%96%93%E3%81%A7%E3%81%99">Elixirのモジュールは名前空間です</a></h4> <p>引用2。</p> <blockquote> <p>IOはモジュール(module)の名前です。Elixirのモジュールは関数の入れ物(名前空間)です。</p> <p>Tsutomu Kuroda. Elixir/Phoenix Primer Volume 1 Third Edition: The first step (OIAX BOOKS) (Japanese Edition) (Kindle の位置No.729-730). Coregenik. Kindle 版.</p> </blockquote> <p>名前空間ってそういう意味だったのかあ〜〜〜〜〜〜〜〜〜〜。</p> <blockquote> <p>次のように関数の引数は括弧で囲まなくても構いません。</p> </blockquote> <pre><code>IO.puts n </code></pre> <blockquote> <p>しかし、原則として関数の引数は括弧で囲むことが推奨されています。<br /> Tsutomu Kuroda. Elixir/Phoenix Primer Volume 1 Third Edition: The first step (OIAX BOOKS) (Japanese Edition) (Kindle の位置No.732-735). Coregenik. Kindle 版.</p> </blockquote> <ul> <li>Rubyライクに括弧を省略していいことになっているけど、マナー的には囲うのが一般的だよ。と。</li> </ul> <h3 id="その他学んだこと"><a href="#%E3%81%9D%E3%81%AE%E4%BB%96%E5%AD%A6%E3%82%93%E3%81%A0%E3%81%93%E3%81%A8">その他学んだこと</a></h3> <ul> <li>コメントは<code>#</code>で。(octothorpe, またのなを pound, hash, mesh, とも。)</li> <li>行全体をコメントに。後ろにつければ行末のコメントはできるけど、<strong>複数行に跨がるコメントのシステムはElixirには存在しない</strong>とのこと。これも驚いた。</li> <li>文末のセミコロンはつけてもいいけど、普通は付けずに1行1センテンスで書くよ、とのこと。</li> </ul> <p>次回は4章、基本データ型。</p> <p>だんだんプログラミングっぽくなってきて嬉しいじゃないの。</p> <p>使っている本:<a target="_blank" rel="nofollow noopener" href="https://www.amazon.co.jp/dp/B01N2K6UBZ/ref=cm_sw_r_tw_dp_EB27FXF19FJT6365276W">Elixir/Phoenix 初級① 第3版: はじめの一歩 (OIAX BOOKS) 黒田 努 </a></p> mkataoka73 tag:crieit.net,2005:PublicArticle/17121 2021-05-14T11:54:29+09:00 2021-05-14T11:54:29+09:00 https://crieit.net/posts/Elixir-3-OTP Elixir学習日記3: OTPって何? <p>Elixirについての学習日記です。今回はOTPという用語について整理してみたいと思います。</p> <h2 id="背景"><a href="#%E8%83%8C%E6%99%AF">背景</a></h2> <p>Elixirについて学んでいる。</p> <h2 id="学んだこと"><a href="#%E5%AD%A6%E3%82%93%E3%81%A0%E3%81%93%E3%81%A8">学んだこと</a></h2> <p>OTPって何?ということを理解するには、正しい文脈のなかに置くのがよさそう。言葉を単独で理解するより、それを適切な文脈のなかに置くことで、理解が進むということが多いと思います。</p> <p>とはいえ、まずは著者の引用をみてみましょう。</p> <blockquote> <p>OTPは“OpenTelecomPlatform”の略です。かつては電信電話サービスと関係があったので“telecom”という単語が含まれています。ひとことで言えば、OTPとはErlangの標準のツール類とライブラリ群の集合体です。Erlang業界の用語法では、Erlangは言語仕様そのものであり、OTPがなければ何もできません。OTPにはErlangのコンパイラやインタプリタも含まれます。</p> <p>Tsutomu Kuroda. Elixir/Phoenix Primer Volume 1 Third Edition: The first step (OIAX BOOKS) (Japanese Edition) (Kindle の位置No.362-365). Coregenik. Kindle 版.</p> </blockquote> <p><strong>Open Telecom Platform</strong> (<strong>OTP</strong>) とは、Erlangの標準のツール群とライブラリ群の集合体、とありますね。</p> <p>「OTPには、Erlangのコンパイラやインタプリタも含まれる」と書いてあるので、確かにそれがなければ何もできなさそうです。</p> <p>ErlangとOTPの関係は、言語仕様と、それが動くための仕組み、と覚えておくといいのかもしれないですね。</p> mkataoka73 tag:crieit.net,2005:PublicArticle/17102 2021-05-13T12:14:26+09:00 2021-05-13T12:23:12+09:00 https://crieit.net/posts/Elixir-2-Elixir-Erlang Elixir学習日記2: ElixirとErlang <p>Elixirの勉強を進めている。</p> <h2 id="学んだこと"><a href="#%E5%AD%A6%E3%82%93%E3%81%A0%E3%81%93%E3%81%A8">学んだこと</a></h2> <ul> <li>Elixirのファイルの拡張子は<code>.exs</code>。</li> <li>Atomなどのエディタで普通に書いて、ターミナルから実行できる。ただし、実行環境にはDockerが必要(?)</li> <li>実行環境は、Dockerという仮想環境構築ソフトで用意する。 <ul> <li>Dockerというのは、仮想環境を用意してくれるアプリケーション。けっこう容量が大きい。</li> <li>たとえば、Macで開発しているのに、Windows上で動くか?とかいうのを試すことができる。</li> <li>JavaがJava Virtual Machineという仮想マシン上で動くように、Elixirは—内部的には—BEAMという仮想マシンの上で実行される。 <ul> <li>現代的なプログラム言語は、実はそれぞれに専用の仮想マシンを持っていることが多い。</li> <li>例えば、Rubyもそれ専用のバーチャルマシン(YARVという)の上で解釈され、実行されている。</li> </ul></li> <li>BEAMは、もともとはErlangというプログラミング言語のために開発された実行環境。</li> <li>ErlangとElixirは違う言語だけれど、ElixirのプログラムもBEAM上で実行可能。</li> <li>似たような関係に、JavaとKotlinがある。言語は違うが、同じ環境の上で実行可能。</li> <li><code>.exs</code>ファイルを実行すると、BEAM上で実行できるバイトコードに変換され、BEAMが解釈することでプログラムが実行可能になる。</li> <li>このように、ElixirはErlangと深い関係にある。</li> <li>ElixirのコンパイラがErlangで開発されている。など。</li> </ul></li> </ul> <h3 id="Erlangについて"><a href="#Erlang%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">Erlangについて</a></h3> <ul> <li>Erlangの発表は1986年(Java、Rubyは1995年)。</li> <li>Erlangの名前の由来はスウェーデンの数学者Agner Erlangに由来。</li> <li>最近では<a target="_blank" rel="nofollow noopener" href="https://eh-career.com/engineerhub/entry/2019/08/01/103000#堅牢さと並行性を兼ね備えたErlang">任天堂のゲーム開発にも導入されている</a>様子。</li> </ul> <h2 id="Elixirを実行してみる"><a href="#Elixir%E3%82%92%E5%AE%9F%E8%A1%8C%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B">Elixirを実行してみる</a></h2> <h3 id="hello, world!"><a href="#hello%2C+world%21">hello, world!</a></h3> <p>Elixirで、標準出力に'hello, world!'を出力してみましょう。<br /> こんなプログラムを用意します。</p> <pre><code class="elixir"># ~/elixir_study/hello.exs IO.puts('hello, world!') </code></pre> <p><a href="https://crieit.now.sh/upload_images/20363b2be8aecfaff854ef1ca344f249609c98e6ee373.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/20363b2be8aecfaff854ef1ca344f249609c98e6ee373.png?mw=700" alt="image.png" /></a></p> <p>ファイルが保存してあるディレクトリに移動し、ターミナルで以下を実行します。</p> <pre><code class="bash">elixir hello.exs </code></pre> <p><a href="https://crieit.now.sh/upload_images/20363b2be8aecfaff854ef1ca344f249609c97d12da7e.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/20363b2be8aecfaff854ef1ca344f249609c97d12da7e.png?mw=700" alt="image.png" /></a></p> <p>実行できました!</p> <h3 id="所感"><a href="#%E6%89%80%E6%84%9F">所感</a></h3> <p><code>elixir hello.exc</code>を実行してから'hello, world!'が表示されるまで、結構時間がかかる(1秒未満だけど0.7秒くらい?)ので、仮想マシンで実行しているのだな〜、コンパイルしているのだな〜という感じがする。</p> <h3 id="次は?"><a href="#%E6%AC%A1%E3%81%AF%3F">次は?</a></h3> <p>引き続きどんなことができるのか学習していきます。</p> mkataoka73 tag:crieit.net,2005:PublicArticle/15424 2019-09-25T09:50:26+09:00 2019-09-25T09:50:26+09:00 https://crieit.net/posts/Elixir-Phoenix-Heroku-Docker Elixir+PhoenixのサービスをHeroku+Dockerで動かす <p>Elixir & PhoenixのサービスをDockerイメージでHerokuにデプロイした時の備忘録です。</p> <p>背景として、元々GCE上で動作させていましたが、GCEのIPが有料になるかもという情報があったのでHerokuに移行することにしました。ただ、無料枠で運用している限りだとIPも有料にはならないかもということなので意味はなかったかもしれません。</p> <p>ただ、もう誰も使っていないサービスでデータも少ないですし、ElixirとかNodeのようなビルドがある言語のサービスは本番サーバーでビルドするとサーバーが止まってしまうため本来行うべきではないため、気分転換にDockerでのリリース形式への変更を試してみるのには良かったかなと思いました。</p> <p>ちなみにHerokuではよく使われているElixir用のBuildpackがあるため、本来はそちらを利用したほうが簡単だと思います。しかし、今回のサービスは結構バージョンが古く、ちらっと試したところ動かないようでした。何か設定がおかしかったのかもしれませんが、とにかく試行錯誤するよりは元々Linuxサーバー上で動いているものなのでDocker化してデプロイするようにした方が早くて確実そうだったのでそちらの形にしました。</p> <h2 id="環境"><a href="#%E7%92%B0%E5%A2%83">環境</a></h2> <ul> <li>Elixir 1.6.0</li> <li>Phoenix 1.3.0</li> </ul> <h2 id="Dockerfileをつくる"><a href="#Dockerfile%E3%82%92%E3%81%A4%E3%81%8F%E3%82%8B">Dockerfileをつくる</a></h2> <p>できたのがこんな感じです。</p> <pre><code class="dockerfile">FROM elixir:1.6.0-slim RUN apt-get update && \ apt-get install -y dbus git curl RUN curl -sL https://deb.nodesource.com/setup_9.x | bash - RUN apt-get install -y nodejs inotify-tools RUN apt-get install -y --reinstall build-essential RUN apt-get clean && \ rm -rf /var/lib/apt/lists/* ENV MIX_HOME=/root/.mix MIX_ENV=prod COPY . /app WORKDIR /app RUN mix local.hex --force && \ mix local.rebar --force && \ mix deps.get && \ mix compile EXPOSE 4000 CMD mix phx.server </code></pre> <p>開発もDockerで行っており、本番もLinuxでデプロイ方法はメモしていたのでここは特に問題ありませんでした(余分な処理は混じっているかもしれませんが)。JavaScriptのビルドもありますがこれはローカルでビルドしてそのまま使っています。今回はもう更新しないサービスのDocker化のため処理を入れていませんが、必要であればここでビルドもしておくと良いかもしれません。</p> <p>細かい部分をメモがてら解説しておきます。</p> <h3 id="MIX_HOMEを指定"><a href="#MIX_HOME%E3%82%92%E6%8C%87%E5%AE%9A">MIX_HOMEを指定</a></h3> <p>環境変数としてMIX_HOMEを指定しています。</p> <pre><code class="dockerfile">ENV MIX_HOME=/root/.mix MIX_ENV=prod </code></pre> <p>HerokuのDockerデプロイではrootユーザーではなく、その場で適当なユーザーが作られそちらで起動などのコマンドが実行されます。そのため、Dockerイメージビルド時にhexやrebarを追加した時にrootのディレクトリにインストールされたものが認識されず、サーバー起動コマンド実行時にまたインストール&ビルドが行われてしまいます。すると時間が経ちすぎてデプロイに失敗します。そのためイメージビルド時に処理したものを正常に認識させるため、MIX_HOMEを指定しています。</p> <p>dockerfile内でユーザーの作成&指定も可能ですが、これは無効となります。</p> <h3 id="EXPOSEは不要"><a href="#EXPOSE%E3%81%AF%E4%B8%8D%E8%A6%81">EXPOSEは不要</a></h3> <p><code>EXPOSE 4000</code>を書いていますがこれは不要です。後述しますが、デプロイ時に<code>PORT</code>の環境変数が渡されるため、そちらでサーバーを起動するようにPhoenix側で設定する必要があります。</p> <h2 id="Phoenixの設定"><a href="#Phoenix%E3%81%AE%E8%A8%AD%E5%AE%9A">Phoenixの設定</a></h2> <p><code>config/prod.exs</code>を設定します。元々のサーバーの設定から変えたところはとりあえずこんな感じになりました。</p> <pre><code class="elixir">config :profile, ProfileWeb.Endpoint, load_from_system_env: false, url: [host: System.get_env("APP_HOST"), scheme: "https", port: 443], http: [port: System.get_env("PORT")], cache_static_manifest: "priv/static/cache_manifest.json" config :profile, Profile.Repo, adapter: Ecto.Adapters.MySQL, username: System.get_env("DB_USERNAME"), password: System.get_env("DB_PASSWORD"), database: System.get_env("DB_DATABASE"), hostname: System.get_env("DB_HOST"), charset: "utf8mb4", ssl: true, pool_size: 5 </code></pre> <h3 id="環境変数を使う形にする"><a href="#%E7%92%B0%E5%A2%83%E5%A4%89%E6%95%B0%E3%82%92%E4%BD%BF%E3%81%86%E5%BD%A2%E3%81%AB%E3%81%99%E3%82%8B">環境変数を使う形にする</a></h3> <p>下記のように、可能な限り環境変数を使う形にします。</p> <pre><code class="elixir"> username: System.get_env("DB_USERNAME"), password: System.get_env("DB_PASSWORD"), database: System.get_env("DB_DATABASE"), hostname: System.get_env("DB_HOST"), </code></pre> <p>今はどうか知りませんが、Phoenixのプロジェクトを初期化すると<code>prod.secret.exs</code>を作ってそれがprod.exsから読み込まれることで本番の設定をする、という形になっていましたのでついついその形でやってしまいがちかもしれませんが、Herokuなどで利用することを考えると全部環境変数を利用する形にしておく必要がありますし、そもそも設定も環境毎で別にする必要もなくなるため環境変数を利用する方がシンプルだと思います。</p> <h3 id="DBのpool_sizeを減らす"><a href="#DB%E3%81%AEpool_size%E3%82%92%E6%B8%9B%E3%82%89%E3%81%99">DBのpool_sizeを減らす</a></h3> <pre><code class="elixir"> pool_size: 5 </code></pre> <p>Herokuの無料DBはいくつか種類があります。今回はJawsDBを使っていますが、どれにしろ最大接続数が決まっていますので、それより低くpool_sizeを設定しておかないと接続エラーで起動できなくなります。各DBの仕様を見て最大数よりもちょっと少なめにしておきましょう。</p> <h3 id="ポートの設定"><a href="#%E3%83%9D%E3%83%BC%E3%83%88%E3%81%AE%E8%A8%AD%E5%AE%9A">ポートの設定</a></h3> <p>前述したとおり、ポート番号は環境変数で指定されるため、それでListenする必要があります。そのため下記のように指定します。</p> <pre><code class="elixir"> http: [port: System.get_env("PORT")], </code></pre> <h3 id="URLの設定"><a href="#URL%E3%81%AE%E8%A8%AD%E5%AE%9A">URLの設定</a></h3> <p>通常は不要かもしれませんがURLの設定もしています。</p> <pre><code class="elixir"> url: [host: System.get_env("APP_HOST"), scheme: "https", port: 443], </code></pre> <p>OGPを利用しているため、Routerのヘルパーで正しい完全なURLをテンプレート上に出力する必要がありました。正しく設定をしないと<code>http://localhost:11242</code>みたいなURLが出力されてしまうため、上記のような設定を追加しています。<code>port: 443</code>は不要かなと思いましたが、設定しておくとURLのポート表記を消すことができます。</p> <h2 id="デプロイ"><a href="#%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4">デプロイ</a></h2> <p>諸々の設定ができたらデプロイします。これはHerokuのダッシュボードのDeployページで全部確認できますので細かくは書きませんが、とりあえず</p> <pre><code class="sh">heroku container:push web </code></pre> <p>で手元のDockerfileのイメージがビルド&pushされ、</p> <pre><code class="sh">heroku container:release web </code></pre> <p>でpushしたイメージでリリースされます。あとは<code>heroku run</code>でマイグレーションを行ったり、<code>heroku logs</code>でログを確認しておかしなところを解決すれば動くと思います。下記は実際に動いているものです。(絵文字が文字化けしていますが他のところでは正常に表示されているので単にここの不具合のようです。もう修正はしませんが…)</p> <p><a target="_blank" rel="nofollow noopener" href="https://torima.appllis.net/users/dala00">https://torima.appllis.net/users/dala00</a></p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5d8ab79a0cbf3.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5d8ab79a0cbf3.png?mw=700" alt="" /></a></p> <h2 id="独自ドメイン"><a href="#%E7%8B%AC%E8%87%AA%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3">独自ドメイン</a></h2> <p>独自ドメインは解約したかったのでついでに適当なサブドメインにURLを変更しました。ただどちらにしろ独自ドメインの設定をHeroku側で行わなければなりません。ただ、どうもHerokuでの独自ドメインは有料プランでないとできないようでした。</p> <p>しかし、Cloudflare経由にして無理やりSSLにすることもできるようですので、今回はそのようにしました。~~.herokuapp.comをCNAMEとしてDNSレコードを追加すればよいようです。Herokuの画面で一緒に<code>DNS Target</code>というものも表示されますが、これはCloudflare経由の場合は関係ないようです。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>Buildpackがあればそれを使えば楽ですが、Dockerを使えば特殊なパッケージをインストールしたサービスも動かす事ができますので、必要に応じてHerokuのDockerデプロイを使ってみると良さそうです。CI上でビルド&pushすればGitHubにpushするだけでも可能そうです。</p> <p>容量が少ないですが言語問わず無料でRDBが使えて簡単にデプロイできるのはHerokuだけだと思いますので、ユーザーやアクセスの少ない初期のサービスなどはやっぱりここが良さげだなと感じました。</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14548 2018-09-21T20:04:31+09:00 2018-10-18T07:36:18+09:00 https://crieit.net/posts/Elixir-ExTwitter ElixirのExTwitterでツイートする <p>Elixirには<a target="_blank" rel="nofollow noopener" href="https://github.com/parroty/extwitter">ExTwitter</a>というTwitter用のライブラリがある。<br /> それでツイートしてみた。</p> <h2 id="設定"><a href="#%E8%A8%AD%E5%AE%9A">設定</a></h2> <p>ExTwitterの説明通り。access_tokenとaccess_token_secretはユーザーのものを使うので空にしている。</p> <h2 id="ツイート"><a href="#%E3%83%84%E3%82%A4%E3%83%BC%E3%83%88">ツイート</a></h2> <pre><code class="elixir"> def tweet(token, secret, body) do ex_twitter_configure(token, secret) ExTwitter.API.Tweets.update(body) end defp ex_twitter_configure(token, secret) do conf = [ consumer_key: Application.get_env(:extwitter, :oauth)[:consumer_key], consumer_secret: Application.get_env(:extwitter, :oauth)[:consumer_secret], access_token: token, access_token_secret: secret ] ExTwitter.configure(:process, conf) end </code></pre> <p>ログイン中のユーザーのアクセストークンを使うために、API実行前に設定をしている。</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14227 2018-04-03T04:36:07+09:00 2018-10-18T07:32:11+09:00 https://crieit.net/posts/Phoenix1-3-ex-admin Phoenix1.3でex_adminを使う <p>Phoenixではex_adminという管理画面作成パッケージを導入することで簡単に管理画面が作成できる。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/smpallen99/ex_admin">smpallen99/ex_admin: ExAdmin is an auto administration package for Elixir and the Phoenix Framework</a></p> <p>しかし依存関係やフォルダ構成の違いの問題で最新のPhoenixではうまく動かない。ただ、よくよく調べてみると下記のphx-1.3ブランチを使えばできるっぽいので試してみた。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/smpallen99/ex_admin/tree/phx-1.3">https://github.com/smpallen99/ex_admin/tree/phx-1.3</a>(https://github.com/smpallen99/ex_admin/tree/phx-1.3)</p> <h3 id="導入方法"><a href="#%E5%B0%8E%E5%85%A5%E6%96%B9%E6%B3%95">導入方法</a></h3> <p>phx-1.3ブランチを指定して入れる。ビルドに失敗するのでgettextのバージョンも上げる。</p> <p>mix.exs</p> <pre><code class="elixir">defp deps do ... {:gettext, "~> 0.13.1"}, {:ex_admin, github: "smpallen99/ex_admin", branch: "phx-1.3"}, ... end </code></pre> <p>configを追加。</p> <p>config.exs</p> <pre><code class="elixir">config :ex_admin, repo: MyProject.Repo, module: MyProjectWeb, modules: [ MyProject.ExAdmin.Dashboard, ] </code></pre> <p>後は通常通りのインストールと同じ。</p> <h3 id="brunch-configの修正"><a href="#brunch-config%E3%81%AE%E4%BF%AE%E6%AD%A3">brunch-configの修正</a></h3> <p>そのままだとex_adminのcssとかが混ざってしまう。brunch-config.jsに修正方法が追記されているので、そのとおりに修正。</p> <p>またその際、node_modules内のcssをインポートしている場合はcssの設定だけ下記に修正が必要。</p> <pre><code class="js"> "css/app.css": /^(css|node_modules)/, </code></pre> <h3 id="モデル追加"><a href="#%E3%83%A2%E3%83%87%E3%83%AB%E8%BF%BD%E5%8A%A0">モデル追加</a></h3> <pre><code class="sh">mix admin.gen.resource User </code></pre> <p>これは特に変わりはない。ただ、1.3だと<code>phx.gen.html</code>を使っている場合、context module以下にモデルを入れていると思う。例えば<code>Accounts.User</code>等。</p> <p>なので生成されたadmin/user.ex内のモデルの指定だけ修正しておく。</p> <p>admin/user.ex(before)</p> <pre><code class="elixir"> register_resource MyProject.User do </code></pre> <p>admin/user.ex(after)</p> <pre><code class="elixir"> register_resource MyProject.Accounts.User do </code></pre> <p>これで動作した。</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14233 2017-12-26T06:09:15+09:00 2018-10-29T23:50:59+09:00 https://crieit.net/posts/Wercker-Phoenix-CI WerckerでPhoenixアプリケーションのCI <p>※1.6.0にしてフォーマットを追記</p> <p>WerckerにてPhoenixアプリケーションのCIをするためのwercker.yml。<br /> DBもservicesで追加できるので専用のコンテナを準備する必要がない。</p> <pre><code class="yaml">box: shufo/phoenix:1.6.0 services: - id: mariadb name: mysql username: root password: "" tag: latest env: MYSQL_ALLOW_EMPTY_PASSWORD: "true" build: steps: - script: name: mix format --check-formatted code: mix format --check-formatted - script: name: mix deps.get code: mix deps.get - script: name: mix test code: mix test </code></pre> <p>ほんとはbrunchのビルドもしたかったのだがこのboxだとnpmコマンドが見つからない。<br /> 自分で作っているboxを使えば良いのだが、面倒だったのでとりあえずelixir側だけにした。</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14235 2017-12-21T06:19:58+09:00 2018-10-31T19:05:36+09:00 https://crieit.net/posts/Live-6 サイボウズLiveを作る-第6回-イベント作成 <p>あと一つ大きなメイン機能であるイベント機能が残っていたのでそちらを作成した。</p> <p>色々見てみた結果、とりあえず全部FullCalendarに置き換えればいいだろうと言う結論に至った。</p> <p><a target="_blank" rel="nofollow noopener" href="https://fullcalendar.io/">FullCalendar - JavaScript Event Calendar</a></p> <p>期間や範囲切り替えもあるし、これだけで一通りまかなえる気がする。</p> <p><a href="https://crieit.now.sh/upload_images/84d7f7b7f8ae772890ba098f4217f40a5b0d186bce9d0.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/84d7f7b7f8ae772890ba098f4217f40a5b0d186bce9d0.png?mw=700" alt="" /></a></p> <h3 id="イベント予定メニューマスタ"><a href="#%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E4%BA%88%E5%AE%9A%E3%83%A1%E3%83%8B%E3%83%A5%E3%83%BC%E3%83%9E%E3%82%B9%E3%82%BF">イベント予定メニューマスタ</a></h3> <p>本家にはない(?)が、予定メニューにも色を付けられるようにした。</p> <p><a href="https://crieit.now.sh/upload_images/3092bacb48ffa3436e5dda227b23a98f5b0d186c323ad.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3092bacb48ffa3436e5dda227b23a98f5b0d186c323ad.png?mw=700" alt="" /></a></p> <pre><code class="html"><template> <div class="row"> <div class="col-12"> <div class="sample"> <span class="event-color-sample" v-bind:style="{backgroundColor: currentBgColor.hex, color: currentTextColor.hex}">サンプル</span> </div> </div> <div class="col-12 col-sm-4"> <div>背景色</div> <swatches-picker v-model="currentBgColor"></swatches-picker> </div> <div class="col-12 col-sm-4"> <div>文字色</div> <swatches-picker v-model="currentTextColor"></swatches-picker> </div> <input type="hidden" :name="bg_color_name" :value="currentBgColor.hex"> <input type="hidden" :name="text_color_name" :value="currentTextColor.hex"> </div> </template> <style scoped> div.row { margin-bottom: 20px; } </style> <script> import { Swatches } from 'vue-color' export default { props: ['bg_color_name', 'text_color_name', 'bg_color', 'text_color'], components: { 'swatches-picker': Swatches, }, data () { return { currentBgColor: {hex: this.bg_color === undefined ? '#3F51B5' : this.bg_color}, currentTextColor: {hex: this.text_color === undefined ? '#FFFFFF' : this.text_color}, } }, methods: { } } </script> </code></pre> <p>一覧も<a target="_blank" rel="nofollow noopener" href="https://github.com/SortableJS/Vue.Draggable">vuedraggable</a>を使ってドラッグ&ドロップで簡単に並び替えできるようにした。</p> <p>面倒かと思ったが、元々のテンプレートをとりあえずコピーから始められるのでそれほどでもなかった。</p> <pre><code class="html"><template> <table class="table"> <thead> <tr> <th></th> <th>予定メニュー名</th> <th></th> </tr> </thead> <draggable v-model="scheduleCategories" :element="'tbody'" :options="{handle: '.handle'}" @end="onEnd"> <tr v-for="scheduleCategory in scheduleCategories" :key="scheduleCategory.id"> <td class="handle"><i class="material-icons">drag_handle</i></td> <td> <span v-text="scheduleCategory.name" class="event-color-sample" v-bind:style="{backgroundColor: scheduleCategory.bg_color, color: scheduleCategory.text_color}" ></span> </td> <td class="text-right"> <span><a :href="`/${group_id}/schedule-categories/${scheduleCategory.id}/edit`" class="btn btn-default btn-xs">編集</a></span> <span> <a href="#" data-confirm="削除してよろしいですか?" :data-csrf="csrf" data-method="delete" :data-to="`/${group_id}/schedule-categories/${scheduleCategory.id}`" rel="nofollow" class="btn btn-danger btn-xs" >削除<div class="ripple-container"></div></a> </span> </td> </tr> </draggable> </table> </template> <style scoped> .handle { cursor: crosshair; } </style> <script> import draggable from 'vuedraggable' import axios from 'axios' export default { props: ['group_id', 'schedule_categories'], components: {draggable}, data () { return { scheduleCategories: this.schedule_categories, csrf: document.querySelector('meta[name=csrf]').getAttribute('content'), } }, methods: { onEnd() { axios.put(`/${this.group_id}/schedule-categories/update-order`, { ids: this.scheduleCategories.map(c => c.id), }); } } } </script> </code></pre> <p>保存側。</p> <pre><code class="elixir"> def update_schedule_categories_order(group_id, ids) do Enum.with_index(ids) |> Enum.each(fn{id, index} -> schedule_category = get_schedule_category!(id, group_id) update_schedule_category(schedule_category, %{"display_order" => index + 1}) end) end </code></pre> <h3 id="カレンダー"><a href="#%E3%82%AB%E3%83%AC%E3%83%B3%E3%83%80%E3%83%BC">カレンダー</a></h3> <p>こちらも面倒かと思ったが、データの取得はコールバックに作ればいいだけだったので非常に楽だった。</p> <pre><code class="html"><template> <div id="calendar"> </div> </template> <style scoped> </style> <script> import axios from 'axios' import moment from 'moment' export default { props: ['group_id', 'month'], data () { return { currentEvents: [], } }, mounted() { if (this.month !== undefined) { } $('#calendar').fullCalendar({ locale: 'ja', header: { right: 'month,agendaWeek,agendaDay today prev,next' }, views: { month: { titleFormat: 'YYYY年 MMMM', }, }, buttonText: { today: '今日', month: '月', week: '週', day: '日', }, firstDay: 1, timeFormat: 'HH:mm', defaultDate: this.getDefaultDate(), events: this.loadEvents.bind(this), dayClick: this.dayClick.bind(this), eventClick: this.eventClick.bind(this), }); }, methods: { dayClick(date, jsEvent, view) { const dateText = date.format('YYYY-MM-DD'); if (view.name == 'month') { location.href = `/${this.group_id}/schedules/new?date=${dateText}`; } else { const timeText = date.format('HH:mm:ss'); location.href = `/${this.group_id}/schedules/new?date=${dateText}&time=${timeText}`; } }, eventClick(calEvent, jsEvent, view) { location.href = `/${this.group_id}/schedule-posts/${calEvent.schedule.id}`; }, loadEvents(start, end, timezone, callback) { const startDate = start.format('YYYY-MM-DD'); const endDate = end.format('YYYY-MM-DD'); axios.get(`/${this.group_id}/schedules/events/${startDate}/${endDate}`) .then(response => { this.$emit('GET_AJAX_COMPLETE'); callback(response.data); }); }, getDefaultDate() { if (this.month !== undefined) { return moment(this.month + '-01'); } else { return moment(); } } } } </script> </code></pre> <p>取得側。<br /> こういう重そうな範囲検索は、大規模サービスだとどう実装してるのか気になる。<br /> 日毎にデータを分けてるのかな。(日を子データにしてるとか)大変そう。</p> <pre><code class="elixir"> def list_schedules_for_range(group_id, start_date, end_date) do query = from s in Schedule, where: s.group_id == ^group_id and ( (s.start_date < ^start_date and ^end_date < s.end_date) or (^start_date <= s.start_date and s.start_date <= ^end_date) or (^start_date <= s.end_date and s.end_date <= ^end_date) ) and is_nil(s.deleted_at) Repo.all(query) |> Repo.preload(:schedule_category) end </code></pre> <h3 id="不足点"><a href="#%E4%B8%8D%E8%B6%B3%E7%82%B9">不足点</a></h3> <p>リピートの予定とか放置してる。</p> <p>あと、設備とかどこで使ってるんだろうと思ったら、アクセスの方法によって登録できたりするっぽい。<br /> 完全に抜けてる。</p> <p>わかりづらいなここは。急にグループ関係なしの画面になるし意味が分からない。</p> <h3 id="次"><a href="#%E6%AC%A1">次</a></h3> <p>テスト放置なので整備しようかと思う。<br /> そっちの方が面白くて書くこと多かったりするかもしれない。<br /> ソース書いてるのVueばっかりだし。</p> <p><a target="_blank" rel="nofollow noopener" href="https://live.alphabrend.com">Copying live</a></p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14237 2017-12-19T06:24:49+09:00 2018-10-24T21:37:00+09:00 https://crieit.net/posts/Systemd-Phoenix Systemdを使ったPhoenixの本番デプロイ詳細例 <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/advent-calendar/2017/elixir">Elixir Advent Calendar 2017 - Qiita</a></p> <p>19日目。</p> <p>サーバーを準備し、コンテナを使わずに運用できるところまでの準備まで一通りまとめてみた。<br /> Systemdで動作させる。</p> <p>(以前残したログをまとめているだけなので正確でない可能性あり)</p> <h3 id="前提"><a href="#%E5%89%8D%E6%8F%90">前提</a></h3> <ul> <li>Elixir1.5</li> <li>Phoenix1.3</li> <li>Debian</li> </ul> <h3 id="初期設定"><a href="#%E5%88%9D%E6%9C%9F%E8%A8%AD%E5%AE%9A">初期設定</a></h3> <p>最初だけ本番で調整したものをコミットしたりもするのでgitもちょっと設定。</p> <pre><code class="sh">sudo apt-get update sudo apt-get install -y git dbus sudo timedatectl set-timezone Asia/Tokyo git config --global user.name "name" git config --global user.email "email" </code></pre> <h3 id="Elixir &amp; Phoneix &amp; Node.js"><a href="#Elixir+%26amp%3B+Phoneix+%26amp%3B+Node.js">Elixir & Phoneix & Node.js</a></h3> <pre><code class="sh">curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash - wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb && sudo dpkg -i erlang-solutions_1.0_all.deb sudo apt-get update sudo apt-get install -y nodejs inotify-tools esl-erlang elixir mix local.hex --force mix local.rebar --force mix archive.install https://github.com/phoenixframework/archives/raw/master/phx_new.ez --force sudo apt-get install --reinstall build-essential </code></pre> <h3 id="DB"><a href="#DB">DB</a></h3> <p>MySQLとかPostgreSQLとか。<br /> 普通は別サーバーだろうし省略。</p> <h3 id="ビルド"><a href="#%E3%83%93%E3%83%AB%E3%83%89">ビルド</a></h3> <p>ssh-keygenかtokenかでcloneできるように準備。<br /> 適当なので環境変数とかユーザーは適宜いい感じにしてもらった方が良いかもしれない。</p> <pre><code class="sh">git clone ********.git cd appdir mix deps.get --only prod sudo MIX_ENV=prod mix compile cd assets npm install sudo npm install -g brunch brunch build --production cd .. sudo MIX_ENV=prod mix phx.digest sudo MIX_ENV=prod mix ecto.migrate sudo MIX_ENV=prod mix phx.server </code></pre> <p>とりあえずここまでで動くか確認する。</p> <h3 id="Let's Encrypt"><a href="#Let%27s+Encrypt">Let's Encrypt</a></h3> <p>省略。下記の記事に一応詳細は書いてある。</p> <p><a target="_blank" rel="nofollow noopener" href="http://alphabrend.hatenablog.com/entry/2017/10/10/214222">PhoenixでLet's EncryptによるSSL - アルファブレンド プログラミングチップス</a></p> <h3 id="Systemd"><a href="#Systemd">Systemd</a></h3> <pre><code class="sh">sudo vi /etc/systemd/system/myapp.service </code></pre> <pre><code class="ini">[Unit] Description=My app [Service] WorkingDirectory=/home/username/appdir Restart=always Environment=MIX_ENV=prod HOME=/root ExecStart=/usr/local/bin/mix phx.server [Install] WantedBy=multi-user.target </code></pre> <pre><code class="sh">sudo systemctl enable cybozulive sudo systemctl start cybozulive </code></pre> <h3 id="運用"><a href="#%E9%81%8B%E7%94%A8">運用</a></h3> <p>pull, compile, brunch build, digest, migrate, restart</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14238 2017-12-06T06:25:29+09:00 2018-10-18T07:16:11+09:00 https://crieit.net/posts/Live-5 サイボウズLiveを作る-第5回-グループへ参加 <p>とりあえず一旦グループにメンバーを追加する機能を進めてみた。</p> <p>メールアドレスは今のところ登録してほしくないし、とりあえずそれ無しでできる部分だけ進めた。</p> <p>具体的には本家と同じで、招待URLを使ってそこからアクセスしてログインすればグループ申請となる形。</p> <p>というか、本当にほとんどそれくらいなので何も書くことがない。<br /> とりあえず、ただそのまま処理を書いてるだけなので何の役にも立たないがソースでも貼っておく。</p> <pre><code class="elixir"><br /> def join_request(conn, %{"id" => id, "invitation_hash" => invitation_hash}) do user = Auth.get_user(conn) group = Groups.get_group!(id) cond do invitation_hash != Group.invitation_hash(group) -> redirect(conn, to: "/") user -> Groups.create_invitation(user, group) redirect(conn, to: group_path(conn, :index)) true -> conn |> put_layout(false) |> render("join_request.html", group: group, invitation_hash: invitation_hash) end end </code></pre> <p>ログインしていたらそのままメッセージもなく申請データが登録されて自分のページに戻るので、<br /> 非常にわかりづらい。さすがに直した方がいいかもしれない。</p> <p>承認。</p> <pre><code class="elixir"> def approve(conn, %{"group_id" => group_id, "id" => id}) do user = Auth.get_user(conn) group = Groups.get_group!(group_id) invitation = Groups.get_invitation!(id, group_id) invitation_params = %{"closed_at" => Timex.now} result = Repo.transaction(fn -> Groups.update_invitation!(invitation, invitation_params) Groups.create_group_user!(invitation.user, group) end) case result do {:ok, _changes} -> conn |> put_flash(:info, "承認しました。") |> redirect(to: invitation_path(conn, :requests, group_id)) {:error, _any} -> conn |> put_flash(:error, "エラーが発生しました。") |> redirect(to: invitation_path(conn, :requests, group_id)) end end </code></pre> <p>あとはページ上に表示されているユーザーのリンクは単純なusersのshowだったが、<br /> 関係ないグループのユーザーも表示できてしまうので全てGroupUserのリンクに変更し、<br /> 同じグループのユーザーしかアクセスできないものにした。<br /> 自分の編集画面などはid等のパラメータなどもなしのURLに変更。</p> <h3 id="考察"><a href="#%E8%80%83%E5%AF%9F">考察</a></h3> <p>自分の知っている人を新たなグループに招待する機能もサイボウズLiveにはあり、<br /> それは便利なので必要かなと思う。<br /> 今回は申請、許可的な機能だがそちらは招待なので参加する側が許可すれば参加できる、<br /> 今回とは逆の機能となる。<br /> 招待テーブルを作るのか、フラグで分けるのか、また気が向いた時に本家の画面を見て決めたりなどが必要。</p> <h3 id="次"><a href="#%E6%AC%A1">次</a></h3> <p>次はイベント機能を進めようかと思う。<br /> それが終わったら今テストを全く触っておらず、自動生成されたままのためエラー出まくりなので、<br /> そっちを一旦整備もしたい。修正したり要らないものを捨てて絞ったり。<br /> (なんとなくそっちの方が書くことがあるような気がする)</p> <p>あとはイベント機能に伴い、今まで放置してたタイムゾーン問題も少し時間をかけて調べる時間を取ろうと思う。</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14239 2017-12-05T07:03:13+09:00 2018-10-31T17:45:36+09:00 https://crieit.net/posts/e379835214e697a4af50e463d7a73400 個人開発のだいたいの流れの例 <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/advent-calendar/2017/private-development">個人開発 Advent Calendar 2017 - Qiita</a></p> <p>の5日目。</p> <h3 id="概要"><a href="#%E6%A6%82%E8%A6%81">概要</a></h3> <p>普段から時間があればプログラミングで遊びつつ何か作成している。<br /> せっかくなので誰も使わなくてもリリースしたりしていて、<br /> それらは特に仕事でやっているのとかけ離れているものではないので、<br /> 適当に箇条書き程度で紹介。</p> <h3 id="今作っているもの"><a href="#%E4%BB%8A%E4%BD%9C%E3%81%A3%E3%81%A6%E3%81%84%E3%82%8B%E3%82%82%E3%81%AE">今作っているもの</a></h3> <p>ちょっと前にサイボウズLiveが終了すると発表された。<br /> 自分も丁度お客さんが使っていてそこでやり取りをしていた。<br /> 丁度Elixir & Phoenixを使って色々遊んでいたところに飛び込んできたニュースで、<br /> 丁度別のアプリケーションをキリのいいところまで作り終えたところだったので、<br /> サイボウズLiveをコピーして作ってみようと思い作成中。</p> <p>成果物は下記。</p> <p><a target="_blank" rel="nofollow noopener" href="https://live.alphabrend.com">Copying live</a></p> <p>ちなみに当ブログで連載中。</p> <p><a target="_blank" rel="nofollow noopener" href="http://alphabrend.hatenablog.com/archive/category/%E3%82%B5%E3%82%A4%E3%83%9C%E3%82%A6%E3%82%BALive%E3%82%92%E4%BD%9C%E3%82%8B">サイボウズLiveを作る カテゴリーの記事一覧 - アルファブレンド プログラミングチップス</a></p> <h3 id="進め方"><a href="#%E9%80%B2%E3%82%81%E6%96%B9">進め方</a></h3> <p>とりあえずどんどん出来ていくのが楽しいので、細かいところは無視してどんどん自分のやりたいところを先に進める。<br /> 細かいところは後回し。<br /> 今もデータ登録が中心で削除等の細かい機能は放置している。</p> <h3 id="デザイン"><a href="#%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3">デザイン</a></h3> <p>デザイナーじゃないのでやっぱりBootstrap。<br /> 特に</p> <p><a target="_blank" rel="nofollow noopener" href="https://fezvrasta.github.io/bootstrap-material-design/">Bootstrap</a></p> <p>がおしゃれで何にでも使えるので良いと思う。<br /> JavaScriptのフレームワーク毎にMaterial Desginのライブラリがあったりもするのでそういうのでもいい。</p> <h3 id="とりあえずの画面"><a href="#%E3%81%A8%E3%82%8A%E3%81%82%E3%81%88%E3%81%9A%E3%81%AE%E7%94%BB%E9%9D%A2">とりあえずの画面</a></h3> <p>掲示板</p> <p><a href="https://crieit.now.sh/upload_images/2d38283e660a7c0117faeae103135a165b0d18736371a.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/2d38283e660a7c0117faeae103135a165b0d18736371a.png?mw=700" alt="" /></a></p> <p>Todo</p> <p><a href="https://crieit.now.sh/upload_images/a028542c2b31eaf44e1964a5fff3a0ab5b0d1873d7096.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/a028542c2b31eaf44e1964a5fff3a0ab5b0d1873d7096.png?mw=700" alt="" /></a></p> <h3 id="エディタ"><a href="#%E3%82%A8%E3%83%87%E3%82%A3%E3%82%BF">エディタ</a></h3> <p>Visual Studio Code</p> <p>今のところ知っている中では軽量&高機能&設定手軽なものでベストだと思う。</p> <h3 id="開発環境"><a href="#%E9%96%8B%E7%99%BA%E7%92%B0%E5%A2%83">開発環境</a></h3> <p>docker-composeを使う。Linux Mintなのでただインストールして使うだけ。</p> <p>Elixir & Phoenixだと下記のような感じ。<br /> 見てのとおりだがPhoenix & MySQL & phpMyAdmin。<br /> Phoenixのイメージは誰かがどこかで公開していたのを参考にしただけのもの。</p> <pre><code class="yaml">version: '2' volumes: mysql_data: driver: 'local' services: mysql: image: mysql:8.0 volumes: - mysql_data:/var/lib/mysql ports: - "3310:3306" environment: MYSQL_ALLOW_EMPTY_PASSWORD: "true" phpmyadmin: image: phpmyadmin/phpmyadmin environment: - PMA_ARBITRARY=1 - PMA_HOST=mysql - PMA_USER=root - PMA_PASSWORD= ports: - 8100:80 cybozulive: image: dala00/phoenix:1.3.0 env_file: .docker-env volumes: - .:/var/opt/app ports: - "4000:4000" tty: true stdin_open: true </code></pre> <h3 id="ソース管理"><a href="#%E3%82%BD%E3%83%BC%E3%82%B9%E7%AE%A1%E7%90%86">ソース管理</a></h3> <p>自分の場合、ソースを公開していいならGitHubに置くし、<br /> 多少大きくてあまり公開したくないものはBitbucketに置いている。<br /> (Bitbucket最近重い気がするけど)</p> <h3 id="フロント"><a href="#%E3%83%95%E3%83%AD%E3%83%B3%E3%83%88">フロント</a></h3> <p>Vueを使っている。<br /> Phoenixは最初からbrunchが入っているのでVueを入れたらそのまま使える。<br /> Phoenixの開発サーバーに元々ウォッチ機能がついていて、htmlもcssもJavaScriptも全部保存時に勝手に画面を更新してくれるので何も考える必要がない。<br /> そのためjQueryを使う意味すら無いしもう全員Vueで良いと思う。<br /> なにより簡単に適当に一部のみコンポーネント化できるのが最高。</p> <p>PHPならLaravelも最初からVueが使える。<br /> もしフレームワークに何もついてなくてもwebpack導入してwatchするだけなのでとりあえずもうVueを使っておけばいいと思う。</p> <h3 id="公開"><a href="#%E5%85%AC%E9%96%8B">公開</a></h3> <p>せっかく作ったならぐちゃぐちゃでも公開していったらいいと思う。</p> <p>僕の場合、PHPならさくらのスタンダードを1つ借りてるのでそちらにアップして公開する。<br /> (バージョン上げると動かなくなるものも入っているのでもうひとつ借りたくなるところ…)</p> <p>それ以外の場合、VPSとかでも1アプリケーションごとに500円とか千円とかかかってしまい、<br /> 積み重なっていくとモチベーションに影響してしまうので、最近だと全部GCEの無料のプラン。<br /> Herokuでも良いかもしれないがどうもDBの接続が安定しないようなのでちょっと不安。</p> <p>Phoenixのデプロイ方法はAdvent Calendarの別の記事で紹介する予定。</p> <p>あとSSLは例えしょうもないアプリでももう必須だと思う。<br /> 焦らずにリリース前の、失敗してサーバーをぐちゃぐちゃにしても問題ないタイミングでちゃんと設定しておきたい。</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14242 2017-11-28T07:21:48+09:00 2018-10-23T13:38:35+09:00 https://crieit.net/posts/Live-4-Todo サイボウズLiveを作る-第4回-Todoをざっと <p>掲示板をざっと作成後、次は次に簡単そうなToDoを作成することにした。<br /> とりあえずざっと下記を作成した。</p> <h3 id="ToDoの新規登録、編集、コメント追加"><a href="#ToDo%E3%81%AE%E6%96%B0%E8%A6%8F%E7%99%BB%E9%8C%B2%E3%80%81%E7%B7%A8%E9%9B%86%E3%80%81%E3%82%B3%E3%83%A1%E3%83%B3%E3%83%88%E8%BF%BD%E5%8A%A0">ToDoの新規登録、編集、コメント追加</a></h3> <p>特に目新しいこともなく、コメントなどはほとんど掲示板と同じ。<br /> 黙々とシンプルに作成したので、特筆することはなかった。</p> <h3 id="担当者選択UI"><a href="#%E6%8B%85%E5%BD%93%E8%80%85%E9%81%B8%E6%8A%9EUI">担当者選択UI</a></h3> <p>本家だとselectのマルチセレクトで複数の担当者を選択できるように実装されている。<br /> 昔は良く使われていた気がする。ちゃちゃっとjavascriptで作成できる。</p> <p>ただ、今の時代はそういったUIはnpmでインストールするだけ。<br /> 丁度良さそうなものを見つけたので導入した。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/SortableJS/Vue.Draggable">GitHub - SortableJS/Vue.Draggable: Vue component allowing drag-and-drop sorting in sync with View-Model. Based on Sortable.js</a></p> <p>READMEを見ると分かるように、ドラッグで並び替えも、左右のボックスで入れ替えもできる。<br /> Vueのコンポーネントを作成してhiddenタグを自動的に更新するだけで実装できる。</p> <p><a href="https://crieit.now.sh/upload_images/37464f594c64ce314978c655a879670b5b0d1877e77b1.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/37464f594c64ce314978c655a879670b5b0d1877e77b1.png?mw=700" alt="" /></a></p> <p>コンポーネントの実装も非常にシンプル。</p> <pre><code class="html"><template> <div class="row"> <div class="col-6 col-sm-3"> <div class="card"> <draggable v-model="selectedUsers" :element="'ul'" :options="{group:'users'}" @start="drag=true" @end="drag=true" class="list-group list-group-flush"> <li class="list-group-item" v-for="(user, index) in selectedUsers" :key="user.id"> <img :src="user.avatar"> <span>{</span><span>{</span>user.name<span>}</span><span>}</span> <input type="hidden" :name="`todo_task[todo_tasks_users][${index}][user_id]`" :value="user.id"> <input type="hidden" :name="`todo_task[todo_tasks_users][${index}][display_order]`" :value="index + 1"> </li> </draggable> </div> </div> <div class="col-6 col-sm-3"> <div class="card text-secondary"> <draggable v-model="allUsers" :element="'ul'" :options="{group:'users'}" @start="drag=true" @end="drag=true" class="list-group list-group-flush"> <li class="list-group-item" v-for="user in allUsers" :key="user.id"> <img :src="user.avatar"> <span>{</span><span>{</span>user.name<span>}</span><span>}</span> </li> </draggable> </div> </div> <input v-if="selectedUsers.length == 0" type="hidden" name="todo_task[todo_tasks_users]"> </div> </template> <style scoped> li { cursor: pointer; } img { width: 24px; } </style> <script> import draggable from 'vuedraggable' export default { components: {draggable}, props: ['name', 'value', 'users', 'selected'], data () { const users = JSON.parse(this.users); const selected = JSON.parse(this.selected); return { allUsers: users.filter(user => selected.indexOf(user.id) === -1), selectedUsers: users.filter(user => selected.indexOf(user.id) !== -1), } }, methods: { } } </script> </code></pre> <p>呼び出しも簡単。</p> <pre><code class="html"> <todo-user-select users="<%= Poison.encode!(@users) %>" selected="[<%= if Map.get(@conn.assigns, :todo_task) do Enum.join(Cybozulive.Todo.TodoTask.user_ids(@todo_task), ",") end %>]"></todo-user-select> </code></pre> <h3 id="締め切り日時選択"><a href="#%E7%B7%A0%E3%82%81%E5%88%87%E3%82%8A%E6%97%A5%E6%99%82%E9%81%B8%E6%8A%9E">締め切り日時選択</a></h3> <p>これもよくあるのはinputタグをクリックすると日付選択UIが現れるもの。<br /> ただ、inputタグを使って直接入力させる必要性も感じなかったので完全にDatepickerとTimepickerで選択させるようにした。</p> <p>Datepicker。左下のリンクをクリックで表示される。ゴミ箱クリックでnullとなる。</p> <p><a href="https://crieit.now.sh/upload_images/961ef32b2d18db8213c1b029c5b324755b0d18786630e.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/961ef32b2d18db8213c1b029c5b324755b0d18786630e.png?mw=700" alt="" /></a></p> <p>Timepicker。</p> <p><a href="https://crieit.now.sh/upload_images/e3d7b78c81b911b9da6790256b7372ef5b0d1878cab53.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/e3d7b78c81b911b9da6790256b7372ef5b0d1878cab53.png?mw=700" alt="" /></a></p> <p>実装も特筆することはなくシンプル。Timepickerもほとんど同じ。</p> <pre><code class="html"><template> <span> <a ref="toggle" href="#" @click.prevent="toggle()"> <span>{</span><span>{</span>showDate()<span>}</span><span>}</span> </a> <a href="#" @click.prevent="setNull()"><i class="material-icons">delete_forever</i></a> <input type="hidden" :name="name" :value="showValue()"> </span> </template> <style scoped> .material-icons { font-size: 20px; } </style> <script> import mdDateTimePicker from 'md-date-time-picker' import moment from 'moment' const dialog = new mdDateTimePicker({ type: 'date' }) export default { props: ['name', 'value'], data () { const currentValue = this.value === '' ? null : moment(this.value); if (currentValue !== null) { dialog.time = currentValue; } return { currentValue, } }, mounted() { dialog.trigger = this.$refs.toggle; this.$refs.toggle.addEventListener('onOk', () => { this.currentValue = dialog.time; }) }, methods: { toggle() { dialog.toggle(); }, showDate() { if (this.currentValue === null) { return '(未設定)'; } return this.currentValue.format('YYYY-MM-DD'); }, showValue() { if (this.currentValue === null) { return ''; } return this.currentValue.format('YYYY-MM-DD'); }, setNull() { this.currentValue = null; } } } </script> </code></pre> <p>マークダウン</p> <pre><code class="html"><datepicker name="todo_task[limited_date]" value="<%= Ecto.Changeset.get_field(@changeset, :limited_date) %>"></datepicker> <timepicker name="todo_task[limited_time]" value="<%= Cybozulive.Todo.TodoTask.limited_time(@changeset.data) %>"></timepicker> </code></pre> <h3 id="次"><a href="#%E6%AC%A1">次</a></h3> <p>次はスケジュールを作成、としたいところだが、グループウェアなのにユーザーが自分しかいないのが意味不明なので、<br /> とりあえず招待とかを作ろうかと思う。<br /> 本当はメールアドレスのような個人情報は登録したくはないのだが…非ユーザーを招待するならそれしかなさそう。<br /> (なんかあるのかな)</p> <p><a target="_blank" rel="nofollow noopener" href="https://live.alphabrend.com">Copying live</a></p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14244 2017-11-21T06:49:27+09:00 2017-11-21T06:49:27+09:00 https://crieit.net/posts/Live-3 サイボウズLiveを作る-第3回-トピック登録まで <p>グループは作成できたので次は実際のコンテンツを作成していく。<br /> とりあえず仕様的にシンプルそうな掲示板を作ってみることにした。<br /> (もしかすると細かい機能が多くあるのかもしれないが)</p> <p>処理的に特筆するところは特に何もなかったが、<br /> 投稿に関してはwysiwygエディタを入れた。</p> <p>最終的に画像のアップロードも必要だと思うので有料になるCKEditorは無し。<br /> 最近のスタンダードがよく分からなかったのでStarやForkが非常に多い下記を入れてみた。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/quilljs/quill">GitHub - quilljs/quill: Quill is a modern WYSIWYG editor built for compatibility and extensibility.</a></p> <p>昔のwysiwygエディタといえば、textareaをターゲットにして起動すれば勝手にぜんぶやってくれたが、<br /> これは多分SPA等も考慮されていると思うので勝手にPOSTまで出来るようにはなっていない。<br /> そのため自前でハンドリングしてhiddenタグに入れる。</p> <pre><code class="javascript">import Quill from 'quill'; $(function() { $('div.richtext').each(function() { const $this = $(this); const quill = new Quill(this, { theme: 'snow', }) $this.data('quill', quill); quill.on('text-change', function(delta, oldDelta, source) { const html = $this.find('.ql-editor').html(); this.next('input[type=hidden]').val(html); }.bind($this)) }) </code></pre> <pre><code class="html"> <div class="form-group"> <%= label f, :body, "本文", class: "control-label" %> <div class="richtext"> <p><%= raw(Ecto.Changeset.get_field(@changeset, :body)) %></p> </div> <input type="hidden" name="board_topic[body]" value="<%= Ecto.Changeset.get_field(@changeset, :body) %>"> <%= error_tag f, :body %> </div> </code></pre> <p>上記は元々jQueryで書いていたが下記はVueで書きなおしたもの。</p> <pre><code class="html"><template> <div> <div ref="richtext"> <p v-html="value"></p> </div> <input type="hidden" :name="name" :value="currentValue"> </div> </template> <script> import Quill from 'quill' export default { props: ['name', 'value'], data () { return { currentValue: this.value, } }, mounted() { const quill = new Quill(this.$refs.richtext, { theme: 'snow', }); this.$refs.richtext.querySelector('input[type=text]').classList.add('form-control'); quill.on('text-change', (delta, oldDelta, source) => { const html = this.$refs.richtext.querySelector('.ql-editor').innerHTML; this.currentValue = html; }) }, methods: { } } </script> </code></pre> <p>呼び出しも下記で良いので非常に簡単。新しい時代が来てるなぁという感じ。</p> <pre><code class="html"> <editor name="board_post[body]" value="<%= Ecto.Changeset.get_field(@changeset, :body) %>"></editor> </code></pre> <p>htmlを取る方法も特に無いようなので、<br /> issueを探ってみたら.ql-editor内をそのまま使えばいいとの事だったのでそのようにした。</p> <p>現在までの完成分はこちら。<br /> とりあえずコメント投稿まで。カテゴリも設定、絞り込みできるようにした。<br /> その他の処理はほぼエラー。<br /> あとはタイムゾーンの設定をしていないので時刻がおかしい。</p> <p><a target="_blank" rel="nofollow noopener" href="https://live.alphabrend.com">Copying live</a></p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14245 2017-11-14T07:04:08+09:00 2017-11-14T07:04:08+09:00 https://crieit.net/posts/Live-2 サイボウズLiveを作る-第2回-グループ登録まで <p>アイコンは出来たので引き続きグループの作成機能。</p> <p>本家だととりあえず一番簡単なパターンでは、グループ名だけ入力すれば登録できる。<br /> とりあえずそこまでを作った。<br /> アイコンも選択できるようにしている。</p> <p>実装は非常にシンプルで、まずモデルに所属メンバー用のアソシエーションを設定。マイグレーションなどもマニュアル通り。</p> <pre><code class="elixir"> many_to_many :users, Cybozulive.User, join_through: "groups_users" </code></pre> <p>登録処理もシンプル。</p> <pre><code class="elixir"> def create(conn, %{"group" => group_params}) do user = Auth.get_user(conn) changeset = Ecto.build_assoc(user, :groups) |> Group.changeset(group_params) |> Ecto.Changeset.put_assoc(:users, [user]) case Repo.insert(changeset) do {:ok, group} -> conn |> put_flash(:info, "グループを作成しました。") |> redirect(to: group_path(conn, :show, group)) {:error, changeset} -> icons = IconRepo.get_select_icons(user.id) render(conn, "new.html", changeset: changeset, icons: icons, show_group: false) end end </code></pre> <p>とりあえずここまでを公開した。</p> <p>Copying live</p> <p><a target="_blank" rel="nofollow noopener" href="https://live.alphabrend.com">https://live.alphabrend.com</a></p> <p>GCEのf1-micro、1台にDBまで全部詰め込み。<br /> Let's EncryptでSSL対応。<br /> 体裁とか未完成の部分はぐちゃぐちゃ。</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14246 2017-11-10T07:06:16+09:00 2018-09-05T16:24:45+09:00 https://crieit.net/posts/arc-ecto-DB arc_ectoでDBにファイル名を直接保存したい時 <p><a target="_blank" rel="nofollow noopener" href="https://github.com/stavro/arc_ecto">GitHub - stavro/arc_ecto: An integration with Arc and Ecto.</a></p> <p>を使うと、postされた画像を簡単に登録することができる。</p> <p>ただ、そのためにはmodelの型も専用のものに変えなければいけないので、<br /> string型でなくなってしまう関係で直接ファイル名を保存することが出来ない。</p> <p>とりあえず下記のようにすると保存することが出来た。</p> <pre><code class="elixir"> %Icon{id: current_id, icon: %{ file_name: filename, updated_at: nil <span>}</span><span>}</span> |> Repo.insert! </code></pre> <p>updated_atはパターンマッチの関係でエラーになるので入れている。</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14247 2017-11-08T06:42:30+09:00 2017-11-08T06:42:30+09:00 https://crieit.net/posts/Phoenix-Task-DB PhoenixのTaskでDBにアクセスする <p>PhoenixでTaskを試した。</p> <p>実際にはPhoenixではなくmix自体のtaskを作成して実行するだけ。</p> <p>ただ、単にtask内でRepoを使ってDBアクセスしようとすると下記のようなエラーが出る。</p> <pre><code>repo App.Repo is not started, please ensure it is part of your supervision tree </code></pre> <p>どうもRepoはちゃんとスタートさせなければならないらしい。</p> <p>最終的に下記の様にRepoの初期化を入れることで動いた。</p> <pre><code class="elixir">defmodule Mix.Tasks.Aiue.Oooo do use Mix.Task alias App.Repo alias App.User import Mix.Ecto @shortdoc "aiueo" @moduledoc """ This is aiuoe """ def run(args) do Mix.shell.info "=== Active user ===" ensure_repo(Repo, args) ensure_started(Repo, []) user = Repo.one!(User) changeset = User.changeset(user, %{email: "[email protected]"}) Repo.update!(changeset) IO.inspect(user) end end </code></pre> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14248 2017-11-07T06:28:04+09:00 2018-10-18T07:17:26+09:00 https://crieit.net/posts/Live-1 サイボウズLiveを作る-第1回-アイコン登録まで <h3 id="概要"><a href="#%E6%A6%82%E8%A6%81">概要</a></h3> <p>サイボウズLiveが終了するとのこと。</p> <p><a target="_blank" rel="nofollow noopener" href="https://topics.cybozu.co.jp/news/2017/10/24-4407.html">無料グループウェア「サイボウズLive」サービス終了のお知らせ | サイボウズ株式会社</a></p> <p>丁度他のアプリケーション作成が一区切りついたところだったので、今度はサイボウズLiveのコピーを作ってみようと思う。<br /> ざっと見てみたらそんなにページ数も多くなさそうな気もするし。</p> <p>一通り作ってはみようと思うが特に代替、移行先として呼びこむつもりはない。<br /> そんなアクセスが来たら止まるだろうし。</p> <h3 id="仕様"><a href="#%E4%BB%95%E6%A7%98">仕様</a></h3> <ul> <li>Elixir1.5</li> <li>Phoenix1.3</li> <li>Bootstrap Material Design</li> </ul> <p>JavaScriptはAngularかVueを使おうかと思ったが、面倒だったことを思い出すと嫌になったのでやめた。<br /> ソロだし付属のbrunchでES6を使ってjQueryで綺麗に書けば十分。</p> <p>※PhoenixでVueも簡単に使えるようなので置き換え中。</p> <h3 id="とりあえず作ったところ"><a href="#%E3%81%A8%E3%82%8A%E3%81%82%E3%81%88%E3%81%9A%E4%BD%9C%E3%81%A3%E3%81%9F%E3%81%A8%E3%81%93%E3%82%8D">とりあえず作ったところ</a></h3> <h4 id="認証"><a href="#%E8%AA%8D%E8%A8%BC">認証</a></h4> <p>とりあえずTwitterでログインできるようにした。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/ueberauth/ueberauth_twitter">GitHub - ueberauth/ueberauth_twitter: Twitter Strategy for Überauth</a></p> <p>ユーザー作成やログイン部分は自分で勝手にやりたかったので、<br /> ログインURLの発行とcallbackのパラメータ構築だけ任せ、<br /> 取得したパラメータであれこれやっている。</p> <pre><code class="elixir">defmodule Cybozulive.AuthController do use Cybozulive.Web, :controller alias Cybozulive.UserRepo plug Ueberauth def request(conn, _params) do Ueberauth.Strategy.Helpers.callback_url(conn) end def callback(%{assigns: %{ueberauth_failure: _fails<span>}</span><span>}</span> = conn, _params) do conn |> put_flash(:error, "Failed to authenticate.") |> redirect(to: "/") end def callback(%{assigns: %{ueberauth_auth: auth<span>}</span><span>}</span> = conn, _params) do user = UserRepo.find_or_create!(auth) conn |> Cybozulive.Auth.set_user(user) |> redirect(to: "/") end end </code></pre> <h4 id="アイコンの登録"><a href="#%E3%82%A2%E3%82%A4%E3%82%B3%E3%83%B3%E3%81%AE%E7%99%BB%E9%8C%B2">アイコンの登録</a></h4> <p>とりあえず掲示板とか作りたいところだが、</p> <ul> <li>掲示板作るにはグループが要る</li> <li>グループ作るにはアイコンが要る</li> </ul> <p>ということで面倒だがとりあえずアイコンを登録できる機能を作った。</p> <p>phoenix.gen.htmlとarc_ectoでちゃちゃっと作れた。<br /> そのままだと画像は公開されないので、</p> <p><a target="_blank" rel="nofollow noopener" href="https://medium.com/@Stephanbv/elixir-phoenix-uploading-images-locally-with-arc-b1d5ec88f7a">Elixir / Phoenix — Uploading images locally (With ARC)</a></p> <p>にあるとおり、Plug.Staticの設定で公開できる。</p> <pre><code class="elixir"> plug Plug.Static, at: "/uploads", from: Path.expand("./uploads"), gzip: false </code></pre> <p><a href="https://crieit.now.sh/upload_images/92c96a11d1b8ae1106aa55e70234451e5b0d187e62b80.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/92c96a11d1b8ae1106aa55e70234451e5b0d187e62b80.png?mw=700" alt="" /></a></p> <p>(公開側はBootstrap Material Designだが、管理画面は面倒だし本当は作りたくなかったのでPhoenixデフォルトのBootstrap)</p> <p>全部これで登録はやってられないのでスクリプトで一括登録で作成予定。</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14249 2017-10-26T06:27:36+09:00 2017-10-26T06:27:36+09:00 https://crieit.net/posts/Phoenix Phoenixで作った請求書作成システムをリリース <p>Elixir & Phonenixで作った請求書作成システムをリリースした。</p> <p>元々Misocaを使っていたが、弥生の傘下に入ってからフリープランでは5通までしか作成できなくなってしまった。</p> <p>別に良いかと思っていたが、ちっちゃい請求が続いたりするとどうも超えてしまうのではないかと怖くなることが多くなった。<br /> そのため、ちょうどElixir & Phoenixで遊んでいたので自分で作ったら良いんじゃないかと思い作成してみたところうまくいった。</p> <h3 id="請求書作成サービス"><a href="#%E8%AB%8B%E6%B1%82%E6%9B%B8%E4%BD%9C%E6%88%90%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9">請求書作成サービス</a></h3> <p><a target="_blank" rel="nofollow noopener" href="https://bill.alphabrend.com">Bill Builder</a></p> <p>基本的には自分で使う用なので質素でシンプル。<br /> 別に使いたい人がいたら使ってもらっていいと思う。<br /> まだステージングサーバーもなくDBバックアップもしてないけど、<br /> 自分で使っているのでそのうちする予定。</p> <p>安定するかは不明だけど、自分で使わないといけないから使える程度にはメンテナンス&拡張する予定。</p> <h3 id="本番環境"><a href="#%E6%9C%AC%E7%95%AA%E7%92%B0%E5%A2%83">本番環境</a></h3> <ul> <li>Elixir 1.5</li> <li>Phoenix 1.3</li> <li>MySQL 5.7</li> <li>Google Compute Engine f1-micro</li> <li>Systemctlによるサービス実行</li> <li>SendGrid</li> <li>Let's Encrypt</li> <li>bootstrap material design</li> </ul> <h3 id="開発環境"><a href="#%E9%96%8B%E7%99%BA%E7%92%B0%E5%A2%83">開発環境</a></h3> <p>docker-composeに下記のようなサービスを入れている。</p> <ul> <li>Phoenix</li> <li>MySQL</li> <li>phpMyAdmin</li> <li>PDF作成</li> </ul> <h3 id="Elixir &amp; Phoenixで開発してみた感想"><a href="#Elixir+%26amp%3B+Phoenix%E3%81%A7%E9%96%8B%E7%99%BA%E3%81%97%E3%81%A6%E3%81%BF%E3%81%9F%E6%84%9F%E6%83%B3">Elixir & Phoenixで開発してみた感想</a></h3> <p>非常に良い。とにかくphoenix.gen.htmlでモデル、コントローラ、テンプレートまでざっと作成してくれるのが良い。<br /> それだけで参考になるしちょっといじれば動くのでとてもスムーズに色々進む。<br /> テンプレートがデフォルトでbootstrapなのもいい。<br /> phpだとCakePHP3も同様に最高だった。Laravelはコントローラのメソッドが空なのでやる気をなくす。</p> <p>Elixir自体が今まで使っていた言語とちょっと違うので癖があり躓いたりしたが、<br /> 慣れたら本当に気持ちよく迅速に開発できそう。</p> <h3 id="デプロイ"><a href="#%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4">デプロイ</a></h3> <p>色々Elixirのデプロイ方法もあるみたいだが、とりあえず今は手動。<br /> 頻繁なリリースもないし、今のところ誰も使ってないのでとめちゃいけないということもないし。</p> <p>とはいえ、手動でもデプロイは非常に簡単。</p> <ul> <li>gitでpull</li> <li>compile</li> <li>cssやjsいじってればそちらもビルド</li> <li>マイグレーション追加してたらmigrate</li> <li>準備出来たらsystemctlでrestart</li> </ul> <p>多分restartの瞬間くらいしかダウンタイムも無いと思うのでのんびりできる。<br /> Elixir自体が自分でサービス稼働させているからこその利点。</p> <h3 id="運用費"><a href="#%E9%81%8B%E7%94%A8%E8%B2%BB">運用費</a></h3> <p>とりあえずf1-microだし誰も使ってない限りは無料。<br /> (1サーバーにElixirもMySQLも全部入っている)<br /> だれか使い始めたら適当に広告でも張るがあまり意味なさそう。</p> <h3 id="今後の拡張"><a href="#%E4%BB%8A%E5%BE%8C%E3%81%AE%E6%8B%A1%E5%BC%B5">今後の拡張</a></h3> <p>毎月同じ金額の請求をメールで送るとかできたら良いな、と思ってたけど、<br /> 実は届いていなかった、とかがあると怖いので実装するかどうか検討中。<br /> SendGrid使っているためログは見やすいので問題ないのかもしれない。</p> <p>領収書は使わないけど、体裁ほとんど同じでいいなら作ろうかなとも思う。</p> <h3 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h3> <p>またなんかPhoenixで作りたい。</p> <p><a target="_blank" rel="nofollow noopener" href="https://bill.alphabrend.com">Bill Builder</a></p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14251 2017-10-11T06:42:22+09:00 2017-10-11T06:42:22+09:00 https://crieit.net/posts/Phoenix-Let-s-Encrypt-SSL PhoenixでLet's EncryptによるSSL <h3 id="前提"><a href="#%E5%89%8D%E6%8F%90">前提</a></h3> <p>PhoenixでLet's Encryptにより無料でSSL対応を行う。</p> <ul> <li>Elixir 1.5.2</li> <li>Phoenix 1.3.0</li> </ul> <h3 id="手順"><a href="#%E6%89%8B%E9%A0%86">手順</a></h3> <p>基本的には</p> <p><a target="_blank" rel="nofollow noopener" href="https://medium.com/@a4word/phoenix-app-secured-with-let-s-encrypt-469ac0995775">Phoenix/Elixir App Secured with Let’s Encrypt – Andrew Forward – Medium</a></p> <p>で書かれている通り。</p> <p>とりあえずサーバー起動。</p> <pre><code class="bash">MIX_ENV=prod mix phx.server </code></pre> <p>サーバーを起動したまま.well-knownフォルダを更新しなければならないのでそのための設定を行う。</p> <p>まず.well-knownフォルダ以下をそのままアクセスできるようにするための設定。</p> <p>lib/プロジェクト名/web/endpoint.ex<br /> にてPlug.Staticの設定に.well-knownを追加する。</p> <pre><code class="elixir">plug Plug.Static, at: "/", from: :yourproject, gzip: false, only: ~w(css fonts images js favicon.ico robots.txt .well-known) </code></pre> <p>これで<br /> _build/prod/lib/プロジェクト名/priv/static/.well-known<br /> 以下がそのまま公開され、<br /> <a target="_blank" rel="nofollow noopener" href="http://yourdomain.com/.well-known/****.html">http://yourdomain.com/.well-known/****.html</a><br /> のような感じでアクセスできるようになる。</p> <p>ちなみに、設定を変更したらアプリケーションを再起動する必要があると思う。<br /> また、再起動すると作っていた.well-knownは消えるので混乱しないよう注意。</p> <p>うまくいったらあとはcertbotで証明書を発行する。</p> <p>そして設定を更新。</p> <pre><code class="elixir">config :yourproject, Yourproject.Web.Endpoint, http: [port: 80], https: [port: 443, url: [host: "yourdomain.com", port: 443], keyfile: "/etc/letsencrypt/live/yourdomain.com/privkey.pem", cacertfile: "/etc/letsencrypt/live/yourdomain.com/chain.pem", certfile: "/etc/letsencrypt/live/yourdomain.com/cert.pem"], force_ssl: [hsts: true] </code></pre> <p>これで完了。更新などもそのまま普通にできるようになる。</p> <blockquote> <p>Now your site is being served up only through SSL directly through Phoenix (no Nginx required).</p> </blockquote> <p>と書かれているので別のWEBサーバーを使う冗長な方法もあったのだろうか。<br /> とはいえ_build以下を使う方法なので今後のバージョンによってはできなくなる可能性などもあるのかもしれない。</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14252 2017-10-03T06:19:23+09:00 2017-10-03T06:19:23+09:00 https://crieit.net/posts/Phoenix-render Phoenixで他のフォルダの共通テンプレートをrenderする <p>自動生成されたedit.html.eexのformテンプレート読み込み部分を見ると、下記のようになっている。</p> <pre><code class="elixir"><%= render "form.html", changeset: @changeset, action: post_path(@conn, :update, @post) %> </code></pre> <p>ファイル名しか指定されていないので他の共通フォルダなどに入れている場合は指定が出来そうもない。</p> <p>共通フォルダなどのファイルを指定したい場合どうすればよいかというと、<br /> よくよく調べるとrender関数は第1引数にViewを指定することもできる。</p> <p>また、自動生成されたプロジェクトのViewを見てみると、LayoutView等、コントローラに連動していないView等もある。</p> <p>つまり、まず勝手に使用したいフォルダのViewを作成し、</p> <pre><code class="elixir">defmodule App.CommonView do use App.Web, :view end </code></pre> <p>それを使ってrenderを呼べばいい。これでtemplates/common/test.html.eexが表示される。</p> <pre><code class="elixir"><%= render App.CommonView, "test.html" %> </code></pre> だら@Crieit開発者