Rails経験者に贈るPhoenix入門

December 12, 2019

はじめに

初 AdventCalendar です!よろしくお願いします!

昨日の記事は@takamizawa46 さんでよわよわな新卒が運営する勉強会の開催までの経緯と得られた知見でした。

僕は Phoenix はインターン先で使っていて慣れていますが、Rails は勉強中の身分です。 (まあ、Phoenix もまだまだ完璧に使いこなせていないのですが・・・)

何か間違えてる部分やこっちの方がええで!ってのがありましたら、ぜひぜひコメントお願いします。

(特に Rails に関しては初学者なのでよろしくお願いします・・・)

Phoenix とは

関数型言語 Elixir の Web フレームワークに当たります。 「そもそも Elixir が Ruby のインスパイアを受けていると言われており、関数型言語の中では学習コストが低い」とどこかで読んだ気がします。

福岡の Elixir コミュニティ fukuoka.ex さんでは以下のように Phoenix の紹介がなされています。(勝手に引用ごめんなさい)

Phoenix は、Web アプリケーションの世界では、最もメジャーな「Ruby on Rails」を作っていたメンバーによって開発された、大量アクセスと高速分散の捌きが得意な Web アプリケーションフレームワークで、Rails 同等の高度な Web アプリ開発を、とても気軽に行えます(中でも、WebSocket 性能は、あらゆる言語の FW 中でも最速) — 福岡の Elixir コミュニティ fukuoka.ex さんはこちら

ということで構成が酷似しています。

Elixir / Phoenix を扱う企業も増えており、Rails を普段使っている方にとってはハードルも低いので一緒に Phoenix を勉強しておくと一石二鳥なわけですね

最近では Qiita をはじめ多くの方が Phoenix の扱い方を記事にしていますので日本語でも学習しやすくなっています。

また、先ほどのfukuoka.exさんや昨日の記事の@takamizawa46 さんの運営する清流 elixirなど全国で多くの勉強会が開かれています。 (初学者の方の参加も OK のものが多く、またリモートでの参加も可能になっている勉強会も多くあります。)

この記事があなたの Phoenix ライフのきっかけになると嬉しいです。

Elixir 勉強してみたいって方は

今回のこの記事内では Elixir の文法などは扱いません。 純粋にフレームワークとして Rails と Phoenix の扱い方を比較します。

この記事を読んで Elixir を勉強してみようという気になった方は以下を参照してみてください。

公式ガイド(英語多め) https://elixir-lang.org/getting-started/introduction.html

Elixir school(日本語多めなのでおすすめ) https://elixirschool.com/ja/

Rails と Phoenix の比較

新規アプリケーションの作成〜サーバーの立ち上げ

まずはアプリケーションの作成です。

$ rails new sample
$ mix phx.new sample

そしてサーバーを立ち上げます。

$ cd sample
$ rails server  #サーバーの立ち上げ
$ cd sample
$ mix ecto.create #DBの作成
$ mix phx.server  #サーバーの立ち上げ

Rails ではこの画面 image.png

Phoenix ではこの画面 image.png が表示されれば成功です!

各ファイルの対応関係とその置き位置

ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ Rails Phoenix
controller app/controllers lib/{アプリケーション名}_web/controller
view app/views lib/{アプリケーション名}_web/templates
router db/routes.rb lib/{アプリケーション名}_web/router.ex
model app/models lib/{アプリケーション名}/ 内

Controller

Controller に関する大きな違いは Phoenix では render を記述する必要がある点です。

Rails でも部分テンプレートを用いる時に使う render という関数と働きは似ています。

class PageController < ApplicationController

   def index
   end
 end
defmodule SampleWeb.PageController do
  use SampleWeb, :controller

  def index(conn, _params) do
    render(conn, "index.html")
  end
end

Rails では上記のように記述するだけで page/index.html がレンダリングされますが、Phoenix の場合は明示的に render 関数でレンダリングするものを指定する必要があります。

view

MVC の V ですね。

前述のように Rails の.html.erb のファイルは Phoenix では lib/{アプリケーション名}_web/template に格納されています。

そして、Phoenix の lib/{アプリケーション名}_web/views では Template の中で用いる関数が定義されます。

defmodule SampleWeb.PageView do
  use SampleWeb, :view

#template/page内のTemplateで使用する関数を定義する

end

ここまでわかっておけば十分でここからの話は少しややこしいので飛ばしてもらっても大丈夫です。

前述のように、controller で render を呼び出すことで指定の Template を呼び出すのですが、 これは例えば Sample(← アプリ名)の PageController から呼び出した場合、最終的に Sample.PageView 内に定義されている(実際は use SampleWeb, :view で定義されている)render関数を呼ぶことになります。

Sample.PageView 内に定義されている render 関数は lib/{アプリケーション名}_web/template/page 内に存在する指定の Template をレンダリングします。

このような流れになっているため、Template は Sample.PageView 内でレンダリングされる訳なので Sample.PageView に関数を定義すると、template/page 内の Template で使用できるわけですね。

この辺り詳しく知りたい方はこちらの記事が非常に参考になります。

Template 自体は Rails と同様に <%= %> のなかで Elixir の関数/変数が使用できます。

model

model は DB をいじいじする部分です。

Rails では ActiveRecord がやってくれている部分を Phoenix では自分で記述しなければいけない部分があります。

Phoenix では AcriveRecord で言う create などのメソッドは全て自分で定義する必要があります。 また、DB の定義も Rails よりもしっかりと書かなければいけません。

実際は generator を使えばこれらを含めて作成してくれるので 1 から自分で作るということは少ないという印象です。

Rails の model は AcriveRecord のサブクラスとしてファイルを作成するだけで、これらの手間を省いているのだと思います。 (AcriveRecord 便利ですね)

以下はそれらのサンプルコードです

defmodule Sample.Accounts.User do
  use Ecto.Schema
  import Ecto.Changeset
  alias Sample.Accounts.Credential

  schema "users" do
    field :name, :string
    has_one :credential, Credential
    has_many :boards, Trellu.Display.Board
    has_many :activity_logs, Trellu.Activity.Activity_log
    many_to_many :teams, Trellu.Group.Team, join_through: "users_teams",on_replace: :delete
    timestamps()
  end

  @doc false
  def changeset(user, attrs) do
    user
    |> cast(attrs, [:name])
    |> validate_required([:name])
    |> unique_constraint(:name)
  end
end

スキーマ定義は field でスキーマを記述します。 また、has_manyなどでテーブル同士の紐付けを示します。(Rails の model に記述する has_many と役割は同じです)

DB 定義のファイルにはスキーマ定義の他、changesetと呼ばれる validation に用いる関数の定義を行う必要があります。 これは Rails で model 内に記述する validation と役割は同じです。

defmodule Sample.Accounts do
  @moduledoc """
  The Accounts context.
  """

  import Ecto.Query, warn: false
  alias Sample.Repo

  alias Sample.Accounts.{User, Credential}

  @doc """
  Returns the list of users.

  ## Examples

      iex> list_users()
      [%User{}, ...]

  """
  def list_users do
    User
       |> Repo.all()
       |> Repo.preload(:credential)
  end

  @doc """
  Gets a single user.

  Raises `Ecto.NoResultsError` if the User does not exist.

  ## Examples

      iex> get_user!(123)
      %User{}

      iex> get_user!(456)
      ** (Ecto.NoResultsError)

  """
  def get_user!(id) do
    User
    |> Repo.get!(id)
    |> Repo.preload(:credential)
  end

  @doc """
  Creates a user.

  ## Examples

      iex> create_user(%{field: value})
      {:ok, %User{}}

      iex> create_user(%{field: bad_value})
      {:error, %Ecto.Changeset{}}

  """
  def create_user(attrs \\ %{}) do
    %User{}
    |> User.changeset(attrs)
    |> Ecto.Changeset.cast_assoc(:credential, with: &Credential.changeset/2)
    |> Repo.insert()
  end

  @doc """
  Updates a user.

  ## Examples

      iex> update_user(user, %{field: new_value})
      {:ok, %User{}}

      iex> update_user(user, %{field: bad_value})
      {:error, %Ecto.Changeset{}}

  """
  def update_user(%User{} = user, attrs) do
    user
    |> User.changeset(attrs)
    |> Ecto.Changeset.cast_assoc(:credential, with: &Credential.changeset/2)
    |> Repo.update()
  end

  def update_for_slack_user(%User{} = user, attrs) do
    user
    |> User.changeset(attrs)
    |> Ecto.Changeset.cast_assoc(:credential, with: &Credential.changeset_for_slack/2)
    |> Repo.update()
  end

  @doc """
  Deletes a User.

  ## Examples

      iex> delete_user(user)
      {:ok, %User{}}

      iex> delete_user(user)
      {:error, %Ecto.Changeset{}}

  """
  def delete_user(%User{} = user) do
    Repo.delete(user)
  end

  @doc """
  Returns an `%Ecto.Changeset{}` for tracking user changes.

  ## Examples

      iex> change_user(user)
      %Ecto.Changeset{source: %User{}}

  """
  def change_user(%User{} = user) do
    User.changeset(user, %{})
  end

これが Rails では ActiveRecord が行ってくれている部分の定義になります。

※以降の説明では DB 定義のファイルを DB 定義ファイル、DB 関連の関数の定義ファイルを DBUtil ファイルと呼ぶことにします。

各種 generator の働き ###全部一気に作成

$ rails generate scaffold Post title:string content:text
$ mix phx.gen.html Blog Post posts title:string content:string

諸々が全部一気に作成されます。

Rails は WebAPI 作成の用途でも上の rails generate scaffold で対応できるようですが、 Phoenix は WebAPI 作成用だと

$ mix phx.gen.json Blog Post posts title:string content:string

を使う必要があります。

model

model に関しては、前述の違いのこともあってか厳密に同じ役割をする generator はありませんでした。

$ rails generate model User name:string age:integer

Rails に関しては model、migration が生成されます。

$ mix phx.gen.context Accounts User users name:string age:integer

Phoenix には phx.gen.model なるものは現在ありません。 似たような働きをするものとしてこの phx.gen.context があります。 migration と DB 定義ファイル、DBUtil ファイルが生成されます。

migration

$ rails generate migration マイグレのクラス名

...(マイグレファイルをいじる)

$ bundle exec rake db:migrate
$ mix ecto.gen.migration マイグレのクラス名

...(マイグレファイルをいじる)

$ mix ecto.migrate

これによって migration が生成&実行されます。

また、Rails を勉強していて驚いたのですが、Phoenix では Rails のようにマイグレのクラス名でコードを自動的に生成まではしてくれません。

$ rails generate migration CreateBlogs
$ mix ecto.gen.migration CreateBlogs

Rails ではこのような場合 Blogs テーブルを作成するような migration が生成されます。

しかし、Phoenix の場合このようにクラス名を指定しても、中身の書かれていない migration が作成されます。 そのため自分で migration ファイルを編集する必要があります。

Phoenix にはあるが対応するものが Rails に(多分)存在しないもの

$ mix phx.gen.schema Post posts title:string content:string

これでは migration と DB 定義ファイルが生成されます。

Rails でいう generate model を行いたい時は phx.gen.schema に加え。自分で DBUtil ファイルを作成する必要があります。

終わりに

どうだったでしょうか。 Rails には優秀な ActiveRecord があるので少し Phoenix では手間が増える部分がありましたが、かなり似ている部分が多かったと思います。

これを機に Elixir / Phoenix を学んでみてはいかがでしょうか。 ここまでしっかり読んでくださったあなたなら Elixir を少し学べばすぐに Phoenix をスタートできるはずです!

このエントリーをはてなブックマークに追加