YouTube Data API (v3)でOAuthを使ってAPIを使う準備 その1
どうも、靖宗です。
最近YouTubeのAPIを触ることがあってメモ代わりに記事にします。
OAuthあたりはよく忘れるので・・・
オープンな情報ならAPI Keyを作成するだけで簡単なのですが、ユーザーに紐付いた操作などをする場合はOAuthの認証が必要です。
今回はAPI Keyの方は言及しませんがもしかしたらそのうち書くかも。
Google Cloud Platformで下準備
まずはGoogle Cloud Platform(GCP)のダッシュボードからプロジェクトを作成し、その元にアプリケーションを登録します。
アクセスするとログイン画面がでるので、APIで操作したいアカウントでログインします。
プロジェクト作成
プロジェクト毎に認証情報は異なる方が都合が良いのでプロジェクトを作成します。
左上の「プロジェクトの選択」から作成します。
プロジェクト名は各自好きに設定して下さい。
「作成」というボタンを押すと、ダッシュボードに戻されて右上でなんか進みます。
暫くすると「プロジェクト「○○」を作成」と表示されればプロジェクトの作成が完了していると思います。
OAuthクライアント
お次にアプリケーションの登録を行います。
左上のメニューマークから
「APIとサービス」→「認証情報」を選択します。
プロジェクトが選択されてないと「このページを表示するには、プロジェクトを選択してください。」という表示が出ると思うので、右上の「プロジェクトの選択」から先ほど作成したプロジェクトを選択します。
プロジェクトが選択されていれば、「認証情報」という窓が表示されていると思うので、認証情報を作成していきます。
今回はOAuthを利用するのでOAuthクライアントIDを選択します。
同意画面の設定
進むと「OAuth クライアント ID を作成するには、まず同意画面でサービス名を設定する必要があります。」と警告されていると思います。
ユーザーがOAuthを利用する際に「承認しますか?」と聞かれるページに表示されている情報を設定して下さいという意図です。確かにそれがないと始まらない。
同意画面の設定ボタンを押して設定しときます。
設定画面が表示されますので、必要な情報を入力します。
とりあえず動かしたいって方はおそらく
- アプリケーション名
- サポートメール
だけ設定しておけばローカル環境では動きそうです。
もしサービスを運用するドメイン(URL)なども決まっている場合は「承認済みドメイン」の箇所を設定しておいて下さい。
たぶんここを設定してないとなにがしかのトラブルに巻き込まれます(不正な認証情報だとか云々?しらんけど)。
実際にOAuthを利用するサービスにする場合はちゃんとポリシーなんかのアドレスも設定して下さい。
設定できたら「保存」を押して同意画面の設定を完了します。
クライアントIDの作成
同意画面の設定を完了できればようやくクライアントIDの発行ができます。
今回はWebアプリケーションを想定して一番上の「ウェブアプリケーション」を選択します。
承認済みのJavaScript生成元
ちょっと画像に入れ忘れてたんですが、ローカル環境で開発する際は「承認済みのJavaScript生成元」に"http://localhost"や"http://127.0.0.1:8000"などを設定しておかないとGoogleにはじかれます。
承認済みのリダイレクト URI
また、認証情報を取得してそのまま情報をアプリケーションのDBに格納したいというケースも考えられますが、そういった際にリダイレクトされるURL(URI)を「承認済みのリダイレクト URI」に設定しておきます。
例えば、承認済みのリダイレクト URIを"http://localhost/oauth/"に設定しておくと、OAuthの認証が終わった後に"http://localhost/oauth/?code=xxxxxxxxxxxxxx"へリダイレクトされます。
これを利用してアプリケーションは"アプリケーションのURL/oauth/?code=[認証トークン]"というルーティングに処理を書いておくことで認証トークンを取得することができます。
Windows 10 Home (64bit)でDockerの環境を構築する
どうも、靖宗です。
Docker for Windowsが出現してからWindowsでDockerを利用するのが簡単になりました。
とはいえ自分の利用しているWindowsがHomeでDocker for Windowsが利用できない人もいるんじゃないでしょうか?ぼくもその一人です。
(アップグレードしろよという突っ込みは受け付けません)
どうやら広大なネットの海で調べたところ、Windows 10 HomeではDocker Toolboxで一応動かせるようなので、そちらのセットアップをしていきたいと思います。
基本的に下記の記事を参考にさせていただきました。
インストーラーを手に入れる
なんてことないのですが、Docker公式としてはあまりToolboxの方を利用して欲しくないのか分かりにくい所にインストーラーがある気がします。
DockerToolboxのリリース一覧から取得しろとのことです。
今回は「18.09.3」というバージョンをダウンロードしましたが、未来にこの記事を見ている人はより新しいバージョンのインストーラーがあると思います。
(上から見てって「Assets」というところにexeファイルがあるのを選んで下さい。)
ダウンロードできたら基本的にはインストーラーに従って進めて行きます。
インストール
自分はこんなかんじですが、Gitがインストールされてない方などはチェックボックスがついてそうです。
要らないことないのでインストールしておくことをお勧めします。
自分はデスクトップが汚れるのは許せないので「Create a desktop shortcut」はチェックを外しておきます。
この辺はご自由にどうぞ。
DockerToolboxを起動
最後まで順調に進めば「Docker Quickstart Terminal」というショートカットが表示されると思います。
ダブルクリックで起動!
無事くじらAAが表示されればセットアップは完了です。
試しにコンテナを起動してみる
試しにニンジンXのコンテナを起動してみます。
Dockerの基本的な利用方法については割愛しますが、nginxというコンテナを起動します。
docker run --name ninjin -d -p 8080:80 nginx
基本的にインストールし終わってパスが通ってる状態であればDocker Quickstart Terminalのコンソールからコマンドを打たなくてもPowerShellやcmd.exeでdockerコマンドが通ると思います。
VirtualBox上のVMのポートフォワーディング
Docker for Windowsならたぶんここでブラウザに「http://localhost:8080」でnginxが表示されると思うのですが、今回はDockerToolboxを利用しているのでもう一手間要ります。
VirtualBoxに「default」という仮想マシンが作成されていると思いますが(環境によっては変わる?)、このVM上でDockerが動いています。
なので普通にコンテナを起動すると、ポートフォワーディングはdefaultという仮想マシンへ渡されます。
なので、なにも設定していないとWindows側からは見れません。
今回はnginxをdefaultというVMには8080に渡しているので、defaultからWindowsへ8080に渡す設定を追加します。
VirtualBoxの仮想マシンなのでVirtualBoxで設定します。
defaultのポートフォワーディングが設定できれば、Windowsのブラウザ上からもnginxへとアクセスできるようになります。
ブラウザ上で「localhost:8080」にアクセスすれば、nginxのデフォルト表記が確認できると思います。
FusionPCBで基板製作+部品実装(PCB+PCBA)を発注する一連の流れまとめ
どうも、靖宗です。
FusionPCBで基板製作から部品調達+部品実装までしてもらって楽しようという記事です。
防備録としての意味合いも込めて記事にしておきます。
部品を国内から送る方法は需要があれば別途記事にします。
※2019/06/12 現在のまとめです。ちょいちょい変わったりすると思うので、お気を付けて。
※2019/06/13 追記
準備
Gerberデータ作成
一応基板の設計は終わってるものとします。
Gerberデータの作成方法はFusionPCB公式サイトで詳しく記載がありますのでそちらをご覧下さい。
デザインルールなどをチェックするのをお忘れなく。
BOMデータ作成
基板のみ作りたいって人はこの項目を飛ばして貰って構いません。
実装の際に部品表が必要になりますので、そちらを作成していきます。
部品表のテンプレートがあるので、そちらをダウンロードして作成してください。
(リンク切れの場合はFusionPCBのサイトから直接ダウンロードしてください。)
Eagleを使っている方はPartlistなどを出力して部品表を作成するのが良いと思います。
一応PartlistからBOMの基礎となるデータを出力するPythonスクリプトを置いておきますので自己責任でご利用ください。
https://www.axfc.net/u/3985652&key=deus
実装情報(任意)
※この項目は現在調査中ですので参考程度でお願いします。
実装図などをアップロードできるようになったみたいなので、この辺の情報があるとFusionPCBの人達は楽かも。
連絡回数を少なくして迅速に実装してもらうなら準備しておいた方が良いかもしれません。
Eagleで回路を設計しているなら
- ボード図(PDF, brdを印刷→PDFなどで対応、もしくはImageなど?)
- 配置情報(Partlist)
ぐらいあると丁寧かも。
見積
Gerberデータアップロード
GerberデータとBOMデータができていればウェブ上から見積がすぐ取れます。
FusionPCBのPCBのページからGerberデータをアップロードします。
アップロードできれば、念のためガーバービューアで閲覧しておく事をオススメします。
基板の作成枚数が10以外の場合はパラメータのところで変更してください。
BOMデータアップロード
各パラメータを入力し終えたら「実装サービス」という欄をクリックし、実装情報やBOMデータなどをアップロードします。
実装枚数が10以外の場合は、「実装枚数」と書かれた箇所を変更してください。
BOMデータが問題無くアップロードできれば見積が完了します。
もしFusionPCB側でウェブ見積できない部品(実装実績がない部品)があれば、部品見積の一番上に表示が出てきますので、部品を調達するために必要な情報を「購入リンク」から入力しましょう。だいたいDigiKeyにある部品なら調達してくれるのでDigiKeyのリンクを張っておけば問題無いかと思います。
部品を国内から送るとき
この場合はカスタマーサポートへの連絡が必須です。
(調達価格と実装価格が変わるので、特殊なBOMを作って貰うのにメールでのやりとりが必要です。)
迅速丁寧に対応して貰えるので恐れずに fusion.jp@seeed.cc へ連絡を送りましょう。
この辺はまた別途詳細をまとめます。
カートに入れる+購入手続き
上記までが問題無く終わっていれば上部に価格が反映されているので、「カートに追加」ボタンでカートに追加します。
カートに追加した以降は、日本語で案内がでているので大丈夫だと思います。
(PayPal支払とクレジットカード支払が使えたと思います。)
決済後
担当者とのやりとり
購入手続きが完了すると、担当者から連絡があると思います。
日本語で丁寧に対応してくださるので、指示に従って必要な追加データや修正点などあればメールベースでやりとりすることになります。
納期
おおよそ25日前後と考えておいた方が良さそうです。
これは勝手なイメージですが、おおよそ基板製造に2営業日程度、実装に3営業日程度なのですが、部品調達にどうしても時間がかかっている(税関などの影響?最近は厳しいみたいです)ようです。
なので、もし実装を急いでいる場合は事前に部品を調達しておいて、国内から部品を発送したほうが実装期間の短縮には繋がるかもしれません。その分手間は増えますが。
部品を送付する際の手順はまた気が向けば。
Phoenix入門 (第15章 Custom Errors)
どうも、靖宗です。
とりあえず主な項目はコレで最後です。
残るはテストの話とデプロイの話が残っていますが、残ってる話の方が重要そうな気がしますね。
今回はたぶん短めです。
PhoenixというかElixirの例外処理のお話に近いかも。
Custom Errors
Viewsの項目でErrorViewに関しては一回やりました。
基本的に400とか500のエラーはテンプレート(404.html.eex
とか)を用意しておけばPhoenixのコントローラがよしなにしてくれます。
ビューのエラーはこのErrorView
で受けるとして、内部処理のエラーはPhoenixでは例外処理などが少々利用されているようです。
Custom Errors
Elixirの標準機能として、例外を定義するためのdefexception
を利用してエラー処理を行っています。
エラーを実装したいモジュール内部にモジュールを更に定義し、内部でdefexception
を定義するように実装するそうです。
router.ex
が利用しているPhoenix.Router
の内部実装が例としてドキュメントに記載されています。
defmodule Phoenix.Router do defmodule NoRouteError do @moduledoc """ Exception raised when no route is found. """ defexception plug_status: 404, message: "no route found", conn: nil, router: nil def exception(opts) do conn = Keyword.fetch!(opts, :conn) router = Keyword.fetch!(opts, :router) path = "/" <> Enum.join(conn.path_info, "/") %NoRouteError{message: "no route found for #{conn.method} #{path} (#{inspect router})", conn: conn, router: router} end end ... end
Elixirの例外処理の記事では取り扱ってなかったのですが、defexception
を宣言したモジュール内でdef exception
として関数を実装すると、エラー処理の関数になるようです。
(詳細はElixirのExceptionのビヘイビアを確認)
Plugを実装する際にはPlug.Exception
というプロトコルがあるようなのでそちらを利用するのが良いかと思います。
例なども示されてますが、特殊ケースな気がするので、また利用機会があった際に追記したいと思います。
今回は短めですがこんなもんで。
Phoenix入門 (第14章 Mix Tasks その2)
どうも、靖宗です。
引き続きMix Tasksの項目を見ていきます。
今回はEcto関連のコマンドから。
Ecto Specific Mix Tasks
このEcto周辺のコマンドですが、当たり前ですが--no-ecto
とかしてない場合が対象です。
mix ecto.create
データベースの作成を行うコマンドです。デフォルトならconfigやlib/hello/repo.ex
に記載したデータベースに作成するはずです。
デフォルトでいいなら引数は要らないのでシンプルです。
$ mix ecto.create The database for Hello.Repo has been created.
別の名前で定義してたりするなら、-r
オプションでモジュール名を指定します。
$ mix ecto.create -r OurCustom.Repo The database for OurCustom.Repo has been created.
PostgreSQLのユーザー周りの説明は割愛します。
(要望があれば書きますが)
ecto.drop
純粋にecto.create
の逆だと思えば良さそうで、指定したデータベースからデータを消去します。
使い方もほぼcreate
と同じで、デフォルトなら引数は不要。
$ mix ecto.drop The database for Hello.Repo has been dropped.
-r
でモジュール名を指定することも可能。
$ mix ecto.drop -r OurCustom.Repo The database for OurCustom.Repo has been dropped.
mix ecto.gen.repo
データストアが1個じゃないケースもあると思います。そういうときにこのecto.gen.repo
で新しいリポジトリを作成できるそうです。
たとえば、デフォルトのリポジトリがHello.Repo
で、別のリポジトリがOurCustom.Repo
であるばあい、下記のようにして作成するようです。
$ mix ecto.gen.repo -r OurCustom.Repo * creating lib/our_custom * creating lib/our_custom/repo.ex * updating config/config.exs Don't forget to add your new repo to your supervision tree (typically in lib/hello.ex): worker(OurCustom.Repo, [])
* updating config/config.exs
とあるので、configも修正されています。
... config :hello, OurCustom.Repo, database: "hello_repo", username: "user", password: "pass", hostname: "localhost" ...
これは各環境で合わせて下さい。
場合によってはdev.exs
やprod.exs
に記載してもいいかもしれません。
ファイルの生成などは自動でやってくれましたが、リポジトリのワーカーをSupervisorで管理する必要があります。
ドキュメントではlib/hello.ex
を編集、と書いてありますがたぶんlib/hello/application.ex
の間違いです。
... children = [ # Start the Ecto repository Hello.Repo, # Start the endpoint when the application starts HelloWeb.Endpoint, # Starts a worker by calling: Hello.Worker.start_link(arg) # {Hello.Worker, arg}, # Here you could define other workers and supervisors as children OurCustom.Repo ] ...
これで大丈夫そうです。
mix ecto.gen.migration
マイグレーションする準備をするのがecto.gen.migration
です。こういうフレームワークにはよくある奴だと思います。
Contextの章でもいじったファイルを作成するやつで、マイグレーションも実際の所はElixirのスクリプトを実行して行っていますのでそのスクリプトを生成するコマンドということです。
基本的に何かのコマンドで自動生成されるのでそこまで出番があるように思えませんが、スキームの変更などをした場合には手動で作成する必要がありそうです。
コマンドにマイグレーションファイル名を引数として渡します。
mix ecto.gen.migration add_comments_table * creating priv/repo/migrations * creating priv/repo/migrations/20150318001628_add_comments_table.exs
マイグレーション用のファイルがpriv/repo/migrations
に作成されます。おそらくタイムスタンプ_指定した名前.exs
というファイルが作成されます。
ファイルの中身は下記のようになっているそうです。
defmodule Hello.Repo.Migrations.AddCommentsTable do use Ecto.Migration def change do end end
基本的にコマンドで生成した場合は特に何も書かれていないはずです。
このchange/0
に記載された仕様をもとに、データベースを編集したりロールバックしたりします。
たとえば、comments
というテーブルを作成し、body
、word_count
というフィールド+タイムスタンプがあるスキームを追加したとします。
このとき、マイグレーションファイルは下記のように編集します。
... def change do create table(:comments) do add :body, :string add :word_count, :integer timestamps() end end ...
ちなみに、今はcreate
を使いましたが、スキームを追加するのではなく変更する場合などはalter
を利用します。
基本的な使い方は上記の通りですが、デフォルトではないリポジトリを対象としたマイグレーションのファイルは、ecto.create
などと同様-r
オプションでリポジトリ名を指定してやる必要があります。
$ mix ecto.gen.migration -r OurCustom.Repo add_users * creating priv/repo/migrations * creating priv/repo/migrations/20150318172927_add_users.exs
mix ecto.migrate
マイグレーションファイルを作成したらすることは決まっています。マイグレーションです。
何も考えずにmix ecto.migrate
することが多いのではないでしょうか。
$ mix ecto.migrate [info] == Running Hello.Repo.Migrations.AddCommentsTable.change/0 forward [info] create table comments [info] == Migrated in 0.1s
そこまで開発にかかわってこない可能性がありますが、mix ecto.migrate
をするとデータベース上のschema_migrations
というテーブルにマイグレーション日時のタイムスタンプが作成されるようです。
hello_dev=# select * from schema_migrations; version | inserted_at ----------------+--------------------- 20150317170448 | 2015-03-17 21:07:26 20150318001628 | 2015-03-18 01:45:00 (2 rows)
あとで見てみますが、ecto.rollback
などでロールバックするときはこの辺の情報を利用してロールバックするようです。
基本的に存在している全てのマイグレーションファイルを実行してマイグレートするコマンドですが、一応実行する個数を指定出来るようです(順番はタイムスタンプ依存?)
-n
か--step
のオプションを使用して実行するマイグレーションの個数を指定します。
$ mix ecto.migrate -n 2 [info] == Running Hello.Repo.Migrations.CreatePost.change/0 forward [info] create table posts [info] == Migrated in 0.0s [info] == Running Hello.Repo.Migrations.AddCommentsTable.change/0 forward [info] create table comments [info] == Migrated in 0.0s
一応下記も同様
$ mix ecto.migrate --step 2
-v
のオプションを使用すると、指定したタイムスタンプのマイグレーションファイルのみを実行できるようです。
$ mix ecto.migrate -v 20150317170448
--to
も同じ働き。
$ mix ecto.migrate --to 20150317170448
mix ecto.rollback
ロールバック、詰まり先祖返りできます。スキーム変更したけどやり直したい!とかいう場合に活用できそうです。
それもコレもマイグレーションファイルにきっちり変更の仕様が記載されているおかげでしょう。
$ mix ecto.rollback [info] == Running Hello.Repo.Migrations.AddCommentsTable.change/0 backward [info] drop table comments [info] == Migrated in 0.0s
何も指定しなければ全部戻ってしまいそうです。
rollback
はmigration
と同様のオプションが取れます。例えば-n
でn個マイグレーションファイルを遡る、などでしょうか。
こちらはオプションを大いに活用しそうです。
Creating Our Own Mix Tasks
おおよそ見ていったmixコマンドで事足りそうですが、「もうちょっとやってくれよ」「ここ毎回同じ事してる」という場合はかゆいところに手が届くmixコマンドが欲しくなると思います。
そういった需要に応えるがごとく、mixコマンドを作成できるそうです。
まずはlib/
フォルダにmix/tasks
ディレクトリを作成していきます。
$ mkdir -p lib/mix/tasks
試しにhello.greeting.ex
というファイルを作成するとします。
defmodule Mix.Tasks.Hello.Greeting do use Mix.Task @shortdoc "Sends a greeting to us from Hello Phoenix" @moduledoc """ This is where we would put any long form documentation or doctests. """ def run(_args) do Mix.shell.info("Greetings from the Hello Phoenix Application!") end # We can define other functions as needed here. end
慣習なのか知りませんが、基本的にElixirはディレクトリ名をモジュール名に適応するようです。
(そもそもコンパイル時にチェックされる?すみません、この辺は勉強不足です。)
なので、lib/
ディレクトリ以下のmix/tasks
ディレクトリ下にあるhello.greeting.ex
なので、モジュール名はMix.Tasks.Hello.Greeting
になります。
お次にuse Mix.Task
が宣言されています。たぶんこれでmixのコマンドとして機能するのでしょう。
@shortdoc
のモジュール属性ですが、これはmix help
字に表示される説明文です。
@moduledoc
はモジュールのドキュメントです。コレに関しては割愛します。
run/1
はmix
で呼ばれたときに実行される関数です。ここではmix
で実行されたときにシェルに文字列がプリントされるだけのようです。
以上の仕様で、利用する為にはまずコンパイルします。
$ mix compile Compiled lib/tasks/hello.greeting.ex Generated hello.app
コンパイルすると、mix help
にも表示されるようです。
$ mix help | grep hello mix hello.greeting # Sends a greeting to us from Hello Phoenix
実際に利用する場合は、ファイル名で実行するようです。
$ mix hello.greeting Greetings from the Hello Phoenix Application!
run/1
では今のところ文字列を出力するMix.shell.info/1
しか利用していませんが、アプリケーションを実行するにはMix.Task.run/1
を利用すればいいようです。
... def run(_args) do Mix.Task.run("app.start") Mix.shell.info("Now I have access to Repo and other goodies!") end ...
もうちょっといろんな機能を実装するにはMixの仕様をより深掘りする必要がありそうです・・・
Phoenix入門 (第14章 Mix Tasks その1)
どうも、靖宗です。
今回はMix Tasksということでmix phx.hogehoge
のコマンドあたりの説明でしょうか。
Mix Tasks
Phoenix Specific Mix Tasks
Phoenixで使いそうなコマンド一覧があります。
mix local.phx # Updates the Phoenix project generator locally mix phx # Prints Phoenix help information mix phx.digest # Digests and compresses static files mix phx.digest.clean # Removes old versions of static assets. mix phx.gen.cert # Generates a self-signed certificate for HTTPS testing mix phx.gen.channel # Generates a Phoenix channel mix phx.gen.context # Generates a context with functions around an Ecto schema mix phx.gen.embedded # Generates an embedded Ecto schema file mix phx.gen.html # Generates controller, views, and context for an HTML resource mix phx.gen.json # Generates controller, views, and context for a JSON resource mix phx.gen.presence # Generates a Presence tracker mix phx.gen.schema # Generates an Ecto schema and migration file mix phx.gen.secret # Generates a secret mix phx.new # Creates a new Phoenix application mix phx.new.ecto # Creates a new Ecto project within an umbrella project mix phx.new.web # Creates a new Phoenix web project within an umbrella project mix phx.routes # Prints all routes mix phx.server # Starts applications and their servers
細かい機能はPhoenix公式ドキュメントの「MIX TASKS」を見れば良さそうです。
幾つか簡単に取り上げてくれているので、一応目を通しておきます。
mix phx.new
言わずと知れたPhoenixプロジェクトを生成する際に使うコマンドです。
--no-ecto
や--no-webpack
というオプションをつける事でEctoの実装を無くしたり、Webpackの実装を無くしたりできるそうですが、基本的には必要なんじゃないでしょうか。
(APIサーバーとして機能するときはWebpackは不要?)
実際に生成するプロジェクト名はスネークケースがいいっぽいです。
> mix phx.new task_tester
> mix phx.new ../task_tester > mix phx.new /Users/me/work/task_tester
mix phx.new
でプロジェクトを生成するとアプリケーション名はプロジェクト名(上記だとtask_tester
)になります。
もしアプリケーション名を変更したい場合はmix phx.new task_tester --app hello
と--app
オプションを利用するそうです。
mix.exs
でアプリケーション名が変更されています。
defmodule Hello.MixProject do use Mix.Project def project do [app: :hello, version: "0.1.0", ...
このアプリケーション名を変更するとモジュール名などの接頭辞が変わるようです。
defmodule HelloWeb.PageController do use HelloWeb, :controller ...
--app
オプションでhello
を設定したのでモジュール名がHello
から始まってます。
lib/
フォルダ以下のディレクトリやファイル名なども変わります。
基本は--app
でいいような気はしますが、「モジュール名の接頭辞だけ変えたい!」という場合には--module
オプションで指定出来るようです。
$ mix phx.new task_tester --module Hello * creating task_tester/config/config.exs * creating task_tester/config/dev.exs * creating task_tester/config/prod.exs * creating task_tester/config/prod.secret.exs * creating task_tester/config/test.exs * creating task_tester/lib/task_tester/application.ex * creating task_tester/lib/task_tester.ex * creating task_tester/lib/task_tester_web/channels/user_socket.ex * creating task_tester/lib/task_tester_web/views/error_helpers.ex * creating task_tester/lib/task_tester_web/views/error_view.ex * creating task_tester/lib/task_tester_web/endpoint.ex * creating task_tester/lib/task_tester_web/router.ex * creating task_tester/lib/task_tester_web.ex * creating task_tester/mix.exs * creating task_tester/README.md * creating task_tester/.gitignore * creating task_tester/test/support/channel_case.ex * creating task_tester/test/support/conn_case.ex * creating task_tester/test/test_helper.exs * creating task_tester/test/task_tester_web/views/error_view_test.exs * creating task_tester/lib/task_tester_web/gettext.ex * creating task_tester/priv/gettext/en/LC_MESSAGES/errors.po * creating task_tester/priv/gettext/errors.pot * creating task_tester/lib/task_tester/repo.ex
mix.exs
は下のようなかんじ。
defmodule Hello.MixProject do use Mix.Project def project do [app: :task_tester, ...
アプリケーション名はtask_tester
のままで、接頭辞のみがHello
となっています。
ややこしいことになりそうなので、変えたいなら--app
を使った方が良さそうです。
mix phx.gen.html
これはContextあたりで使いました。HTMLでのresource
(router.ex
でresource
指定する奴)を生成する際に便利なコマンドです。
Webレイヤーの実装(コントローラ、ビュー、テンプレート)はもちろん、EctoのマイグレーションファイルやContextも生成してくれます。
このコマンドは引数が多くてややこしいですが、その中身は至ってシンプルで
mix phx.gen.html Context名 スキーマ名 リソース名 スキーマのリスト(項目名:型)
です。
$ mix phx.gen.html Blog Post posts body:string word_count:integer * creating lib/hello_web/controllers/post_controller.ex * creating lib/hello_web/templates/post/edit.html.eex * creating lib/hello_web/templates/post/form.html.eex * creating lib/hello_web/templates/post/index.html.eex * creating lib/hello_web/templates/post/new.html.eex * creating lib/hello_web/templates/post/show.html.eex * creating lib/hello_web/views/post_view.ex * creating test/hello_web/controllers/post_controller_test.exs * creating lib/hello/blog/post.ex * creating priv/repo/migrations/20170906150129_create_posts.exs * creating lib/hello/blog/blog.ex * injecting lib/hello/blog/blog.ex * creating test/hello/blog/blog_test.exs * injecting test/hello/blog/blog_test.exs
mix
系のコマンドに一般的に言えることですが、次に何やったらいいか注釈がでてくれます。
Add the resource to your browser scope in lib/hello_web/router.ex: resources "/posts", PostController Remember to update your repository by running migrations: $ mix ecto.migrate
ルーティングの設定とマイグレーションしろよ!と出ています。基本これだけでリソースが作成できます。
大体無いとは思うんですが、Contextいらないよ!って時(Bootstrapのテストなど、なんらかのテストに使う?)は--no-context
というオプションがあるそうです。
活用法があまり思い浮かばないので省略。
同様に--no-schema
オプションもあり。
mix phx.gen.json
こちらは使ったことはないですが、見た目からして上記のJSON版といったところでしょうか。
$ mix phx.gen.json Blog Post posts title:string content:string * creating lib/hello_web/controllers/post_controller.ex * creating lib/hello_web/views/post_view.ex * creating test/hello_web/controllers/post_controller_test.exs * creating lib/hello_web/views/changeset_view.ex * creating lib/hello_web/controllers/fallback_controller.ex * creating lib/hello/blog/post.ex * creating priv/repo/migrations/20170906153323_create_posts.exs * creating lib/hello/blog/blog.ex * injecting lib/hello/blog/blog.ex * creating test/hello/blog/blog_test.exs * injecting test/hello/blog/blog_test.exs
コマンドの仕様もほぼhtml
と変わらないようです。
コマンド実行後の注釈はちょっと変わってきています。
Add the resource to your :api scope in lib/hello_web/router.ex: resources "/posts", PostController, except: [:new, :edit] Remember to update your repository by running migrations: $ mix ecto.migrate
:new
と:edit
のアクションを除外しています。
JSONなので新規生成画面と編集画面がないためでしょう。HTTPリクエストなどで直接:create
や:update
を実行するということでしょう。
htmlと同様に--no-context
と--no-schema
オプションもあり。
mix phx.gen.context
Contextの章でAccountsにCredentialを追加するときに使いました。
追加するように使った場合はinjecting lib/hello/accounts.ex
というように既存のContextに追記する形で生成されましたが、Contextのみ生成したい場合にも利用できるようです。
主な使用方法はhtml
やjson
の時と同じ。
$ mix phx.gen.context Accounts User users name:string age:integer * creating lib/hello/accounts/user.ex * creating priv/repo/migrations/20170906161158_create_users.exs * creating lib/hello/accounts/accounts.ex * injecting lib/hello/accounts/accounts.ex * creating test/hello/accounts/accounts_test.exs * injecting test/hello/accounts/accounts_test.exs
mix phx.gen.schema
HTMLやJSONのリソースも作らず、Contextも不要というレアケース用?でもコマンドが用意されてるってことは必要なタイミングがあるのかも。
コマンド引数はhtml
やjson
からContext名を省略したようなかんじ。
$ mix phx.gen.schema Accounts.Credential credentials email:string:unique user_id:references:users * creating lib/hello/accounts/credential.ex * creating priv/repo/migrations/20170906162013_create_credentials.exs
mix phx.gen.channel
Channelを生成するためのコマンド。Channelの章では手動で作成しましたが、コマンドでも生成してくれるようです。
引数は純粋にチャンネル名のみ。
$ mix phx.gen.channel Room * creating lib/hello_web/channels/room_channel.ex * creating test/hello_web/channels/room_channel_test.exs
ちゃんとメッセージも出してくれます。
Add the channel to your `lib/hello_web/channels/user_socket.ex` handler, for example: channel "rooms:lobby", HelloWeb.RoomChannel
mix phx.gen.presence
これはPresenceの章で使いました。
一応モジュール名を指定できますが、省略すればPresence
で作成されます。
$ mix phx.gen.presence * creating lib/hello_web/channels/presence.ex
こちらもきちんと注釈を付けてくれます。
Add your new module to your supervision tree, in lib/hello/application.ex: children = [ ... HelloWeb.Presence ] You're all set! See the Phoenix.Presence docs for more details: http://hexdocs.pm/phoenix/Phoenix.Presence.html
mix phx.routes
Routingの章で利用しました。
コマンドを実行すればルーティングルールが表示されます。
$ mix phx.routes page_path GET / TaskTester.PageController.index/2
ルーティングファイルが2個以上あるときは、ルーティングファイル名を指定するとソイツだけみれるようです。
$ mix phx.routes TaskTesterWeb.Router page_path GET / TaskTesterWeb.PageController.index/2
mix phx.server
サーバーを立ち上げるコマンド。DoesNotExist
オプションとかもあるけどよくわかんない。(本来は「DoesNotExist」が必要だけど省略されてる?)
iexで立ち上げたい時はiex -S mix phx.server
を実行してやればよいそうで。
$ iex -S mix phx.server Erlang/OTP 17 [erts-6.4] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace] [info] Running TaskTesterWeb.Endpoint with Cowboy on port 4000 (http) Interactive Elixir (1.0.4) - press Ctrl+C to exit (type h() ENTER for help) iex(1)>
これ地味に便利そうですね。
mix phx.digest
静的ファイル(static assets)の名前にダイジェスト(MD5)を付与し、圧縮するコマンドだそうです。
機能的にほぼデプロイ用(改ざん防止、転送量軽減)と考えて問題無さそうです。
assets/
ディレクトリにあるファイルに処理を行い、デフォルトではpriv/static
にコピーするようです。
出力されるファイルの場所や処理するファイルの場所を指定したいときは引数で調整可能。
$ mix phx.digest priv/static -o www/public Check your digested files at 'www/public'.
コンフィグに:
を指定すると圧縮するファイルの対象を変更できるようです。
config :phoenix, :gzippable_exts, ~w(.js .css)
役割的にconfig/config.exs
あたりにでも書けば良いのでしょうか。
デプロイは別の章でやるので、そのときにもう一度確認します。
飛ばし飛ばしやってるつもりが結構長くなったのでまた次回!
Phoenix入門 (第13章 Contexts その5)
どうも、靖宗です。
流石に今回で終わらす!
Cross-context data
CMSとAccountsというContextが介在していますが、場合によっては1個のContextで表現した方がシンプルになり得ます。(規模が小さいときとか)
この辺は各自の判断だとは思うんですが、個人的には今回みたいに細かくContextを作成し、繋がりを最小限にとどめる設計が拡張しやすいんじゃないかとは思います。
依存関係の編集(CMS)
なにはともあれ、CMSのContext内でもまだ依存関係を追記していません。その辺を修正していきます。
まずはlib/hello/cms/page.ex
から。
defmodule Hello.CMS.Page do use Ecto.Schema import Ecto.Changeset alias Hello.CMS.Author # 追記 schema "pages" do field :body, :string field :title, :string field :views, :integer belongs_to :author, Author # 追記 ...
PageはAuthorに対して従属関係なのでこれで良さそうです。
お次はlib/hello/cms/author.ex
defmodule Hello.CMS.Author do use Ecto.Schema import Ecto.Changeset alias Hello.CMS.Page # 追記 schema "authors" do field :bio, :string field :genre, :string field :role, :string # field :user_id, :id #消去 has_many :pages, Page # 追記 belongs_to :user, Hello.Accounts.User # 追記 ...
pagesに対してAuthorは一対多です。なのでhas_many
を指定します。
user
に対しては一対一の従属関係なのでbelongs_to
です。
belongs_to
の項目があるので:user_id
の行は不要です。
ContextのAPI編集
preloadの追加
お次にContextのlist_pages
とかを編集していきます。
Accounts.Credentialを追加したときのように、Pageがロードされる前に従属関係であるAuthorをpreloadしておく必要があります。
lib/hello/cms.ex
を編集します。
defmodule Hello.CMS do ... alias Hello.CMS.{Page, Author} alias Hello.Accounts ... def list_pages do Page# Repoにしててエラーが起こった。詳細はもうちょっと先の項目で。 |> Repo.all() |> Repo.preload(author: [user: :credential]) end ... def get_page!(id) do Page |> Repo.get!(id) |> Repo.preload(author: [user: :credential]) end ... # alias Hello.CMS.Author # 消去(上でaliasしてるので) ... def get_author!(id) do Author |> Repo.get!(id) |> Repo.preload(user: :credential) end ...
どうやらRepo.preload(author: [user: :credential])
でAuthor、User、Credentialがロードされるようです。
ページを生成するときや編集する時のAuthorの取り扱い
先ほどはデータアクセス(読み込み)の際に必要なpreloadを追加しました。
ではここでは書き込みの際に必要な処理を追記していきます。
同様にlib/hello/cms.ex
を編集していきます。
... def create_page(%Author{} = author, attrs \\ %{}) do %Page{} |> Page.changeset(attrs) |> Ecto.Changeset.put_change(:author_id, author.id) |> Repo.insert() end def ensure_author_exists(%Accounts.User{} = user) do %Author{user_id: user.id} |> Ecto.Changeset.change() |> Ecto.Changeset.unique_constraint(:user_id) |> Repo.insert() |> handle_existing_author() end defp handle_existing_author({:ok, author}), do: author defp handle_existing_author({:error, changeset}) do Repo.get_by!(Author, user_id: changeset.data.user_id) end ...
まずはcreate_page
の変更点ですが、引数にAuthorの構造体が必要になりました。
Ecto.Changeset.put_change(:author_id, author.id)
でPageの構造体にAuthorのIDを追加し、リポジトリに保存という流れです。
次にensure_author_exists
以降の箇所ですが、まず前提を整理します。
このCMSはページを作る前には作成者(Author)の情報が必要(生成するページ情報に紐付くので)です。ですので、もしページを作ろうとしているユーザーがAuthorに登録されていなければ登録するというような処理が必要になります。
フォームを用意してバリデーションではじくという方針も考えられそうですがいちいち手間ですし、バックエンドで処理するべきです。ですので、そういった処理用の関数ensure_author_exists
を作成しているのでしょう。
内容は、アカウント情報を渡してAuthorとしてリポジトリに保存し、その後handle_exisiting_author
でリポジトリに挿入できたか否かをチェックしているようです。
Ecto.Changeset.unique_constraint(:user_id)
でAuthor中に同じuser_idのAuthorがいないかチェックする情報を乗せて、Repo.insert()
の際に判断されるようです。既に存在している場合はRepo.get_by!
でAuthorの情報を返すようになっているようです。
以上でCMSのContextはだいたいおっけーのはずです。
Webレイヤーの実装
お次はCMSのページを作成したりする画面を編集していきます。
ログインできるかどうかは前回確認しましたが、ページを生成するあたりはスルーしました。(リストは表示されてましたが。)
ページを生成するにあたっては、上の項目で作成したensure_author_exists
を利用してユーザーがAuthorに登録されているかどうか、されていなければ登録するPlugを作成してこのPlugを通過するように編集するのが良さそうです。
作成者関係のPlugを作成する
これはコントローラ内に実装します。
lib/hello_web/controllers/cms/page_controller.ex
を編集します。
defmodule HelloWeb.CMS.PageController do use HelloWeb, :controller alias Hello.CMS alias Hello.CMS.Page plug :require_existing_author # 追加 plug :authorize_page when action in [:edit, :update, :delete] # 追加 ... # 以下追加 defp require_existing_author(conn, _) do author = CMS.ensure_author_exists(conn.assigns.current_user) assign(conn, :current_author, author) end defp authorize_page(conn, _) do page = CMS.get_page!(conn.params["id"]) if conn.assigns.current_author.id == page.author_id do assign(conn, :page, page) else conn |> put_flash(:error, "You can't modify that page") |> redirect(to: Routes.cms_page_path(conn, :index)) |> halt() end end # 忘れてた end
require_existing_author
とauthorize_page
という2種類のPlugを作成しました。
require_existing_author
は分かりやすく、ensure_author_exists
を利用して接続情報にAuthorの情報を載せています。(無ければ作ってくれる)
authorize_page
は対象のページの編集者と閲覧者が一致する場合のみ正常に動作し、一致しない場合はフラッシュメッセージを送出してPlugの処理を止めています。このプラグは編集と削除の時だけでいいのでwhen action in~~
のオプションを使用しております。
ついでにこのプラグ内でページのIDを取得し、ページ情報も接続情報に登録(assign)しています。
Plugや他の変更に合わせてコントローラを修正する
これらを踏まえてcreate
、edit
、update
、delete
を編集します。
... def create(conn, %{"page" => page_params}) do # create_pageは上の項目でAuthorの情報が必要になった # あと, page_paramsを忘れていた case CMS.create_page(conn.assigns.current_author, page_params) do {:ok, page} -> conn |> put_flash(:info, "Page created successfully.") |> redirect(to: Routes.cms_page_path(conn, :show, page)) {:error, %Ecto.Changeset{} = changeset} -> render(conn, "new.html", changeset: changeset) end end ... # idの情報はconnに載ってるので冗長 def edit(conn, _) do # connからページ情報を取り出す changeset = CMS.change_page(conn.assigns.page) # ページ情報はconnに載ってるので冗長 render(conn, "edit.html", changeset: changeset) end ... # idの情報はconnに載ってるので冗長 def update(conn, %{"page" => page_params}) do # connからページ情報を取り出す case CMS.update_page(conn.assigns.page, page_params) do {:ok, page} -> conn |> put_flash(:info, "Page updated successfully.") |> redirect(to: Routes.cms_page_path(conn, :show, page)) {:error, %Ecto.Changeset{} = changeset} -> # ページ情報はconnに載ってるので冗長 render(conn, "edit.html", changeset: changeset) end end # idの情報はconnに載ってるので冗長 def delete(conn, _) do # connからページ情報を取り出す {:ok, _page} = CMS.delete_page(conn.assigns.page) conn |> put_flash(:info, "Page deleted successfully.") |> redirect(to: Routes.cms_page_path(conn, :index)) end ...
大量に修正点がありますが、主にauthorize_page
で既にページ情報はPlug.Connにロードしてるのでそこから取り出すように変更したところです。
作成者名を表示する
ついでにページの表示に作成者を表示するように修正しておきます。
まずはビューのlib/hello_web/views/cms/page_view.ex
を編集します。
defmodule HelloWeb.CMS.PageView do use HelloWeb, :view alias Hello.CMS def author_name(%CMS.Page{author: author}) do author.user.name end end
ページ情報に基づき、作成者の名前情報を返す関数を作成しました。
これをテンプレートで呼びます。lib/hello_web/templates/cms/page/show.html.eex
を編集します。
... <li> <strong>Views:</strong> <%= @page.views %> </li> <li> <strong>Author:</strong> <%= author_name(@page) %> </li> </ul> ...
他の項目と並べるように作成者の表示を追加しました。これでOKな筈です。
mix phx.server
で試す(間違い探し)
それではPageは閲覧数以外もう大丈夫な筈なのでチェックしてみます。
mix phx.server
で実行します。
PS \hello> mix phx.server Compiling 2 files (.ex) == Compilation error in file lib/hello_web/controllers/cms/page_controller.ex == ** (TokenMissingError) lib/hello_web/controllers/cms/page_controller.ex:79: missing terminator: end (for "do" starting at line 1) (elixir) lib/kernel/parallel_compiler.ex:208: anonymous fn/4 in Kernel.ParallelCompiler.spawn_workers/6
lib/hello_web/controllers/cms/page_controller.ex
の最後の箇所でエラーが出ています。
end忘れでした。再実行。
サーバーは立ち上がったのでhttp://localhost:4000/cms/pages
にアクセス。
Request: GET /cms/pages ** (exit) an exception was raised: ** (Protocol.UndefinedError) protocol Ecto.Queryable not implemented for Hello.Repo, the given module does not provide a schema. This protocol is implemented for: Atom, BitString, Ecto.Query, Ecto.SubQuery, Tuple (ecto) lib/ecto/queryable.ex:40: Ecto.Queryable.Atom.to_query/1 (ecto) lib/ecto/repo/queryable.ex:14: Ecto.Repo.Queryable.all/3 (hello) lib/hello/cms.ex:23: Hello.CMS.list_pages/0 ...
('ω')。o(????????????)
Ecto絡みということはWebレイヤー側の実装ミスでは無い筈。
Context周りを確認します。list_pages
って書いてあるしそのへん?
... def list_pages do Repo # ←Pageやんけ・・・ |> Repo.all() |> Repo.preload(author: [user: :credential]) end ...
寝てたんかな。PageをRepoとタイポ。
修正して再アクセス。
表示はできたけどセッションが生きてる?
ログアウトしたらCSRFと勘違いされたので、サーバーを再起動。
の後再ログイン。
作成ページは問題無さそう。作成してみる。
タイトルと本文ちゃんと入力したのに怒られた・・・
まずはコントローラーを疑ってみる。
... def create(conn, %{"page" => page_params}) do # , page_params ないやん ↓ case CMS.create_page(conn.assigns.current_author) do {:ok, page} -> ...
そらそうなるわ。修正してもう一度。
こんどはうまくいった!
一応別のユーザーを作成して編集できないかチェック。
authorize_page
のPlugもよさそうです。
Adding CMS functions
AccountsのContextにauthenticate_by_email_password/2
という関数を作成して機能を拡張したようにCMSにも機能を追加で来ます。Contextを使って閲覧数をカウントアップする機能を追加していきます。
機能の仕様を考える
編集したときと同じ様にCMS.update_page
で実装するのは色々と問題があります。
まず第一に競合が起こりやすくなります。PV数がかなり少ないようなショボいケースであればほぼ問題無いのですが、複数人が同時にアクセスした場合にカウントが正しくないケースが考えられます。
例えば
- User 1がカウント13のページをロードする
- User 1がページのカウントを14にする
- User 2がカウント14のページをロードする
- User 2がページのカウントを15にする
こうなれば良いんですが、同時アクセスなどがある場合下記が考えられます。
- User 1がカウント13のページをロードする
- User 2がカウント13のページをロードする
- User 1がページのカウントを14にする
- User 2がカウント14のページをロードする
- User 2がページのカウントを14にする
ではどのような仕様が望ましいか。
ページ情報のロードの際にインクリメントして呼び出されるのが望ましいです。つまり
page = CMS.inc_page_views(page)
となるようなinc_page_views/1
を定義して、Page情報取得の箇所にパイプラインとして渡せば綺麗におさまりそうです。
CMSに実装する
それではCMSのContextに実装していきます。
lib/hello/cms.ex
を編集します。
... def inc_page_views(%Page{} = page) do {1, [%Page{views: views}]} = Repo.update_all( from(p in Page, where: p.id == ^page.id), [inc: [views: 1]], returning: [:views]) put_in(page.views, views) end ...
場所はどこでも良いと思いますが、一応Context内の整理を考慮してchange_page
関数の次に記載しました。
Ectoのクエリを詳しく見る必要がありそうですが、Repo.update_all
の箇所はfrom(p in Page, where: p.id == ^page.id)
で該当ページをヒットさせ、[inc: [views: 1]]
でヒットしたPageのviews
を1インクリメントして保存し、返値にviews
を要求する、という処理のようです。
PageのIDはユニークなので成功すれば、帰ってくるタプルは必ず{1(updateした項目数), hogehoge}
となるというところでしょう。
put_in
でpage.views
にviews
の値を代入し、pageを返すといった流れでしょうか。
これでinc_page_views
の実装は良さそうです。
いつものごとくWebレイヤーを修正していきます。
コントローラを修正する
HTTPアクセスの度に呼び出される処理はコントローラの役目です。lib/hello_web/controllers/cms/page_controller.ex
を編集します。
... def show(conn, %{"id" => id}) do page = id |> CMS.get_page!() |> CMS.inc_page_views() render(conn, "show.html", page: page) end ...
閲覧した時にインクリメントされて欲しいのでshow
に先ほどの機能を実装します。
これで実装は完了されたはずです。サーバーを起動して確認してみます。
インクリメントされてます!完成です!
パスワード認証などははしょりましたが、これでCMSまでをも作る事ができました。
割とこの辺は他のシステムなどにも応用が利きそうです。
FAQ
Returning Ecto structures from context APIs
(Contextはカプセル化するための概念なのになんでcreate_user/1
みたいな関数は失敗したときにEcto.Changeset
を生で返すねん!)
Phoenixでは%Ecto.Changeset{}
は一般的な構造体として考えられているそうで、Phoenix.Param
やPhoenix.HTML.FormData
でハンドリングできるそうです。あと、エラーメッセージとかバリデーションのどこがアカンかったとか見やすいからだそうで。
Strategies for cross-context workflows
今回はページを生成する際にAuthorをバックエンドで作成してましたが、これは必ずしも全ユーザーがAuthorである必要性がない前提です。もしユーザーがAuthorのデータを必要とするときはどのような依存関係になるのでしょうか。
アカウントを生成する際に依存関係を結ぶようなケースを考えると、たとえばAccountsのContextのcreate_user
で
def create_user(attrs) do %User{} |> User.changeset(attrs) |> Ecto.Changeset.cast_assoc(:credential, with: &Credential.changeset/2) |> Ecto.Changeset.put_assoc(:author, %Author{...}) # これ |> Repo.insert() end
となります。
一見問題無さそうに見えますが、この設計だとCMSはAccountsの構造に依存しており、AccountsもまたCMSの構造に依存することになります。
AccountsはCMSから完全に独立しているからこそ拡張性が高いのですが、上記のような循環参照みたいな関係になってしまうと拡張性も糞も無くなります。
ただ、よくある事のようなので、こういうケースは新しくContextを生成するのがベストプラクティスとされているようです。
例えば、今回であればAccountsとCMSはそのままで、UserRegistrationというContextを新しく作ります。このContextからAccountsとCMSを呼び出し、CMSのAuthorの関連付けを行います。こうすることでAccountsとCMSのそれぞれの結びつきを最小限にできるだけでなく、APIとしても明瞭(Context名から何を意味しているのかが推測できたりとか?)になるはずです。このアプローチを採用する際にはEcto.Multi
が有用だそうです。
例を見ます。
defmodule Hello.UserRegistration do alias Ecto.Multi alias Hello.{Accounts, CMS} def register_user(params) do Multi.new() |> Multi.run(:user, fn _ -> Accounts.create_user(params) end) |> Multi.run(:author, fn %{user: user} -> {:ok, CMS.ensure_author_exists(user)} end) |> Repo.transaction() end end
Multi.new()
から続くパイプラインでそれぞれの依存関係の処理を行い、最後にトランザクション処理がなされる流れです。
もしどこかの処理で失敗すれば、全てがロールバックする仕組みだそうです。
おわりに
Context長かったのですが、割とPhoenixの中枢の話な気はします。
また復習してMulti.new()
あたりも活用できるサンプルを公開できればなぁと思います。