スポンサーリンク

2017年4月18日

alchemist report 009

Header

About

ElmでJsonにエンコードする簡易サンプル。

Body

footer

いささか冗長・・・

2017年4月12日

alchemist report 008

alchemist report 008

ElixirのMap(構造体)とASTを使った、就業時間中の暇つぶし内容。
プロジェクト作る。

Example:

> mix new map_and_macro_example
> cd map_and_macro_example
> mix test
ただMap使うんではつまらんので構造体を扱うため、defstructのみを定義したモジュールを作成。

File: lib/User.ex

defmodule User do
  defstruct [:name, :email]
end
ちょっとASTにしたりして遊ぶ。

Example:

iex> %User{name: "hoge", email: "hoge@test.com"}
%User{email: "hoge@test.com", name: "hoge"}
iex> quote do: %User{name: "hoge", email: "hoge@test.com"}
{:%, [],https://gist.github.com/darui00kara/eddf82011b1d99dd166ffb1438b0343b/edit
 [{:__aliases__, [alias: false], [:User]},
  {:%{}, [], [name: "hoge", email: "hoge@test.com"]}]}
さらに遊ぶ。
iex> user = %User{name: "hoge", email: "hoge@test.com"}
%User{email: "hoge@test.com", name: "hoge"}
iex> %User{user | name: "huge"}
%User{email: "hoge@test.com", name: "huge"}
iex> quote do: %User{user | name: "huge"}
{:%, [],
 [{:__aliases__, [alias: false], [:User]},
  {:%{}, [], [{:|, [], [{:user, [], Elixir}, [name: "huge"]]}]}]}
iex> module_name = :User
:User
iex> struct_value = :user
:user
iex> name = "huge"
"huge"
iex> q = {:%, [], [{:__aliases__, [alias: false], [module_name]}, {:%{}, [], [{:|, [], [{struct_value, [], Elixir}, [name: name]]}]}]}
{:%, [],
 [{:__aliases__, [alias: false], [:User]},
  {:%{}, [], [{:|, [], [{:user, [], Elixir}, [name: "huge"]]}]}]}
iex> Macro.to_string(q)
"%User{user | name: \"huge\"}"
iex> quote do: {:%, [], [{:__aliases__, [alias: false], [module_name]}, {:%{}, [], [{:|, [], [{struct_value, [], Elixir}, [name: name]]}]}]}
{:{}, [],
 [:%, [],
  [{:{}, [], [:__aliases__, [alias: false], [{:module_name, [], Elixir}]]},
   {:{}, [],
    [:%{}, [],
     [{:{}, [],
       [:|, [],
        [{:{}, [],
          [{:struct_value, [], Elixir}, [],
           {:__aliases__, [counter: 0], [Elixir]}]},
         [name: {:name, [], Elixir}]]]}]]}]]}
iex> quote do: unquote({:%, [], [{:__aliases__, [alias: false], [module_name]}, {:%{}, [], [{:|, [], [{struct_value, [], Elixir}, [name: name]]}]}]})
{:%, [],
 [{:__aliases__, [alias: false], [:User]},
{:%{}, [], [{:|, [], [{:user, [], Elixir}, [name: "huge"]]}]}]}
おまけ長い(笑)
せっかくなので、マクロでMapをASTで引数にとるものを作る。

File: lib/macro_example.ex

defmodule MacroExample do
  alias User

  defmacro get_struct_info(
    {:%, _, [{_, _, struct_name}, {:%{}, _, values}]}) do

    quote do
      {unquote(struct_name), unquote(values)}
    end
  end

  def user(name, email) do
    get_struct_info %User{name: name, email: email}
  end
end
構造体の名前とキーバリューをタプルにして返すだけ。
別段なにもやっていない。
(そのまま使うのが面倒なんで、同じモジュール内に関数を用意して使う)
動作確認

Example:

iex> MacroExample.user "hoge", "hoge@test.com"

{[:User], [name: "hoge", email: "hoge@test.com"]}
問題なし。
しかし、これだけじゃ他の構造体で動作するかわからないので、もいっこ構造体を定義して確かめてみる。

File: lib/Post.ex

defmodule Post do
  defstruct [:title, :body]
end

File: lib/macro_example.ex

defmodule MacroExample do
  alias User
  alias Post

  ...

  def post(title, body) do
    get_struct_info %Post{title: title, body: body}
  end
end
動作確認。

Example:

iex> MacroExample.post "hogehoge", "foobar"

{[:Post], [title: "hogehoge", body: "foobar"]}
ここで時間切れになった。
「おーい、〜くん。これ頼める?」
「了解です。(就業時間終了間際に仕事頼みにこないでほしい・・・)」
以上。

2017年3月29日

alchemist report 007

Header

About

ElmでJsonのリストデータをデコードする簡易サンプル。

Body

自作した関数
idDecoder =
  (Decode.field "id" Decode.int)

nameDecoder =
  (Decode.field "name" Decode.string)

emailDecoder =
  (Decode.field "email" Decode.string)

userDecoder =
  Decode.map3 User.Schema idDecoder nameDecoder emailDecoder

usersDecoder =
  Decode.list userDecoder

usersDataDecoder =
  (Decode.field "data" usersDecoder)
デコードしたデータを格納するモデル(?)
type alias Schema =
  { id : Int
  , name : String
  , email : String
  }
elm replの実行ログ

> import Json.Decode exposing (..)

> jsonList = "{\"data\":[{\"name\":\"hoge\",\"id\":1,\"email\":\"hoge@test.com\"},{\"name\":\"foo\",\"id\":2,\"email\":\"bar@test.com\"}]}" "{\"data\":[{\"name\":\"hoge\",\"id\":1,\"email\":\"hoge@test.com\"},{\"name\":\"foo\",\"id\":2,\"email\":\"bar@test.com\"}]}"     : String

> decodeString usersDataDecoder jsonList Ok ([{ id = 1, name = "hoge", email = "hoge@test.com" },{ id = 2, name = "foo", email = "bar@test.com" }])     : Result.Result String (List Model.User.Schema)

footer

実行ログの表示がバグってるので見づらいと思われる。(このブログのマークダウンで変換すると表示がバグる)
下記のリンク先にあるREADMEを見た方がわかりやすいかも
darui00kara/front_elm_back_phoenix

2017年3月25日

Environment Elixir(v1.4)&Phoenix(v1.3)&Elm(v0.18)

Header

Env

$ erl +V
Erlang (SMP,ASYNC_THREADS,HIPE) (BEAM) emulator version 8.3

$ elixir -v
Erlang/OTP 19 [erts-8.3] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Elixir 1.4.2

$ node -v
v7.7.3

$ postgres --version
postgres (PostgreSQL) 9.6.1

$ elm -v
0.18.0
Elm not installed? Let’s install!

Example:

$ npm install -g elm

Body

Example

$ mix phx.new elm_elixir_phx
... 
Fetch and install dependencies? [Yn] y
* running mix deps.get
* running mix deps.compile
* running cd assets && npm install && node node_modules/brunch/bin/brunch build

We are all set! Run your Phoenix application:

    $ cd elm_elixir_phx
    $ mix phx.server

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

    $ iex -S mix phx.server

Before moving on, configure your database in config/dev.exs and run:

    $ mix ecto.create

$ cd elm_elixir_phx
$ mix ecto.create
$ mix phx.gen.json Accounts User users name:string email:string
* creating lib/elm_elixir_phx/web/controllers/user_controller.ex
* creating lib/elm_elixir_phx/web/views/user_view.ex
* creating test/web/controllers/user_controller_test.exs
* creating lib/elm_elixir_phx/web/views/changeset_view.ex
* creating lib/elm_elixir_phx/web/controllers/fallback_controller.ex
* creating lib/elm_elixir_phx/accounts/user.ex
* creating priv/repo/migrations/20170325054157_create_accounts_user.exs
* creating lib/elm_elixir_phx/accounts/accounts.ex
* creating test/accounts_test.exs

Add the resource to your api scope in lib/elm_elixir_phx/web/router.ex:

    resources "/users", UserController, except: [:new, :edit]

Remember to update your repository by running migrations:

    $ mix ecto.migrate

File: lib/elm_elixir_phx/web/router.ex

defmodule ElmElixirPhx.Web.Router do
  use ElmElixirPhx.Web, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/", ElmElixirPhx.Web do
    pipe_through :browser # Use the default browser stack

    get "/", PageController, :index
  end

  # Other scopes may use custom stacks.
  scope "/api", ElmElixirPhx.Web do
    pipe_through :api

    resources "/users", UserController, except: [:new, :edit]
  end
end
$ mix ecto.migrate

14:46:40.278 [info]  == Running ElmElixirPhx.Repo.Migrations.CreateElmElixirPhx.Accounts.User.change/0 forward

14:46:40.279 [info]  create table accounts_users

14:46:40.283 [info]  == Migrated in 0.0s
$ cd assets 
$ npm install --save-dev elm-brunch

File: assets/package.json

{
  "repository": {},
  "license": "MIT",
  "scripts": {
    "deploy": "brunch build --production",
    "watch": "brunch watch --stdin"
  },
  "dependencies": {
    "phoenix": "file:../deps/phoenix",
    "phoenix_html": "file:../deps/phoenix_html"
  },
  "devDependencies": {
    "babel-brunch": "6.0.6",
    "brunch": "2.10.7",
    "clean-css-brunch": "2.10.0",
    "css-brunch": "2.10.0",
    "elm-brunch": "^0.8.0",
    "uglify-js-brunch": "2.1.1"
  }
}
$ mkdir lib/elm
$ cd lib/elm
$ elm make
Some new packages are needed. Here is the upgrade plan.

  Install:
    elm-lang/core 5.1.1
    elm-lang/html 2.0.0
    elm-lang/virtual-dom 2.0.4

Do you approve of this plan? [Y/n] y
Starting downloads...

  ● elm-lang/html 2.0.0
  ● elm-lang/virtual-dom 2.0.4
  ●
elm-lang/core 5.1.1
Packages configured successfully!
Success! Compiled 37 modules.
exports.config = {
  ...

  // Phoenix paths configuration
  paths: {
    // Dependencies and current project directories to watch
    watched: ["static", "css", "js", "vendor", "../lib/elm/Endpoint.elm"],
    // Where to compile files to
    public: "../priv/static"
  },

  // Configure your plugins
  plugins: {
    elmBrunch: {
      elmFolder: "../lib/elm",
      mainModules: ["Endpoint.elm"],
      outputFolder: "../../assets/vendor"
    },
    babel: {
      // Do not use ES6 compiler in vendor code
      ignore: [/vendor/]
    }
  },

  ...
};

File: lib/elm/Endpoint.elm

module Endpoint exposing (..)

import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import Http
import Json.Decode as Decode

type alias User =
  { name  : String
  , email : String
  , id    : Int
  }

type alias UserData =
  { data : User }

type Msg =
  LoadUserData ( Result Http.Error User )
  | Name String
  | Email String
  | Signup

-- main

main =
  Html.program
    { init = init
    , view = view
    , update = update
    , subscriptions = subscriptions
    }

init : (User, Cmd Msg)
init =
  ( { name = "none", email = "none", id = 0 }
  , sendRequest
  )

update : Msg -> User -> ( User, Cmd Msg )
update msg user =
  case msg of
    LoadUserData ( Ok newUser ) ->
      ( newUser, Cmd.none )
    LoadUserData ( Err _ ) ->
      ( user, Cmd.none )
    Signup ->
      ( user, Cmd.none )
    Name name ->
      ( { user | name = name }, Cmd.none )
    Email email ->
      ( { user | email = email }, Cmd.none )

view : User -> Html Msg
view user =
  div []
    [ h2 [] [ text user.name ]
    , h2 [] [ text user.email ]
    , div []
        [ h2 [] [ text "Signup Form" ]
        , label [ for "name-field" ] [ text "Name:" ]
        , input [ id "name-field", type_ "text", placeholder "Name", onInput Name ] []
        , label [ for "email-field" ] [ text "Email:" ]
        , input [ id "email-field", type_ "text", placeholder "Email", onInput Email ] []
        , button [ onClick Signup ] [ text "signup" ]
        ]
    ]

subscriptions : User -> Sub Msg
subscriptions user =
  Sub.none

-- decoder

idDecoder =
  ( Decode.field "id" Decode.int )

nameDecoder =
  ( Decode.field "name" Decode.string )

emailDecoder =
  ( Decode.field "email" Decode.string )

userDecoder =
  Decode.map3 User nameDecoder emailDecoder idDecoder

userDataDecoder =
  ( Decode.field "data" userDecoder )

-- request

getUserUrl : String
getUserUrl =
  "http://localhost:4000/api/users/1"

getUser : Http.Request User
getUser =
  Http.get getUserUrl userDataDecoder

sendRequest : Cmd Msg
sendRequest =
  Http.send LoadUserData getUser

File: lib/elm_elixir_phx/web/templates/page/index.html.eex

<div class="jumbotron">
  <h2><%= gettext "Welcome to %{name}!", name: "Phoenix" %></h2>
  <p class="lead">A productive web framework that<br />does not compromise speed and maintainability.</p>
</div>

<div id="elm-main"></div>

<div class="row marketing">
  <div class="col-lg-6">
    <h4>Resources</h4>
    <ul>
      <li>
        <a href="http://phoenixframework.org/docs/overview">Guides</a>
      </li>
      <li>
        <a href="https://hexdocs.pm/phoenix">Docs</a>
      </li>
      <li>
        <a href="https://github.com/phoenixframework/phoenix">Source</a>
      </li>
    </ul>
  </div>

  <div class="col-lg-6">
    <h4>Help</h4>
    <ul>
      <li>
        <a href="http://groups.google.com/group/phoenix-talk">Mailing list</a>
      </li>
      <li>
        <a href="http://webchat.freenode.net/?channels=elixir-lang">#elixir-lang on freenode IRC</a>
      </li>
      <li>
        <a href="https://twitter.com/elixirphoenix">@elixirphoenix</a>
      </li>
    </ul>
  </div>
</div>
$ elm package install elm-lang/http
To install elm-lang/http I would like to add the following
dependency to elm-package.json:

    "elm-lang/http": "1.0.0 <= v < 2.0.0"

May I add that to elm-package.json for you? [Y/n] y

Some new packages are needed. Here is the upgrade plan.

  Install:
    elm-lang/http 1.0.0

Do you approve of this plan? [Y/n] y
Starting downloads...

  ● elm-lang/http 1.0.0

Packages configured successfully!
iex(1)> alias ElmElixirPhx.Accounts, as: Accounts
ElmElixirPhx.Accounts
iex(2)> Accounts.create_user(%{name: "hoge", email: "hoge@test.com"})
[debug] QUERY OK db=4.0ms
INSERT INTO "accounts_users" ("email","name","inserted_at","updated_at") VALUES ($1,$2,$3,$4) RETURNING "id" ["hoge@test.com", "hoge", {{2017, 3, 25}, {7, 15, 55, 261888}}, {{2017, 3, 25}, {7, 15, 55, 268724}}]
{:ok,
 %ElmElixirPhx.Accounts.User{__meta__: #Ecto.Schema.Metadata<:loaded, "accounts_users">,
  email: "hoge@test.com", id: 1, inserted_at: ~N[2017-03-25 07:15:55.261888],
  name: "hoge", updated_at: ~N[2017-03-25 07:15:55.268724]}}
iex(3)> Accounts.get_user!(1)
[debug] QUERY OK source="accounts_users" db=1.7ms
SELECT a0."id", a0."email", a0."name", a0."inserted_at", a0."updated_at" FROM "accounts_users" AS a0 WHERE (a0."id" = $1) [1]
%ElmElixirPhx.Accounts.User{__meta__: #Ecto.Schema.Metadata<:loaded, "accounts_users">,
 email: "hoge@test.com", id: 1, inserted_at: ~N[2017-03-25 07:15:55.261888],
 name: "hoge", updated_at: ~N[2017-03-25 07:15:55.268724]}
$ mix phx.server

Footer

none

人気の投稿