GitHubのリポジトリを直接指定してgemをinstallする
自作gemとかOSSのgemのリポジトリとかブランチとかを指定して使いたい場合の解決策です。
リポジトリの .gemspec
ファイル等は設定済み前提で。
Gemfile使う方法とGemfile使わない方法の2通りあります。
Gemfileを使う場合
普通に指定できます。以下のようにGemfileに記述
gem 'hogehoge', git: 'git@github.com:foo/bar.git', branch: 'develop'
Gemfileを使わない場合
specific_install
gemを使います。
specific_installのInstall
$ gem install specific_install
リポジトリからgem install
$ gem specific_install git@github.com:foo/bar.git develop
内部的にはcloneしてbuild & installしてローカルリポジトリ破棄してるっぽいです。
Effective Ruby 第五章 メタプログラミング まとめ その2
Effective Ruby 第五章 メタプログラミング まとめ その1 - Memento memo. の続きです。
モンキーパッチの代わりとなるものを検討する
モンキーパッチとは
- モンキーパッチとは、実行時にコアクラスを拡張したり挙動を書き換える(パッチを当てる)こと。Active Supportが有名。
- 複数パッチが衝突すると大事故になるのでできるだけモンキーパッチは避けたい
方法1 module関数を使う
愚直にmoduleを定義&オブジェクトをextendする方法です。
module OnlySpace ONLY_SPACE_UNICODE_RE = %r/\A[[:space]]*\z/ def self.only_space? (str) if str.ascii_only? !str.bytes.any? do |b| b != 32 && !b.between?(9, 13) end else ONLY_SPACE_UNICODE_RE === str end end def only_space? OnlySpace.only_space?(self) end end str1 = " \r\n" puts OnlySpace.only_space?(str1) # OOPっぽくなくなる str2 = "hello" str2.extend(OnlySpace) puts str2.only_space? # extendしないと使えない
方法2 新しい別のクラスを作成する
Stringクラスの代わりにStringExtraクラスを作成します。 継承ではなく委譲を使ってStringライクなStringExtraを実装します。(実装省略)
方法3 Refinements機能を使う
Ruby 2.1から入ったRefinements機能を使います。
Refinementsでは、パッチの適用範囲がレキシカルスコープの中に限定されます。以下の例で String#loud
は Foo
の外で呼ぶことはできません。
使い方はこんな感じになります。
module Loud refine String do def loud "#{self}!!!" end end end class Foo using(Loud) def initialize(str) puts str.loud end end Foo.new("wei") #=> wei!!!
参考
エイリアスチェイニングで書き換えたメソッドを呼び出す
- 既存のメソッドに新しい名前を与え、元のメソッド名でメソッドを再定義して最終的に元のメソッドを呼び出す。
- エイリアス作成時にメソッド名がユニークになるよう注意
- エイリアスチェイニングを取り消すメソッドも作成する
alias_methodについて
alias_method(new_name, original_name)
でmethodにエイリアスを貼ることができます。
参考: ref.xaio.jp
sample
module LogMethod def log_method(method) orig = "#{method}_without_logging".to_sym if instance_methods.include?(orig) raise(NameError, "#{orig} isn't a unique name") end alias_method(orig, method) define_method(method) do |*args, &block| $stdout.puts("calling method '#{method}'") result = send(orig, *args, &block) $stdout.puts("'#{method}' returned #{result.inspect}") result end end #エイリアスチェイニングを取り消すメソッドも作成する def unlog_method(method) orig = "#{method}_without_logging".to_sym if !instance_methods.include?(orig) raise(NameError, "was #{orig} already removed?") end remove_method(method) alias_method(method, orig) remove_method(orig) end end Array.extend(LogMethod) #=> Array Array.log_method(:first) #=> :first [1,2,3].first # calling method 'first' '# first' returned 1 #=> 1 Array.unlog_method(:first) #=> Array irb(main):008:0> [1,2,3].first #=> 1
Procの引数の個数の違いに対応できるようにする
- Procオブジェクト生成には"強いProc"と"弱いProc"がある。
lambda?
メソッドで識別可能。 - 弱い(Weak)Proc: 引数の扱いが緩く、間違った個数の引数を与えてもエラーにならない。e.g. block
- 強い(Strong)Proc: 通常メソッド呼び出しと同じで、引数の個数が違うとArgumentError例外が発生する。e.g. lambda
- Proc#arityメソッドを使うと、Procオブジェクトが期待する引数の数がわかる。
- Proc#arityメソッドで引数の個数の違いをうまく吸収させる。
モジュールのprependを使うときは慎重に考える
- 継承階層は
ancestors
メソッドで確認可能 - includeは継承階層においてレシーバの後にモジュールを挿入する。
- prependは継承階層においてレシーバの前にモジュールを挿入する。
- prependよりもalias_methodを使った方が柔軟になる
メタプログラミング面白いですね。Refinementsで局所的にクラス拡張するのは結構気に入りました。
まだ メタプログラミングRuby 第2版 も読めていないのですが、健全なメタプロパワーを高めていきたいです。
- 作者: Peter J.Jones
- 出版社/メーカー: 翔泳社
- 発売日: 2015/01/19
- メディア: Kindle版
- この商品を含むブログ (4件) を見る
denite.nvimとag使い始めた
かの有名な暗黒美夢王ことShougoさん開発中のdenite.nvimを使い始めました。いつもありがとうございます。超速くていい感じです。
install方法とか詳細な説明は以下のエントリが詳しいので割愛。
とりあえずdeniteの設定自体は :h denite
に載ってたのをとりあえず8割くらいコピペしました。
fast grepツールとしてagを使ってます。
設定はこんな感じになりました。
call denite#custom#var('file_rec', 'command', \ ['ag', '--follow', '--nocolor', '--nogroup', '-g', '']) call denite#custom#var('grep', 'command', ['ag']) call denite#custom#var('grep', 'recursive_opts', []) call denite#custom#var('grep', 'final_opts', []) call denite#custom#var('grep', 'separator', []) call denite#custom#var('grep', 'default_opts', \ ['--nocolor', '--nogroup']) nnoremap <silent> <C-k><C-f> :<C-u>Denite file_rec<CR> nnoremap <silent> <C-k><C-g> :<C-u>Denite grep<CR> nnoremap <silent> <C-k><C-l> :<C-u>Denite line<CR> nnoremap <silent> <C-k><C-u> :<C-u>Denite file_mru<CR> nnoremap <silent> <C-k><C-y> :<C-u>Denite neoyank<CR>
とりあえず :Denite grep
:Denite file_rec
あたりが快適なのでいろいろ捗ります。
pluginばっかりに頼ってると素のvim, grep, findあたりの使い方が怪しくなってくる。。。
- 作者: DrewNeil,新丈径
- 出版社/メーカー: KADOKAWA / アスキー・メディアワークス
- 発売日: 2014/01/28
- メディア: Kindle版
- この商品を含むブログ (3件) を見る
Ruby2.3系での"Missing frozen string literal comment"への対処
Missing frozen string literal comment
Ruby2.3系でRubocopを実行したら Missing frozen string literal comment
と怒られました。
ファイルの先頭に以下のmagic commentが必要とのこと。
# frozen_string_literal: true
これによってRubyのStringリテラルが勝手にfreezeされてimmutableになるらしいです。 Ruby3系では基本的にimmutableになる予定なので、その互換のためのようです。便利な上に移行も楽そうですね。
参考:
対処
Rubocopで Missing frozen string literal comment
が出てるファイルの先頭行にmagic commentを追記すればよいので、適当なワンライナーで一括対処できます。
rubocop | grep 'Missing frozen string' | cut -d: -f1 | xargs gsed -i -e '1i\# frozen_string_literal: true\n'
- 作者: Peter J. Jones,arton,長尾高弘
- 出版社/メーカー: 翔泳社
- 発売日: 2015/01/09
- メディア: 大型本
- この商品を含むブログ (13件) を見る
Git Large File Storage (LFS)を使う
会社のSlackでGit Large File Storage (LFS)なるものを知ったので適当に使ってみました。
Git LFSとは?
Gitの拡張です。 画像とか音声みたいなLarge File(BLOB)をGitで管理しようとめちゃくちゃ重くなってきます。 直接BLOBをいじらずにポインタをテキストファイルとしてgitで管理し、BLOBの本体は別サーバに保存してるみたいです。 本体のリポジトリがすっきりする&fetch, cloneが高速になるのがメリットです。
Install
$ brew install git-lfs $ git lfs install
使い方
とりあえず適当な画像(space_cat.jpg)をリポジトリに配置して試しました。 DocのGetting Startedを参考に。
# track対象のパス確認 $ git lfs track Listing tracked paths # track対象に"*.jpg"追加 $ git lfs track "*.jpg" Tracking *.jpg # track対象のパス確認 $ git lfs track Listing tracked paths *.jpg (.gitattributes) # .gitattributesに設定が追記されている $ cat .gitattributes *.jpg filter=lfs diff=lfs merge=lfs -text # あとは普通 $ git add . $ git ci -m 'aaa' $ git push
これだけなので超簡単です。
試したやつ
space_catを回転させてPR出してます。プレビューが楽しい。
仕様
このあたりに書いてあったのであとで読みたいやつです。
エンジニアのためのGitの教科書 実践で使える! バージョン管理とチーム開発手法 (WEB Engineer’s Books)
- 作者: 株式会社リクルートテクノロジーズ,株式会社リクルートマーケティングパートナーズ,河村聖悟,太田智彬,増田佳太,山田直樹,葛原佑伍,大島雅人,相野谷直樹
- 出版社/メーカー: 翔泳社
- 発売日: 2016/01/20
- メディア: 大型本
- この商品を含むブログ (3件) を見る
Effective Ruby 第五章 メタプログラミング まとめ その1
Effective Ruby 第四章 例外 まとめ - Memento memo. の続きです。
第五章 メタプログラミングについての前半部です。
- 作者: Peter J.Jones
- 出版社/メーカー: 翔泳社
- 発売日: 2015/01/19
- メディア: Kindle版
- この商品を含むブログ (4件) を見る
モジュール、クラスフックを使いこなす
- Rubyのイベント通知 -> フック関数の実行は、適切な名前のメソッドを書くだけで実行可能
- 定義できるフックは10種類
hookメソッド | タイミング |
---|---|
included | moduleがincludeされる |
extended | moduleがextendされる |
prepended | moduleがprependされる |
inherited | classが継承される |
method_added | method追加 |
method_removed | method削除 |
method_undefined | method定義解除 |
singleton_method_added | 特異method追加 |
singleton_method_removed | 特異method削除 |
singleton_method_undefined | 特異method定義解除 |
- 全てのフックメソッドは特異メソッドとして定義する
- method追加、削除、定義解除系のフックは引数としてメソッド名を受け取る。クラス名を知りたい場合はselfを使う。
- 全てのフックメソッドは自動的にprivateになる
- フックメソッドに関連する以下のメソッドはオーバーライドしてはならない。フックを使うこと。
- extend_object
- append_features
- prepend_features
module PreventInheritance class InheritanceError < StandardError; end def inherited(child_klass) raise(InheritanceError, "#{child_klass} cannot inherit from #{self}") end end ::Array.extend(PreventInheritance) class ChildArray < ::Array; end #=> ChildArray cannot inherit from Array (PreventInheritance::InheritanceError)
inherited
フックを使うとこんな感じの使い方になります。
クラスフックからはsuperを呼び出す
- hookは他のモジュールで定義されたhookの制御を完全に奪ってしまう場合がある。
- クラスフックメソッド内では必ず
super
を呼ぶのが行儀が良い。
method_missingではなくdefine_methodを使う
- method_missingを使ってはいけない理由
- パフォーマンス面でコストがかかる
- エラーメッセージが分かりにくくなる
respond_to?
等のイントロスペクションメソッドが使えなくなる。
- method_missingで本来やりたいことの大半はproxy, decorator patternの実装だが、これらはdefine_methodで代替可能
- proxyの実装
class HashProxy Hash.public_instance_methods(false).each do |name| define_method(name) do |*args, &block| @hash.send(name, *args, &block) end end def initialize @hash = {} end end
- decoratorの実装
require('Logger') class AuditDecorator def initialize(object) @object = object @logger = Logger.new($stdout) @object.public_methods.each do |name| define_singleton_method(name) do |*args, &block| @logger.info("calling '#{name}' on #{@object.inspect}") @object.send(name, *args, &block) end end end end
上記例の用に、オブジェクトの場合は define_singleton_method
で特異メソッドを定義できます。
- どうしても
method_missing
を避けられない場合はrespond_to_missing?
を併用する。 respond_to_missing?
はrepond_to?
がメソッドを見つけられなかった場合の最後のチャンスをプログラマに委ねる。
evalの多様な変種間の違いを把握する
eval
,instance_eval
,class_eval
で直接文字列を評価するのは避ける。- 代わりに
class_exec
,instance_exec
とブロックを使うと変数のvalidation等が行えるようになる。
参考
細かいところはメタプログラミングRubyを読む必要がありそうですね。
- 作者: Paolo Perrotta,角征典
- 出版社/メーカー: オライリージャパン
- 発売日: 2015/10/10
- メディア: 大型本
- この商品を含むブログ (2件) を見る
続きます。
翔泳社の技術書(電子版)が40%オフセールだったので色々買った
技術書は分厚くて持ち歩くのがしんどいので最近は全部電子版を買って読んでます。 翔泳社の技術書はKindle対応してるので最高です。
翔泳社の技術書のセールが始まったらしいので色々買いました。 個人的にメインはDDD本です。
買った / すでに持ってた本
エリック・エヴァンスのドメイン駆動設計
DDD本です。結構難しいらしいので頑張って読みたいです。
エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)
- 作者: エリック・エヴァンス,今関剛,和智右桂,牧野祐子
- 出版社/メーカー: 翔泳社
- 発売日: 2011/04/09
- メディア: 大型本
- 購入: 19人 クリック: 1,360回
- この商品を含むブログ (131件) を見る
実践ドメイン駆動設計
DDDと合わせて読みたいやつです。途中まで読んで力尽きたのでリトライしたいです。
- 作者: ヴァーン・ヴァーノン
- 出版社/メーカー: 翔泳社
- 発売日: 2015/03/19
- メディア: Kindle版
- この商品を含むブログ (2件) を見る
Spring徹底入門
DIとかAOPの解説が分かりやすい。大判固定レイアウトなので電子版だと読むのがつらかったです。
Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発
- 作者: 株式会社NTTデータ
- 出版社/メーカー: 翔泳社
- 発売日: 2016/07/21
- メディア: 大型本
- この商品を含むブログ (1件) を見る
Effective Ruby
読むだけでRubyのコーディングの質が上がります。 最近読み終わりました。めっちゃ良かったです。
- 作者: Peter J. Jones,arton,長尾高弘
- 出版社/メーカー: 翔泳社
- 発売日: 2015/01/09
- メディア: 大型本
- この商品を含むブログ (13件) を見る
実用Common Lisp
200pくらい読んで挫折しましたが良書です。 全部で1000pくらいあります。
- 作者: ピーター・ノーヴィグ
- 出版社/メーカー: 翔泳社
- 発売日: 2015/06/02
- メディア: Kindle版
- この商品を含むブログを見る
スターティングGo言語
Golangの入門書です。 "みんなのGo言語"より初心者向けのはずです。
- 作者: 松尾愛賀
- 出版社/メーカー: 翔泳社
- 発売日: 2016/05/11
- メディア: Kindle版
- この商品を含むブログを見る
エッセンシャル スクラム
スクラム本はいろいろあるんですが、会社の先輩におすすめされたのでこれ買いました。 アジャイルサムライとかもおすすめらしいです。
- 作者: Kenneth S. Rubin
- 出版社/メーカー: 翔泳社
- 発売日: 2014/08/01
- メディア: Kindle版
- この商品を含むブログ (3件) を見る
今回買ってないけど読んでみたい本
ストラウストラップのプログラミング入門
鈍器&名著らしいですが、多分読めないので今回はスルーしました。
- 作者: ビャーネ・ストラウストラップ
- 出版社/メーカー: 翔泳社
- 発売日: 2015/06/11
- メディア: Kindle版
- この商品を含むブログを見る
プログラマのためのSQL
結構人気が高いみたいです。 今回はキャパオーバーしそうなのでパスしましたがいつか読みたいやつです。
- 作者: ジョー・セルコ,Joe Celko,ミック
- 出版社/メーカー: 翔泳社
- 発売日: 2013/05/24
- メディア: 大型本
- この商品を含むブログ (16件) を見る
プログラマのためのDocker教科書
Dockerは日本語良書が少ない上、結構頻繁にアップデートされてる印象です。 公式ガイド読んだりKubernetesの勉強した方がいいっぽいですが一応
プログラマのためのDocker教科書 インフラの基礎知識&コードによる環境構築の自動化
- 作者: 阿佐志保,山田祥寛
- 出版社/メーカー: 翔泳社
- 発売日: 2015/11/20
- メディア: 大型本
- この商品を含むブログ (2件) を見る
VegetaでPhoenixとRailsの負荷テスト
以前会社の同期とご飯を食べていた時、 HTTP負荷テストツールのVegetaというものを紹介してもらったので試してみました。
GitHubのプロジェクトページを開くとイカしたサイヤ人の王子が目に飛び込んできます。
Golang製ツールで、CLIで比較的簡単に扱えるのが特徴のようです。
大量のリクエストを投げるコマンドが vegeta attack
です。
大量にエネルギー弾を撃ちまくるイメージですね。
ドラゴンボールの負け確フラグです。
"王子戦法"、またの名を"グミ撃ち"というらしいです。
Vegeta で負荷をかける
とりあえず使ってみます。他所のサーバに負荷をかけると本当に怒られるのでやめましょう。自前で用意したサーバかlocalhostに向かって実行しましょう。
負荷テストの対象は最近ハマってるPhoenix (v1.2.1, Elixir 1.2.6)と、比較用のRails (v5.0.0.1, Ruby 2.2.3)です。
手持ちのMBPのローカルでPhoenixとRailsをnewした状態でサーバを立てます。 そしてlocalhostのルートURLに向かってリクエストを飛ばします。
※ 条件は超適当です。
VegetaのREADMEを読みつつ、以下のようなコマンドで rate
を変えながら結果グラフを作成してみます。
$ echo "GET http://localhost:3000" | vegeta attack -duration=10s -rate=100 | vegeta report -reporter=plot > rails-100.html
vegeta attack
のオプションで以下の内容を指定しています。
- duration=10s ... 10秒間負荷をかける(今回は全試行で固定値)
- rate=100 ... 秒間リクエスト数(変動値)
Rails
※Rails5のデフォルト設定のままPumaを使っています。
チューニングすればもう少しなんとかなるかもしれません。
30req/s
余裕ですね。20ms付近で安定しています。
50req/s
まだまだいけそうです。
80req/s
ダメになりました。 半分くらいの処理が詰まって10秒遅れて処理されてます。(リクエスト受け付けずにVegetaがRetryしてるんでしょうか???HTTPステータスコード等を全く見てないので正確な挙動は不明です。すみません。。。) 通常のレスポンスも70ms程度まで落ち込んでいます。
100req/s
すごいことになりました。
Phoenix
次いでPhoenixです。
30req/s
余裕です。
100req/s
まだまだいけます。Railsはここで死んでました。
500req/s
レイテンシが若干大きくなってますが、概ね100ms以下で捌き切ってます。
800req/s
レイテンシが100ms前後安定してます。
1000req/s
エラーがいろいろ発生し始めました。 最初の2秒くらいは頑張ってますね。
2000req/s
半分くらいエラーになってしまいました。お疲れ様でした。
まとめ
条件は超ざっくりですが、ローカル環境では
くらいいけました。Phoenixの方が10倍くらいパフォーマンス良いみたいな通説があるんですが、大体そんな感じの結果です。
分散環境でもPhoenix + Elixirは強いはずなので、高速で大量のリクエストを捌く必要のあるシステム(ソシャゲやtwitter)に向いてる気がします。通信回数控えめなエンタープライズ系だとメリットより導入コスト・リスクの方が上回るような感覚です。
キャパシティプランニング ― リソースを最大限に活かすサイト分析・予測・配置
- 作者: John Allspaw,佐藤直生,木下哲也
- 出版社/メーカー: オライリージャパン
- 発売日: 2009/03/19
- メディア: ペーパーバック
- 購入: 9人 クリック: 61回
- この商品を含むブログ (21件) を見る
あと、あまり負荷テストとかやったことがないので、 このあたりもう少し勉強していきたいです。 グラフの解釈等間違っていたらご指摘いただけると幸いです。
- 作者: Dave Thomas,笹田耕一,鳥井雪
- 出版社/メーカー: オーム社
- 発売日: 2016/08/19
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る
PhoenixのChannelを使う
Phoenixのガイドを眺めていて一番気になったのがChannelだったので、上記の公式ガイドに沿ってChannelを使ってみました。結構端折っています。
- 作者: Dave Thomas,笹田耕一,鳥井雪
- 出版社/メーカー: オーム社
- 発売日: 2016/08/19
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る
概要をつかむために公式ガイドの一番上のところだけ訳してみます。
Channels are a really exciting and powerful part of Phoenix that allow us to easily add soft-realtime features to our applications. Channels are based on a simple idea - sending and receiving messages. Senders broadcast messages about topics. Receivers subscribe to topics so that they can get those messages. Senders and receivers can switch roles on the same topic at any time.
Since Elixir is based on message passing, you may wonder why we need this extra mechanism to send and receive messages. With Channels, neither senders nor receivers have to be Elixir processes. They can be anything that we can teach to communicate over a Channel - a JavaScript client, an iOS app, another Phoenix application, our watch. Also, messages broadcast over a Channel may have many receivers. Elixir processes communicate one to one.
The word "Channel" is really shorthand for a layered system with a number of components. Let's take a quick look at them now so we can see the big picture a little better.
適当訳:
チャネルはPhoenixの中でも本当に面白くて強力なところであり、簡単にソフトリアルタイム性をアプリケーションに持たせることができます。チャネルはメッセージの送受信という、単純なアイデアに基いています。senderはtopicについてブロードキャストし、receiverはtopicを購読することによってメッセージを受け取ることができます。sender, receiverはいつでも役割を交代することができます
ElixirはMessage Passingに基いているため、どうしてメッセージの送受信に他の仕組みを利用するのか疑問に思うことでしょう。チャネルでは、senderもreceiverもElixirのプロセスである必要はありません。チャネルと通信するものはJavaScriptやiOSでも何だってよいのです。また、Elixirのプロセスが1対1で通信するのに対し、チャネルにおけるメッセージブロードキャスティングはreceiverが複数になり得ます。
"Channel"という言葉は多くのコンポーネットをもった多層システムを簡略に表したにすぎません。 全体像をもっとよくつかめるようになるために、もう少し覗いてみましょう。
以下、各partsの説明(省略)
- Socket Handlers
- Channel Routes
- Channels
- PubSub
- Messages
- Topics
- Transports
- Transport Adapters
- Client Libraries
手を動かすのが一番早い理解につながるので、 例の如く適当にプロジェクトとDBを作成します
プロジェクト作成
$ mix phoenix.new channel_sample
$ cd channel_sample
$ mix ecto.create
SocketとChannelの設定
lib/hello_phoenix/endpoint.ex
の4行目付近ですでにsocketが定義されています。
defmodule ChannelSample.Endpoint do use Phoenix.Endpoint, otp_app: :channel_sample socket "/socket", ChannelSample.UserSocket ...
ChannelSample.UserSocket
自体は web/channels/user_socket.ex
で定義されています。5行目のコメントアウトを外して、channelの設定をします。
defmodule ChannelSample.UserSocket do use Phoenix.Socket ## Channels channel "room:*", ChannelSample.RoomChannel
Channelモジュールの実装
HelloPhoenix.RoomChannel
モジュールはまだ存在しないので、web/channels/room_channel.ex
ファイルを作成し、以下の内容でモジュールを定義します。
defmodule ChannelSample.RoomChannel do use Phoenix.Channel def join("room:lobby", _message, socket) do {:ok, socket} end def join("room:" <> _private_room_id, _params, _socket) do {:error, %{reason: "unauthorized"}} end end
( <>
は文字列結合をしています)
認可のために、join/3
関数を定義する必要があります。
今回は"room:lobby" topicだけは誰でも入れるようにし、private_roomのことは考えないこととします。
クライアント側の設定
web/static/js/socket.js
に最低限の実装が最初からあるので、中身を確認します。
// web/static/js/socket.js ... socket.connect() // Now that you are connected, you can join channels with a topic: let channel = socket.channel("room:lobby", {}) channel.join() .receive("ok", resp => { console.log("Joined successfully", resp) }) .receive("error", resp => { console.log("Unable to join", resp) }) export default socket
上記のように57行目付近を
let channel = socket.channel("room:lobby", {})
に変更します。
また、web/static/js/app.js
末尾行のコメントアウトを外してsocket.jsを有効にします。
import socket from "./socket"
Phoenixがライブリロードされ、ブラウザコンソールに以下のように出力され、
socket通信が確立されていることがわかります。
(Phoenixを起動していない場合は $ mix phoenix.server
します)
web/templates/page/index.html.eex
を修正して、入力フォームとメッセージ表示用のコンテナを作成します。
<div id="messages"></div> <input id="chat-input" type="text"></input>
socket.js
ファイルを修正して、socket通信でメッセージの受送信ができるようにします。
... let channel = socket.channel("room:lobby", {}) let chatInput = document.querySelector("#chat-input") let messageContainer = document.querySelector("messages") chatInput.addEventListener("keypress", event => { if(event.keyCode === 13){ channel.push("new_msg", {body: chatInput.value}) chatInput.value = "" } }) channel.on("new_msg", payload => { let messageItem = document.createElement("li"); messageItem.innerText = `[${Date()}] ${payload.body}` messagesContainer.appendChild(messageItem) }) // let channel = socket.channel("topic:subtopic", {}) channel.join() .receive("ok", resp => { console.log("Joined successfully", resp) }) .receive("error", resp => { console.log("Unable to join", resp) }) export default socket
RoomChannelモジュールを修正します。 socketでメッセージが飛んできた際のフック関数として handle_in
を実装すれば大丈夫です。
broadcast!
で接続中のクライアント全員にメッセージを送ります。
handle_out
ではクライアントごとにフィルタリング処理を行ったり、Interceptor的な役割をもっています。今回は何もしていません。
defmodule ChannelSample.RoomChannel do use Phoenix.Channel def join("room:lobby", _message, socket) do {:ok, socket} end def join("room:" <> _private_room_id, _params, _socket) do {:error, %{reason: "unauthorized"}} end def handle_in("new_msg", %{"body" => body}, socket) do broadcast! socket, "new_msg", %{body: body} {:noreply, socket} end def handle_out("new_msg", payload, socket) do push socket, "new_msg", payload {:noreply, socket} end end
これでChatができるようになりました。
Sample
こちらにChat Appのサンプルがあるようです。
http://phoenixchat.herokuapp.com/
Heroku上で動いています。逆に対応してるPaaSはHerokuだけかもしれないです。
Programming Phoenix: Productive, Reliable, Fast
- 作者: Chris Mccord,Bruce Tate,Jose Valim
- 出版社/メーカー: Pragmatic Bookshelf
- 発売日: 2016/04/30
- メディア: ペーパーバック
- この商品を含むブログを見る
まとめ
Channelが手軽につかえていい感じです。 当然普通にRails的な使い方もできるのでなかなか汎用性高いのでは?と思いました(小並感)
Effective Ruby 第四章 例外 まとめ
Effective Ruby 第三章 コレクション まとめ - Memento memo. の続きです。
今回はRubyの例外についてです。
例外は、以下の2つの異なる言語機能をまとめたものだと考えられます。
- エラーの説明
- 制御フロー
raiseにはただの文字列ではなくカスタム例外を渡す
raise
に文字列だけを渡さず、例外のクラス名を指定する- または例外クラスを(パラメータと共に)インスタンス化してraiseに渡す
- カスタム例外クラスにinitializeメソッドを定義するときはsuperを呼び出す
raise("something wrong") # NG raise(CustomError, "something wrong") # OK raise(CustomError.new(param)) # OK raise(CustomError.new(param), "something wrong") # OKだが、第一引数のオブジェクトのメッセージが失われる
- 新しい例外クラスは、標準例外クラス(主にStandardError)を継承し、慣例的に名称をXxxErrorのようにする。
- 1つのプロジェクトに複数例外クラスを作る場合はStandardErrorを継承した基底クラスを作成し、個々の例外クラスはそこから継承する。
出来る限り最も対象の狭い例外を処理する
- 修復プロセスがわかっている特定の例外(DB、NW接続エラー等)だけをrescueで捕まえる
- 限定された例外(詳細度の高いもの)から順にrescueする
- 汎用例外クラスをrescueで補足してはダメ。ensure節を作るか、上流で処理すること。
- rescue節内で例外が発生すると新しい例外が現在の例外を押しのけてしまう。その場合、新しい例外より元の例外をraiseするのが良い。
リソースはブロックとensureで管理する
- メモリはGCで処理されるが、メモリ以外のリソース(オープンしたファイル等)の自動的な開放は保証されない。
- ensure節でリソースを確実に開放する
- ensure節の式はbegin本体と同じスコープだが、変数が初期化されていない可能性に注意する
# ensureによるリソース管理 begin file = File.open(file_name, 'w') ... ensure file.close if file end
- ブロックで抽象化して同等の処理が可能
File.open(file_name, 'w') do |file| ... end
- File::openと同じようなブロック&ensureパターンを独自のリソース管理クラスでも実装すると良い(以下のような実装)
ensureは最後まで実行して抜ける
- ensure節の中で制御文(return, throw, next, break)などの制御文は絶対に使ってはならない
- ダメ、絶対
retryで回数上限を設け、頻度を変化させ、オーディットトレイルを残す
- 無条件のretryは無限ループに等しい危険性がある
- retry実行の境界変数はbegin節の外のスコープに定義する
- retryを使うときはaudit trail(監査証跡)を作ること。エラーのイベント連鎖を必ずログに残す。
- retry時のディレイはrescue節の中で値を(指数関数的に)増やしていくことを検討する。
スコープから飛び出したいときはraiseではなくthrowを使う
- catchとthrowは例外とは関係ない。gotoの安全versionと考える方が近い。
- 複雑な制御フローが必要な場合はraiseよりthrowを使う。throwの場合スタック上位にオブジェクトを送ることができる。
- ただしthrow、catchを多用してはならない。出来る限り単純な制御構造(return)を使う。
ネストの深いループから一撃で脱出したい場合はthrowすると良いみたいです。ただ、ネストの深いコードは循環的複雑度が高くなっちゃうのでそもそもそういったコードは避けるべきですね。
Effective Rubyは八章構成なので、前半戦終わりです。
次はメタプログラミングです。一番重そうな章です。
- 作者: Peter J.Jones
- 出版社/メーカー: 翔泳社
- 発売日: 2015/01/19
- メディア: Kindle版
- この商品を含むブログ (3件) を見る