Elixir Phoenix 試食
はじめに
ここ1か月にたくさんの言語仕事で触っている
PHP(なぜか今まで触ったことなかった)、Golang、Python、Elixir、Rails、Scala、node(5年ぶり2回目) など
1か月程度なので知見がほとんどないけど、知識をまとめていく
作るもの
APIサーバのサンプル(評価)プロジェクト
Redis、MySQL、MongoDBなどのCRUDテスト
言語の評価
Docker構築
Docker構築
最初は OSから自分で入れた
とりあえずUbuntuで
FROM ubuntu:17.04 RUN apt-get update RUN apt-get install -y sudo wget RUN apt-get install -y language-pack-ja RUN wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb && dpkg -i erlang-solutions_1.0_all.deb RUN apt-get update RUN apt-get install -y esl-erlang RUN apt-get install -y elixir ENV LANG ja_JP.UTF-8 ENV LANGUAGE ja_JP:ja ENV LC_ALL ja_JP.UTF-8 # Set app env ENV HOME /root WORKDIR /usr/local/src RUN apt-get install -y libssl-dev ncurses-dev RUN apt-get install -y sudo wget git tar bzip2 incron vim nodejs npm RUN apt-get install -y inotify-tools RUN yes | mix archive.install https://github.com/phoenixframework/archives/raw/master/phoenix_new.ez #RUN mkdir /runnet #WORKDIR /runnet ENV PHOENIX_APP_NAME runnet ENV PHOENIX_APP_PORT 4000 ENV PHOENIX_APP_ROOT /${PHOENIX_APP_NAME} WORKDIR /$PHOENIX_APP_ROOT EXPOSE ${PHOENIX_APP_PORT} COPY . $PHOENIX_APP_ROOT WORKDIR /runnet/$PHOENIX_APP_ROOT # Compile phoenix(FOR dev) RUN yes | mix local.hex && yes | mix local.rebar && mix do deps.get, compile RUN mix deps.get RUN mix ecto.create RUN mix ecto.migrate # npm install #RUN npm install #RUN npm install -g brunch # Run Phoenix CMD ["/bin/bash", "-c", "mix phoenix.server"]
色々無駄なものがあるけど、とりあえず Erlang、Elixir、Phoenix と必要なものをセットアップ
結構めんどくさいね。イメージも大きいし
時間あったら Alpineにしようと思っていた
docker-composeも下記のかんじで
version: '2' services: app: build: . environment: MYSQL_ROOT_USERNAME: 'root' MYSQL_ROOT_PASSWORD: 'pass' MYSQL_HOSTNAME: 'mysql' MYSQL_PORT: '3306' ports: - '4000:4000' volumes: - .:/runnet links: - mysql mysql: image: mysql:5.7.10 environment: MYSQL_ROOT_PASSWORD: 'pass' ports: - '3306:3306' volumes: - mysql-data:/var/lib/mysql volumes: mysql-data: driver: local
PhoenixはデフォがPostgresだけど MySQLを入れた
router
scope "/", Runnet do pipe_through :browser # Use the default browser stack get "/", PageController, :index resources "/users", UserController end
controller
defmodule Runnet.UserController do use Runnet.Web, :controller alias Runnet.User def index(conn, _params) do users = Repo.all(User) render(conn, "index.html", users: users) end def new(conn, _params) do changeset = User.changeset(%User{}) render(conn, "new.html", changeset: changeset) end def create(conn, %{"user" => user_params}) do changeset = User.changeset(%User{}, user_params) case Repo.insert(changeset) do {:ok, _user} -> conn |> put_flash(:info, "User created successfully.") |> redirect(to: user_path(conn, :index)) {:error, changeset} -> render(conn, "new.html", changeset: changeset) end end def show(conn, %{"id" => id}) do user = Repo.get!(User, id) render(conn, "show.html", user: user) end def edit(conn, %{"id" => id}) do user = Repo.get!(User, id) changeset = User.changeset(user) render(conn, "edit.html", user: user, changeset: changeset) end def update(conn, %{"id" => id, "user" => user_params}) do user = Repo.get!(User, id) changeset = User.changeset(user, user_params) case Repo.update(changeset) do {:ok, user} -> conn |> put_flash(:info, "User updated successfully.") |> redirect(to: user_path(conn, :show, user)) {:error, changeset} -> render(conn, "edit.html", user: user, changeset: changeset) end end def delete(conn, %{"id" => id}) do user = Repo.get!(User, id) # Here we use delete! (with a bang) because we expect # it to always work (and if it does not, it will raise). Repo.delete!(user) conn |> put_flash(:info, "User deleted successfully.") |> redirect(to: user_path(conn, :index)) end end
model
defmodule Runnet.User do use Runnet.Web, :model schema "users" do field :name, :string field :email, :string field :encrypted_password, :string timestamps() end @doc """ Builds a changeset based on the `struct` and `params`. """ def changeset(struct, params \\ %{}) do struct |> cast(params, [:name, :email, :encrypted_password]) |> validate_required([:name, :email, :encrypted_password]) end end
migrateファイル
defmodule Runnet.Repo.Migrations.CreateUser do use Ecto.Migration def change do create table(:users) do add :name, :string add :email, :string add :encrypted_password, :string timestamps() end end end
ユーザ名、メールアドレス、パスワードを保存する、よくあるログイン認証まわり
細かく追ってないけど Routerでresources を指定すると、複数のメソッドに対応してくれるっぽい
そのあたりが自動的に controllerの index、new、create、show、update、delete等に対応される
MySQLとの連携もばっちりだし、そこまで言語的にも覚えにくくないと思う
Postgresで再構築
いちおうPostgresにて構築してみる。Redisも使う
同じようにDockerイメージ作る
docker-compose.yml
version: '3' services: db: image: postgres ports: - 5432:5432 redis: image: redis:latest ports: - 6379:6379 web: build: . container_name: "ages_app" volumes: - $PWD:/ages ports: - "4000:4000" links: - db - redis environment: DATABASE_USER: postgres DATABASE_PASSWORD: DATABASE_PORT: 5432 DATABASE_HOST: db REDIS_URL: redis://redis:6379 depends_on: - db - redis
今回はゲームサンプルを見据えて DB定義もそれっぽくする
router
scope "/", AgesApp do pipe_through :browser # Use the default browser stack get "/users", UserController, :index get "/user/:id", UserController, :show get "/", PageController, :index end
コントローラーのソース消失!
model
defmodule AgesApp.User do use AgesApp.Web, :model schema "users" do field :name, :string field :os_type, :string field :device_id, :string field :device_name, :string field :os_version, :string field :game_money, :integer field :ios_charge, :integer field :android_charge, :integer field :max_hp, :integer field :level, :integer field :exp, :integer field :max_deck_count, :integer field :max_deck_point, :integer field :max_friend, :integer field :tutorial_progress, :integer field :last_login_at, :naive_datetime timestamps() end @doc """ Builds a changeset based on the `struct` and `params`. """ def changeset(struct, params \\ %{}) do struct |> cast(params, [:name, :os_type, :device_id, :device_name, :os_version, :game_money, :ios_charge, :android_charge, :max_hp, :level, :exp, :max_deck_count, :max_deck_point, :max_friend, :tutorial_progress, :last_login_at]) |> validate_required([:name, :os_type, :device_id, :device_name, :os_version, :game_money, :ios_charge, :android_charge, :max_hp, :level, :exp, :max_deck_count, :max_deck_point, :max_friend, :tutorial_progress, :last_login_at]) end end
Migrate、Seedのコードも消失!
Postgresは上手くいったが、その後 Redisにセッション保存をしようと思い、Redis用のライブラリを探す
exredisが有名らしいので調べたところ、キー取得時にエラーがわからないという酷い実装のため変更
redixをパッケージに追加するとDependencyエラー
色々なバージョンを試してみたが、簡単にはいかない
その後、うちのメンバーに新人の参入可能性が高まったため、一度JavaScript覚えさせたいのと
最近のnodeは、ES6に対応したり、以前よりも複数人開発がしやすくなっているので
nodeの調査に変更した