スポンサーリンク

2015年7月29日

[Elixir+Phoenix]Create Micropost model

Goal

Micropostモデルを作成する。

Dev-Environment

OS: Windows8.1
Erlang: Eshell V6.4, OTP-Version 17.5
Elixir: v1.0.4
Phoenix Framework: v0.13.1
PostgreSQL: postgres (PostgreSQL) 9.4.4

Wait a minute

ようやっとユーザ以外のモデルを作成します。
今回の内容は特に難しいことはないと思います。
気楽にやっていきましょう~

Index

Create Micropost model
|> Migration of Micropost model
|> User has many Micropost, Also Micropost belongs to User
|> Delete together
|> Validation
|> Extra

Migration of Micropost model

マイクロポストのモデルを作成するため、マイグレーションします。
データモデルは以下のようになります。
モデル名: Micropost
テーブル名: microposts
作成するカラム: content(string)、user_id(integer)
自動生成されるカラム: id(integer)、inserted_at(timestamp)、updated_at(timestamp)
Caution:
今回のマイグレーションではインデックスを作成します。
参考: hexdocs - v0.14.3 Ecto.Migration.index/3
また、concurrently:オプションを利用します。
このオプションを理解するためには、PostgreSQLの資料を読む必要があります。
参考: CREATE INDEX - インデックスの同時作成
インデックスに指定するカラムは以下の二つです。
インデックス: user_id(integer)、inserted_at(timestamp)
インデックスは、こんな感じのソースコードになりますね。
create index(:microposts, [:user_id, :inserted_at], concurrently: true)
まずは、コマンドで自動生成をします。
>mix phoenix.gen.model Micropost microposts content:string user_id:integer
* creating priv/repo/migrations/timestamp_create_micropost.exs
* creating web/models/micropost.ex
* creating test/models/micropost_test.exs
ファイル: priv/repo/migrations/timestamp_create_micropost.exs
マイグレーションファイルを以下のように編集する。
defmodule SampleApp.Repo.Migrations.CreateMicropost do
  use Ecto.Migration
  @disable_ddl_transaction true

  def change do
    create table(:microposts) do
      add :content, :string
      add :user_id, :integer

      timestamps
    end

    create index(:microposts, [:user_id, :inserted_at], concurrently: true)
  end
end
マイグレーションを実行。
>mix ecto.migrate

14:20:27.485 [info]  == Running SampleApp.Repo.Migrations.CreateMicropost.change/0 forward

14:20:27.485 [info]  create table microposts

14:20:27.504 [info]  create index microposts_user_id_inserted_at_index

14:20:27.523 [info]  == Migrated in 0.3s
DBの方を確認しましょう。問題なしですね。
Description:
@disable_ddl_transaction属性は、
トランザクションの外部で実行するように強制できる属性です。
記述していないと以下のようなエラーがDBのログに書き出されます。
2015-07-29 14:06:19 JST ERROR:  CREATE INDEX CONCURRENTLYはトランザクションブロックの内側では実行できません
2015-07-29 14:06:19 JST ステートメント:  CREATE INDEX CONCURRENTLY "microposts_user_id_inserted_at_index" ON "microposts" ("user_id", "inserted_at")

User has many Micropost, Also Micropost belongs to User

ユーザは複数のマイクロポストを持ち、マイクロポストはユーザに属する設定を実施する。
作成したMicropostモデルと、既に作成しているUserモデルを紐づけます。
既に何度かご紹介している機能です。
  • Ecto.Schema.has_many/3
  • Ecto.Schema.belongs_to/3
この二つを利用します。
表示する部分を作成していないので、設定だけになりますが・・・
次の記事にて、行いますのでしばしお待ちを・・・
ファイル: web/models/user.ex
schema "users" do
  field :name, :string
  field :email, :string
  field :password_digest, :string
  field :password, :string, virtual: true

  has_many :microposts, SampleApp.Micropost

  timestamps
end
ファイル: web/models/micropost.ex
schema "microposts" do
  field :content, :string

  belongs_to :user, SampleApp.User, foreign_key: :user_id

  timestamps
end
Description:
多対1の関係性を忘れてしまった方!!
以下の記事を見ると思い出せるかも?
参考: Ectoで1対多の関係性を検証する

Delete together

ユーザが削除されたら、そのユーザのマイクロポストも削除します。
そうでなくては、ユーザの登録が消えているのに、
マイクロポストだけが残ってしまいますね。
UserControllerのDeleteアクションに処理を追加するだけです。
ファイル: web/controllers/user_controller.ex
deleteアクションを以下のように修正します。
def delete(conn, %{"id" => id}) do
  user = Repo.get(SampleApp.User, id)
  from(m in SampleApp.Micropost, where: m.user_id == ^user.id) |> Repo.delete_all
  Repo.delete(user)

  conn
  |> put_flash(:info, "User deleted successfully.")
  |> redirect(to: static_pages_path(conn, :home))
end

Validation

マイクロポストに対して、Validationを追加します。
ファイル: web/models/micropost.ex
def changeset(model, params \\ :empty) do
  model
  |> cast(params, @required_fields, @optional_fields)
  |> validate_length(:content, min: 1)
  |> validate_length(:content, max: 140)
end

Extra

多対1の簡単な動作テストをしてみます。
iex上で実行します。
Caution:
user_idは、存在しているユーザのidを指定して下さい。
Exsample:
マイクロポストのデータを作成。
iex(1)> changeset = SampleApp.Micropost.changeset(%SampleApp.Micropost{}, %{content: "hogehoge", user_id: 7})
...
iex(2)> SampleApp.Repo.insert(changeset)
...
Exsample:
紐づいたデータが取得できるか確認。
iex(3)> SampleApp.User |> SampleApp.Repo.get(7) |> SampleApp.Repo.preload [:microposts]
...
%SampleApp.User{__meta__: %Ecto.Schema.Metadata{source: {nil, "users"},
  state: :loaded}, email: "nuke@nuke.com", id: 7,
 inserted_at: #Ecto.DateTime<2015-07-23T10:27:19Z>,
 microposts: [%SampleApp.Micropost{__meta__: %Ecto.Schema.Metadata{source: {nil,
     "microposts"}, state: :loaded}, content: "hugehuge", id: 3,
   inserted_at: #Ecto.DateTime<2015-07-29T06:30:46Z>,
   updated_at: #Ecto.DateTime<2015-07-29T06:30:46Z>,
   user: #Ecto.Association.NotLoaded<association :user is not loaded>,
   user_id: 7}], name: "nuke", password: nil,
 password_digest: "S3RtK2pSN1hnSExtSzhUeVpCWjZWUT09LS1wdVZ2MmlpSEtYMjhlZHhXRkp1cUd3PT0=--37D3577AE91D1F5DFB99AD354463046F82733790",
 updated_at: #Ecto.DateTime<2015-07-23T10:27:19Z>}
ついでに、画面上からユーザを削除してみて下さい。
紐づいたマイクロポストも削除されていることが確認できると思います。

Speaking to oneself

migrationのup/downとchangeについて初めて知りましたよ。
そういう意味があったんですね~(無知)
参考: tanihiro.log -【Rails】migrationのchangeとup/downって何が違うの?
今回、Ecto.rollbackを初めて使いました。
@disable_ddl_transaction属性を忘れていまして・・・一度、戻したんですよ。
便利です。rollback機能。

Bibliography

2015年7月28日

[Elixir+Phoenix]Upgrade phoenix.new of mix archive

Goal

mix archiveのphoenix.newをアップグレードする。
before: v0.13.1
after: v0.15.0

Dev-Environment

OS: Windows8.1
Erlang: Eshell V6.4, OTP-Version 17.5
Elixir: v1.0.4
Phoenix Framework: v0.13.1 and v0.15.0

Wait a minute

archiveの方のphoenix.newをアップグレードする。
Rails Tutorial for Phoenixで利用している、
ローカルバージョンはv0.13.1で固定しているので変わりません。
アップグレードは、やりたい方だけ実行して下さい。
何れにせよ、記事が全て書き終われば、
バージョンアップによる変更点は反映していくので・・・
今やらなくても大丈夫です。

Index

VersionUpgrade
|> Upgrade Phoenix of mix archive
|> Extra

Upgrade Phoenix of mix archive

それでは、アップグレードしていきましょう!!
まずは、archive.installで新しいバージョンを指定して上書きできるか試してみます。
>mix archive.install https://github.com/phoenixframework/phoenix/releases/tag/v0.15.0/phoenix_new-0.15.0.ez
Found existing archive(s): phoenix_new-0.13.1.ez.
Are you sure you want to replace them? [Yn] y
* creating .mix/archives/phoenix_new-0.15.0.ez
** (MatchError) no match of right hand side value: {:error, :bad_directory}
    (mix) lib/mix/tasks/archive.install.ex:64: Mix.Tasks.Archive.Install.install_archive/2
    (mix) lib/mix/cli.ex:55: Mix.CLI.run_task/2
    (stdlib) erl_eval.erl:657: :erl_eval.do_apply/6
    (elixir) lib/code.ex:131: Code.eval_string/3
おや、何かエラーが出ましたね。
creating…の文言が出ているのでインストール(アップグレード)はできたようですが・・・
>mix archive
* hex.ez
* phoenix_new-0.15.0.ez
Archives installed at: .../.mix/archives
ふむ、やっぱりインストール(アップグレード)はできてますね。
Phoenixのインストールガイドを見直しました。
問題がでるようだったら、ローカルに.ezファイルをダウンロードしてインストールして下さいとあります。
それに従ってもう一度インストールします。
なので、一度アンインストールします。(念の為)
>mix archive.uninstall phoenix_new-0.15.0.ez

>mix archive
* hex.ez
Archives installed at: .../.mix/archives
以下のリンク先へ行き、Downloadsから”phoenix_new-0.15.0.ez”をダウンロードして下さい。
ダウンロード: Github - phoenixframework/phoenix v0.15.0
ダウンロードしたら適当なディレクトリへ配置。
Description: コマンドプロンプトからパス指定できるならどこでも良い
私の場合、以下のように配置した。
Example: C:\MyWorkSpaces\tools\phoenix_new-0.15.0.ez
>mix archive.install C:\MyWorkSpaces\tools\phoenix_new-0.15.0.ez
Are you sure you want to install archive C:\MyWorkSpaces\tools\phoenix_new-0.15.0.ez? [Yn] y
* creating .mix/archives/phoenix_new-0.15.0.ez
エラーなくインストールできたようです。

Extra

一応、心配なので新しいバージョンのプロジェクトを作成して起動までします。
>mix phoenix.new v_0_15_0_project_sample

We are all set! Run your Phoenix application:

    $ cd v_0_15_0_project_sample
    $ mix ecto.create
    $ mix phoenix.server

You can also run your app inside IEx (Interactive Elixir) as:

    $ iex -S mix phoenix.server
個人的に生成遅くなった気が・・・(気のせい?)
ecto.createの項目が加わってますね。
>cd v_0_15_0_project_sample

>mix ecto.create
コンパイルが流れて・・・
Generated v_0_15_0_project_sample app
The database for V_0_15_0ProjectSample.Repo has been created.

>mix phoenix.server
以下のURLへアクセス。(デフォルトポート使用)
URL: http://localhost:4000
生成から起動まで問題なし!

Speaking to oneself

お疲れ様でした。
アップグレード完了です。
しかし、ネットワーク経由でインストールした場合、
一体何が問題だったんでしょう?
初期インストールの時はエラーは出てなかったと思いますが・・・
まぁ、問題はないみたいなのでいいですけど。
v0.15.0のバージョンは・・・
Channel機能に対しての変更が多いですね。
利用している方は対応大変かもですが、頑張ってください!
(20150728追記)
友人がPhoenix Frameworkのリリースノートを翻訳してくれました。
ハードリカーエンジニア - phoenixframework v0.15.0 リリースノート

Bibliography

2015年7月25日

[Elixir+Phoenix]Display of Markdown in Phoenix (with highlight.js)

Goal

Elixir+Phoenixで、Markdown記法の文字列を表示する。

Dev-Environment

OS: Windows8.1
Erlang: Eshell V6.4, OTP-Version 17.5
Elixir: v1.0.4
Phoenix Framework: v0.13.1
Earmark: v0.1.17
highlight.js: v8.6

Wait a minute

Elixirのライブラリにpragdave/earmarkと言うのがある。
Markdown記法で書かれた文字列をhtmlに変換してくれるライブラリのようです。
今後、自分で作成するWebサービスで導入するので、
動作検証を兼ねて遊んでみようと思います。
かなりシンプルな内容になると思いますが、
参考になれば幸いです。

Index

EarmarkMySample
|> Create Project
|> Preparing for use
|> Let’s use
|> Raw HTML
|> Extra

Create Project

サンプル用のプロジェクトを作成する。
>cd プロジェクト作成ディレクトリ
>mix phoenix.new earmark_sample
>cd earmark_sample
>mix phoenix.server
>ctrl+c

Preparing for use

ライブラリを利用する準備をする。
ファイル: mix.exs
ライブラリの追加。
defp deps do
  [{:phoenix, "~> 0.13.1"},
   {:phoenix_ecto, "~> 0.4"},
   {:postgrex, ">= 0.0.0"},
   {:phoenix_html, "~> 1.0"},
   {:phoenix_live_reload, "~> 0.4", only: :dev},
   {:cowboy, "~> 1.0"},
   {:earmark, "~> 0.1.17"}]
end
依存関係の解決。
>mix deps.get
** (Mix) No package version in registry matches earmark > 0.1.17 (from: mix.exs)

C:\MyWorkSpaces\darui_works_local\elixir_space\earmark_sample>mix deps.get
Running dependency resolution
Dependency resolution completed successfully
  earmark: v0.1.17
* Getting earmark (Hex package)
Checking package (https://s3.amazonaws.com/s3.hex.pm/tarballs/earmark-0.1.17.tar)
Using locally cached package
Unpacked package tarball (c:/Users/Takes_000/.hex/packages/earmark-0.1.17.tar)
準備終わり。

Let’s use

本題です。ライブラリを使います。
ファイル: web/views/page_view.ex
Markdownの文字列を変換する関数を追加。
def parse_markdown(markdown) do
  Earmark.to_html(markdown)
end
ファイル: web/templates/page/index.html.eex
以下の記述をテンプレートへ追加。
<div>
  <%= parse_markdown(@markdown) %>
</div>
ファイル: web/controllers/page_controller.ex
indexアクションを以下のように編集。
def index(conn, _params) do
  markdown = """
  # h1 Title
  ## h2 Title

  `EarmarkSample.PageController`

  `elixir(←`を3つ記述、退避の問題)
  defmodule EarmarkSample.PageView do
    use EarmarkSample.Web, :view

    def markdown_to_html(markdown) do
      Earmark.to_html(markdown)
    end
  end
  ` (←`を3つ記述、退避の問題)
  """

  render conn, "index.html", markdown: markdown
end
実行してみましょう!!
あれれ?生のHTMLが表示されてしまいましたね・・・

Raw HTML

Phoenixでは生のHTMLを表示する場合、
以下のようにしなければいけません。
ファイル: web/templates/page/index.html.eex
以下の記述をテンプレートへ追加。
<div>
  <%= parse_markdown(@markdown) |> raw %>
</div>
Description:
phoenix_htmlライブラリには、raw/1と言う関数があります。
生のHTMLはデフォルトでは退避されてしまうので、
そのまま利用したい場合は、raw/1関数を利用しましょう。

Extra

ソースコードに色(ハイライト)が欲しいとは思いませんか?
そうですよね・・・白黒でソースコード作成したり見たりするのはおかしいですよね・・・
(つい最近まで、Elixirのソースコードが白黒だったなんて言えない・・・)
自分でCSSを作成しますか?
そんな面倒なことはしたくありません!!
ハイライトができるようになる素晴らしいJavaScriptがあります!!
highlight.js
ここから一式をダウンロードして来て下さい。
(Elixirのチェックを付けるのを忘れないで!!)
解答したディレクトリ構成は以下のようになっていました。
ディレクトリ
|-styles/色々なCSS
|-CHANGES.md
|-highlight.pack.js
|-LICENSE
|-README.md
|-README.ru.md
この中のstylesとhighlight.pack.jsを
プロジェクトで以下のように配置して下さい。
jsの配置: priv/static/js/highlight.pack.js
cssの配置: priv/static/css/styles
配置後それぞれの読み込みを追加する。
ファイル: web/templates/layout/application.html.eex
CSSの読み込みを追加。
<link rel="stylesheet" href="<%= static_path(@conn, "/css/styles/default.css") %>">
JavaScriptの読み込み追加。
<script src="<%= static_path(@conn, "/js/highlight.pack.js") %>"></script>
<script>hljs.initHighlightingOnLoad();</script>
ソースコード全体。
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="author" content="">

    <title>Hello Phoenix!</title>
    <link rel="stylesheet" href="<%= static_path(@conn, "/css/app.css") %>">
    <link rel="stylesheet" href="<%= static_path(@conn, "/css/styles/default.css") %>">
  </head>

  <body>
    <div class="container" role="main">
      <div class="header">
        <ul class="nav nav-pills pull-right">
          <li><a href="http://www.phoenixframework.org/docs">Get Started</a></li>
        </ul>
        <span class="logo"></span>
      </div>

      <p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
      <p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>

      <%= @inner %>

    </div> <!-- /container -->
    <script src="<%= static_path(@conn, "/js/app.js") %>"></script>
    <script>require("web/static/js/app")</script>
    <script src="<%= static_path(@conn, "/js/highlight.pack.js") %>"></script>
    <script>hljs.initHighlightingOnLoad();</script>
  </body>
</html>
わーい!ハイライトが付きました!!

Speaking to oneself

ElixirでMarkdownを使う方法探していたんだ!良かった~。
Parserを自分で作るのはやりたくなかった(笑)
ライブラリの作者様には感謝です!!

Bibliography

人気の投稿