メドピア開発者ブログ

集合知により医療を再発明しようと邁進しているヘルステックリーディングカンパニーのエンジニアブログです。PHPからRubyへ絶賛移行中!継続的にアウトプットを出し続けられるようにみんなでがんばりまっす!

RubyKaigi2018 速報 (5/31 1日目)!!

RubyKaigi2018の5/31 (1日目)の速報です!

RubyKaigi2018に参加中のメドピア エンジニアが、セッションの内容や感想を更新していきます。

最新の「RubyKaigi2018 速報!!」まとめ
2日目以降のまとめはこちらです! RubyKaigi2018 速報 !! ( 6/1 - 1つ目 ) - メドピア開発者ブログ RubyKaigi2018 速報 !! ( 6/1 - 2つ目) - メドピア開発者ブログ
RubyKaigi2018 速報 !! ( 6/2 最終日) - メドピア開発者ブログ

f:id:motsat:20180531132717j:plain

A parser based syntax highlighter (5/31 16:40〜)

Masataka Kuwabara @p_ck_

スライド:

speakerdeck.com

  • 既存のHighlighterは正規表現を使用している。
    • atomではcson内に正規表現が記載されている。
  • 既存のSyntax Highlighterの問題
    • コードを読むのが辛い
      • 正規表現を読むのが辛い。。。
    • ハイライトが完璧じゃない
      • editorによっては、ハイライトが壊れてしまう。。。
  • gemIro
    • Iroの概要
    • 今後について
      • ヒアドキュメントのハイライト(SQL等)
      • 多言語対応(Slim, Markdown)
    • よくある質問への回答
      • 3000行程度であれば、パフォーマンスは問題ない
      • Ripperのおかげで編集途中のファイルでも上手くハイライト出来る

RubyGems 3 & 4 (5/31 15:50〜)

スライド:

www.slideshare.net https://www.slideshare.net/tagomoris/hijacking-ruby-syntax-in-ruby

  • 今の最新バージョンは2.7
    • rubygems単体で2.5,2.6をリリースされることはないのでrubyのアップデートが必要
    • 2.7に関しては、メンテナンスがされるのでrubyのアップデートは不要
    • ruby1.8をサポート対象としてたり、bundlerとの兼ね合いがあるのでサポートが結構たいへん。。。 *Ruby 2.6.0には3.0、2.7 or 3.0には4.0が入る予定
  • RubyGem3
    • 非推奨メソッドを削除
      • Gem::Duplicateを使うと非推奨の警告を簡単に出せて便利
    • サポートがruby2.2以上に
    • bundlerとdependency resolverのversionをあわせたい
    • gem codesearchでgem内の検索(def thenがけっこうある…)
    • all-ruby 世の中のすべてのrubyであるコードを実行できテストや調査
  • RubyGem4
    • 現状あまり機能していないdefault optionを使いやすくする
    • --user-installをdefaultにするかも
    • update rdocでconflictが発生しないように修正する

Fast Numerical Computing and Deep Learning in Ruby with Cumo (5/31 15:50〜)

Naotoshi Seo @sonots

スライド:

speakerdeck.com

感想

  • pythonで良いのでは?なんて思っても言ってはいけない
  • 楽しそうではあるが、pyth.....

memo

  • cumoとはcudaで数値計算するライブラリ
  • scientific computation in ruby
    • pythonだとnumpy, cupy, pycudaとかあります
    • rubyだとnumo, cumok, rbcudaとかができてきている
      • pycallというのもあり
  • CUDA基礎
    • GPU速い(30日(cpu)→4日(gpu))
    • GPUはコア数が多いので
    • 行列演算とか強い
  • CUDA architecture
    • GPU memory確保
    • GPU memoryに転送
    • 計算
    • GPU memoryから転送
    • なんか使う
  • 非同期な命令が多く、同期が必要な命令のところで待ち合わせる
  • cumo
    • numo compatibility
      • コードの一括置換でそのままGPU化できる
    • cuBLAS by NVIDIAで内積計算をする
      • fortlanベースなので、メモリレイアウトが微妙に違う(行/列)
    • Memory pool
      • cuda mallocが遅いので
      • best fit algorithm
        • 単純なfree listではなくfree listをsizeで分けている
      • best fit with coalescing (BFC)
        • みんなのmallocはこれを使っている
      • JIT compiling
        • NVRTC (NVIDIA Runtime Compilation)を使ってる
  • numo vs cumo
    • element-wise 40倍速い
    • dot product 158倍速い
    • red-chainerで試したら…
      • 20倍速い(memory poolなし)
      • 75倍速い(memory poolあり)
  • GPUとGCと相性悪いのでは?
    • すぐ解放したいメモリがあってもGPUを待たなければならない
      • GPU側の使用メモリ量はGC発動に影響を及ぼさない
      • pythonだと参照カウントだから問題ない!
    • #freeメソッドを追加してごまかす
  • mkmfの機能たりない
    • コンパイラを複数種類作れない
    • 拡張子によってコンパイラを分けるproxy的なものをコンパイラに指定して回避
  • broadcast operation遅い
    • kernelを叩く回数をへらす必要がある
    • でかい行列を作って1回で済ませて回避(?)
  • Inplace math operations
    • a += ba = a + bでやりたくない(一時オブジェクトを作りたくない)
    • pythonだと参照カウントを見てうまく一時オブジェクトを作らないようなことをしているらしい

All About RuboCop (5/31 14:50)

Bozhidar Batsov @bbatsov

speakerdeck.com

内容

  • Lintはcommon senseの置き換えにはならない
  • Rubocopの歴史
    • 初期rubocopは正規表現でチェックしていた
    • 2013年4月の怒涛のアップデート
    • ripperからparser gemへ
  • 過去の話から未来の話
    • 1.0に向けてrailsとかパフォーマンス関連のcopをコアから外す予定
  • 他の話
    • Emacsはいいぞ
    • vimmerはspacemacsを使おう

感想

  • ついにRubocopが1.0になりそうな雰囲気!
  • spacemacesいいよね

TTY - Ruby alchemist’s secret potion(5/31 14:40〜)

Piotr Murach @piotr_murach

内容

感想

  • 簡単に綺麗なCLIのインターフェースを作れそう。
  • 機能毎にgemが別れているので、プログレスバーだけ等必要なものだけ使うとかも出来そうだから導入ハードルが低そう。
  • 英語はあまり分からなかったけど、コードが表示されていてなんとく内容が理解出来てよかった・・・!
  • ライブコーディングあったけど、コードもわかりやすい(not crazy!!!)
  • CLIで色々面白いことを簡単にできるので、ちょっと触ってみたくなった
  • 外人がさらりと日本語で自己紹介していて、かっこいいと思った

Hijacking Ruby Syntax in Ruby(5/31 13:50〜)

事前知識

  • Binding
    • 変数の設定(名前と値)をとっておく機能
    • 既存の変数を上書きすることも出来る。
  • TracePoint
    • Rubyの組み込みクラス
    • 例外の情報を集めるのが便利とあるが、他にもたくさんの情報を集められる。
    • Bindingが呼び出せるので、任意の行の変数の上書きが出来る!
  • Refinement
    • 安全な局所的なメソッドを追加出来る仕組み、安全にモンキーパッチやクラス拡張が行える。

BindingやTracePointを使ってRubyをHackした話

www.slideshare.net

RubyをHackするのは楽しい!でもデバッグがとてもつらい・・・! 「覚悟は出来てるか?俺は出来ている!」

A practical type system for Ruby at Stripe(5/31 13:50〜)

  • ストライプではpureなRubyを使っている

  • 型チェックのツールをオープンソース(にする予定)プロジェクトとして作ってる。

  • いろんな型チェック機能の詳細の説明

  • いろんなユースケースとか知りたいから興味ある人は連絡してね

BlockChain * Ruby の話(Bancorというgemを作成したよって話) (5/31 13:00〜)

Yuta Kurotaki @kurotaky
https://github.com/kurotaky/bancor

  • Ethereum(イーサリウム)などの仮想通貨は、「Smart Contract」という仕組みを基盤にしている(Smart Contractを使って、自動で売買契約の実行を行えるようになる)
  • 「仮想通貨A * 1コインを15$で売りたい」という人がいた時、「15以上で仮想通貨Aを書いたい」というユーザーがいないと、取引が完結しない。(流動性が下がる。) 特に、マイナーな仮想通貨間の取引では、なかなか流動性が上がらない。
  • ユーザーの売買の欲求、総発行量・準備金から、価格を流動的に算出するものが「Bancor Protocol」(Bancorはバンコールと読む)。これを、なるべく早く正確に計算できるようにしたい。
  • 「Bancor Protocol」をもとに、売買できる仕組みをRubyで作るためのgemを作った。そして、RailsのWebアプリで動かしてみるまで実施した
  • 小数点以下の演算とかまだまだざっくりなので、きちんと計算できるようにしてサービス化していきたい

感想

  • Rubyでブロックチェーンを活用したアプリは、他の言語に比べて少ないので、どんどん盛り上げていきたい(いってほしい)。

Analyzing and Reducing Ruby Memory Usage(5/31 13:00〜)

Aaron Patterson @tenderlove
Rubyによるメモリ使用量の減少。

  • コードを読む
  • mallocをスタックトレースする
  • Objectspace、allocation_tracerはRubyオブジェクトしか判定できない
  • Malloc Stack Loggingでrubyプログラムが行ったmallocを判定できる → 対象プログラムに対し行うとファイルにalloc freeなどの情報を出力できる → 誰がmallocしたかはわからない…
  • ファイル読み込みの改善
    → 一度読みこんだファイル情報をキャッシュしておく
    → 「同じファイル」は相対パスの関係でいろいろな文字列になる
    → それらをそれぞれ文字列を作りmallocしないで、全体文字列からのポインタ?のようなものだけ持って削減する
  • あなたならこの機能にいくらはらいますか?5000円?10000円?5000兆円?
  • Ruby2.6にアップグレードすれば無料!
  • なんと5年前に同じようなpatchが書かれていた…(patch書くときはissueの調査から!)
  • バイトコードの命令、オブジェクトをパラメータを切り分け、オブジェクトをバイトコードにpushされたときにバイトコードをGCにマークさせる(これをしないと命令とオブジェクトの切り分けが難しい)
  • あなたならこの機能にいくらはらいますか?5000円?10000円?5000兆円?
  • Ruby2.6にアップグレードすれば無料!
  • Ruby2.6にアップグレードしてください!

感想

メモリ改善、こういった細かいところで知恵がしぼられて行われているんだとしみじみ思った。 日本語でのセッションでしたが全く違和感なく聞けてすごい…

Keynote Yukihiro "Matz" Matsumoto@yukihiro_matz ( 5/31 10:30〜)

  • 名前重要。命名にこだわることで開発効率をよくすることを支えている。
    パフォーマンスカイゼン、重要。
    プログラミングは物理的な制約がないので概念で構成される
    名前付けには「振る舞い」、「プロジェクト、コミュニティ」の2パターンがある。
  • メソッド名の悪い例
    yield_self(会場笑) メソッドを使って何がしたいかがわからない
    →then
    Rubyという名前にしてよかったけど、それはbefore googleだったから
     悪い例:
      Go(会場大爆笑)
      Swift

  • メソッド名のいい例:
    Ruby on Rails(スペースで単語)
    Jupyter(ちょっといじる)
    Hanami, Nokogiri, Kaminari(会場笑)

  • いろいろなエンジニアが実行速度のカイゼンを続けている。

  • コミュニティ重要。非互換性を生むような進化はコミュニティの停滞に繋がり、言語の繁栄の弊害となる
  • Rubyは上記3点を大事にしながら進化し、Rubyistをハッピーにしていく。  
  • Rubyistの生産性・幸福度を重視した松本さんの思いを感じたKeynoteでした。

  • 今回のテーマはことわざ

  • 塞翁が馬、名は体を表す等のことわざを用いてRubyの「いままで」と「これから」をMatzから話がありました。
  • JITコンパイラを使って高速化やバージョンアップアップによるコミュニティの分断を防ぐ等の今後のRubyの見通しが伺えました
  • 塞翁が馬=人生いいことも悪いこともある
    2013-2018のRubyはハイプ・サイクルでいう安定期のようなものだが、Ruby is Deadと言われるようになった。毎年言われている。Ruby is Dead Every Year(会場笑)
  • ポール・グラハム「簡潔さは力なり」というエッセイ 豆知識:MatzはRubocopのデフォルトルールが嫌い(笑)

  • 名は体を表す=名前重要

  • 時は金なり
    Rubyはパワフルで簡潔で〜なので時間を有効活用できる

  • パワフル:便利なメソッドがたくさんある、ライブラリ、gem、フレームワーク、2.days.ago
    コミュニティが大きくなっているので助けてくれる人が多い

tech.medpeer.co.jp


(☝︎ ՞ਊ ՞)☝︎是非読者になってください


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html

RubyKaigi2018に参加してきます!!

こんにちは!4月から参画したエンジニアの森田です。

RubyKaigi2018、いよいよ明日からですね!

rubykaigi.org

※メドピアもGoldSponsorとして、協賛しています。

私を含めて総勢11人(希望者全員参加!🎊)のエンジニアで参加費、交通費、宿泊費、懇親会費と、なにからなにまで会社からの補助で参加します🙏
(なんとメドピアRailsエンジニアの参加率80%!👀留守を守ってくれるエンジニアの皆様、ありがとうございます…!) f:id:akinorifukumura:20180530001233j:plain

当日は、ゆるきゃら(?)であるメドベアをプリントしたパーカーを着ていますので懇親会等でお会いした際には、ぜひお話しましょう! f:id:madogiwa0124:20180529163312j:plain

会場ではステッカーも配ってますので、よろしければもらってください🐻 f:id:madogiwa0124:20180530104601j:plain

またRubyKaigi中に各セッションの速報をリリース予定です📝
当日はこちらも、チェックしてみてください !

それでは、当日みなさんにお会いできることを楽しみしています🙌


(☝︎ ՞ਊ ՞)☝︎是非読者になってください


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html

ActiveModelSerializersを使った所感

こんにちは。メドピアエンジニアの保立です。

メドピアでは、ドクター向けに運営している「MedPeer」のiOSアプリを3月15日にリリース致しました!!!
今回は、iOSアプリ開発の過程で、APIの実装にActiveModelSerializersを使ったので、そこで得た知見を書きます。

ActiveModelSerializers を使った理由

json形式のレスポンスを返却する場合、jbuilderを使うケースも多いのではないでしょうか。
メドピアでも、今まで外部へのAPIにはjbuilderを使用していました。
しかし、iOSアプリ用のAPIでは、以下のメリットを考慮して、ActiveModelSerializersを使うことにしました。

メリット① 複雑なjsonを返す際に、ActiveModelSerializersの方が、レスポンスが早い

スマートフォン用のAPIでは、なるべく少ないリクエストで画面の描画に必要なすべての情報を返却したいため、何重にも入れ子になるjsonを返却することが多いと思います。そのため、viewをレンダリングするjbuilderでは汎用的なオブジェクトはpartial化することが求められますが、partialを呼び出すのに時間がかかってしまいます。
一方、ActiveModelSerializersでは、レスポンスの内容を定義するserializerにhas_manyhas_oneを使って、関連するオブジェクトを指定することができます。これにより、jbuilderよりも素早くレスポンスを返すことができます。

メリット② DSLな書き方をしなくてよい

jbuilderの書き方は、直感的に分かりづらい記法になるケースがあります。

json.title  @post.title
json.body @post.body
# {"title": "タイトル", "body": "本文"}


json.post @post, :title, :body
# {"post": {"title": "タイトル", "body": "本文"}}}

# これも同じ内容を返す(こっちの方が少し分かりやすい)
json.post do
  json.title @article.title
  json.body @article.body
end

さらに、partialを読んだり、if文などの分岐が入ると、より分かりづらくなります。
ActiveModelSerializersでは、Rubyの記法に則って書けるため、分かりやすいコードになります。

以上の点から、メドピアのiOSアプリ用APIとして、ActiveModelSerializersを採用しました。

使用例

ActiveModelSerializerは以下のように使用します。

class PostsController < ApplicationController
  def show
    @post = Post.find(params[:id])
    render json: @post, serializer: PostSerializer
  end
end


class PostSerializer < ActiveModel::Serializer
  attribute :title

  # 別名をつけたい時はkeyを使用する
  attribute :created_at, key: :timestamp

  # has_manyやhas_oneで関連するオブジェクトを指定する
  has_many :comments,  serializer: CommentSerializer

  # メソッドを呼び出すことも可能
  attribute :published

  def published
    object.published_at.present?
  end  
end

class CommentSerializer < ActiveModel::Serializer
  attribute :body
end

# レスポンス
{  
  title: "タイトル",
  timestamp: "2018-01-01T00:00:00+09:00",
  published: true,
  comments: [
    {
       body: "コメント" 
    }  
  ]
}



並列関係の複数Modelに紐づくAPIを返却したい場合もあります。 以下の例は、アプリ起動時など、Masterデータを取得する時のコードです。

class MasterDataController
  render json: MasterData.new, serializer: MasterDataSerializer
end


class MasterData
  def first_master_data
    @first_master_data ||= Master::FirstMasterData.all
  end

  def second_master_data
    @second_master_data ||= Master::SecondMasterData.all
  end
end


class MasterDataSerializer
  has_many :first_master_data
  has_many :second_master_data

  class FirstMasterDataSerializer
    attributes :id, :name
  end

  class SecondMasterDataSerializer
    attributes :id, :name
  end
end

使ってみた感想

書いていて、ものすごくつまずくということがなかったです。
ActiveRecordの延長のような作りなので、つまずくことなく書くことができました。
またGitHubのドキュメントが豊富なので、基本的にわからないことがあってもドキュメントをみれば解決しました。
コード量はjbuilderより多くなるので、「すぐに実装したい」「複雑なjsonを返す必要がない」場合はjbuilderでもいいと思います。 逆に、長い間運用したり、拡張する可能性があるAPIを用意する場合は、ActiveModelSerializerおすすめです^^

おまけ(Active Model Serializer と jbuilder のパフォーマンス比較)

posts#showアクションにて、postsを1件とhas_many関係のcommentsを20件をレスポンスに設定するケースで比較します。

jbuilder

# app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def show
    @post = Post.find(params[:id]).preload(:comments)
  end
end

# app/views/posts/show.json.jbuilder
json.title  @post.title
json.page_views do
  json.partial! partial: 'comment', collection: @post.comments, as: :comment
end

# app/views/posts/_comments.json.jbuilder
json.body comments.body

実行結果は以下のようになります。

Processing by PostsController#show as JSON
Posts Load (1.0ms)  SELECT `posts`.* FROM `posts` LEFT JOIN `comments` ON `posts`.`id` = `comments`.`post_id` 
Rendered posts/_comments.jbuilder (0.3ms)
Rendered posts/_comments.jbuilder (0.3ms)
Rendered posts/_comments.jbuilder (0.3ms)
(commentsの数だけ続く)
Rendered posts/_comments.jbuilder (0.3ms)
Rendered posts/show.json.jbuilder (47.2ms)
Completed 200 OK in 87ms (Views: 49.6ms | ActiveRecord: 7.5ms)

Active Model Serializers

# app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def show
    @post = Post.find(params[:id]).preload(:comments)
    render json: @post, serializer: PostSerializer
  end
end

# app/serializers/posts_serializer.rb
class PostSerializer < ActiveModel::Serializer
  attribute :title
  # has_manyやhas_oneで関連するオブジェクトを指定する
  has_many :comments,  serializer: CommentSerializer
end

class CommentSerializer < ActiveModel::Serializer
  attribute :body
end

実行結果は以下のようになります。

Processing by PostsController#show as HTML
Posts Load (1.0ms)  SELECT `posts`.* FROM `posts` LEFT JOIN `comments` ON `posts`.`id` = `comments`.`post_id` 
[active_model_serializers] Rendered PostSerializer with ActiveModelSerializers::Adapter::Attributes (11.23ms)
Completed 200 OK in 41ms (Views: 9.7ms | ActiveRecord: 7.9ms)

と、いうことで2倍以上の速度でレスポンスを返すことができました。
実際のMedPeerアプリでは、3重4重に入れ子にしたjsonを返却することがあるため、Active Model Serializers の恩恵は計り知れないものがあります。


(☝︎ ՞ਊ ՞)☝︎是非読者になってください


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html

社屋を移転したのでオフィスの壁をデザインした話

f:id:umeccco:20180404142238j:plain

こんにちは。デザイナーの松村です。

この春、メドピアはめでたくオフィスを銀座に移転しました!ワーワー! medpeer.co.jp

今回はオフィス移転をきっかけに会社の沿革をイラストに起こしたやり取りが「ザ・メドピアのものづくり」という感じだったので、デザイナー活動記としてブログにしたためてみました。

ラフを作ろう

広報の藤野女史から、WALLコンセプトの共有を受けラフ案を作成するながれに。 MedPeerの転換期には必ず医療に関する事件や法律改正が絡んでいるので、MedPeerのこれまでの歩みとの相関関係を見せたい、というのがコンセプトでした。

コンセプト時点では「MedPeerの歴史」がフィーチャーされていたのですが、「医師をサポートし、患者を救う」というMedPeerコンセプトから考えても、いっそMedPeerだけじゃなくてもっと広いレンジにしたほうがハマるのではないか、と考えて作ったのがこちらのラフ案。

f:id:umeccco:20180129144708j:plain f:id:umeccco:20180130123132j:plain
f:id:umeccco:20180130123104j:plain f:id:umeccco:20180130123115j:plain

いかがですかこのやる気を引き出してくれる誉め殺しコミュニケーション。

いいもの作って期待に答えるしかない!!て感じですよね。

〜ちなみにメドベアとは、MedPeer内で連載中の4コマ漫画です。〜

医師の声を取り入れよう

ラフ案のプレビューで、代表医師の石見先生に「上医は国を医し、中医は人を医し、下医は病を医す」という言葉に影響を受けたのでそれを入れたい、というフィードバックを受けました。

こうなってくるとせっかくなので医師会員の皆様の声も入れたいなあ……ということになり、 石見代表にMedPeer内の 「FORUM Q&A Life」で募集してもらいました。

f:id:umeccco:20180129144542j:plain

ワクワクしながら投稿を待ちわびる我々。

f:id:umeccco:20180308132217j:plain

おかげさまで、最終的には70以上の案をお出し頂きました。 これには石見代表も感激。

f:id:umeccco:20180308132156j:plain

いよいよ完成!

よーしいよいよ入稿だ〜!と思ったら大きな罠が。 f:id:umeccco:20180130125550p:plain

f:id:umeccco:20180129144427j:plain

アイレベル設定を完全に失念していました。

こんな時にもワハハノリで流してくれるので変にストレスを溜めずに修正することができます。

f:id:umeccco:20180130121903p:plain

というわけで最終的に調整して完成!

f:id:umeccco:20180404142415j:plain

対面には石見セレクションの医学書が。

f:id:umeccco:20180404142335j:plain

こんな感じで弊社では、部署や役職の垣根なく意見を交換してものづくりを行なっています。 (今回のように社内外の医師にご意見を頂き、一緒に作り上げることもしばしばあります。)

MedPeerのデザイン

今回は弊社の思想を表現するため、よりコンセプチュアルなデザインとなりましたが、 webサイトやイベントのデザインも「医師の体温」が伝わるデザインに、と調整を行なっています。

MedPeerのサイトコンセプトはズバリ「臨床の役に立つ」サイト。 今年からはそれに加えて、臨床知識を得られながらも、患者さんに向き合うための英気を養える、医師にとって居心地の良い場の再構築を目指しております。

「自分ならこういうことで実現するかなあ」と思った皆さま。

是非一度、お話をお聞かせください。

MedPeerでは共に働く仲間を募集しています。

おまけ

今回は自動販売機もラッピングできるということで、欲望溢れるデザインにしてみました。(ヘルステックカンパニーの意識とは…)

f:id:umeccco:20180404143127j:plain

MedPeerの会議室 Patioはイベントスペースとしても貸し出しております。 勉強会などの会場でお困りの方はお気軽にお声かけください。

f:id:umeccco:20180404143225j:plain

スカイツリーも見えますよ〜

f:id:umeccco:20180404143149j:plain

メドピアではIT勉強会での会場を提供いたします

こんにちは。メドピアCTOの福村です。
RubyKaigi2018@仙台が横浜開港祭と被っていて家族との調整で頭を抱えている今日このごろです。
開発合宿の計画も進めており今年も(すでに4月ですが)熱い1年になりそうです。
好きな季節は夏です。

さて、メドピアグループ(メドピア、フィッツプラス、Mediplat)は、2月26日から拠点を銀座に移し1フロアでグループシナジーを生みながら事業に邁進しております。 medpeer.co.jp

今回の移転でイベントスペースを作ったのでIT業界への貢献の思いも込め、 ITや医療系の勉強会に積極的にこのスペースを提供していこうという運びになりました。

イベントスペース

https://s3-ap-northeast-1.amazonaws.com/prod.cojp.wp.media.press/press/wp-content/uploads/2018/04/03152607/patio.jpg

アクセス

〒104-0061 東京都中央区銀座6-18-2 野村不動産銀座ビル11階
東銀座駅 徒歩3分

収容人数

50名

設備

  • プロジェクター
  • ゲストWi-Fi
  • 椅子・机
  • マイク
  • 電源タップ

お問い合わせ先

info@medpeer.co.jp

  • イベントの概要
  • ご希望の日時(社員立ち会いが必要な都合上、平日のみとさせていただいております)
  • 参加予定人数

※ 営利を目的とした勉強会については、お断りをさせていただくことがございます

勉強会開催します!

JapanTaxiさんと4/25(水)にRubyをテーマに勉強会を実施します!
Ruby/Rails開発全般で面白いテーマが揃っていると思います。 是非是非新オフィスに遊びに来てください。

medpeer.connpass.com

会場を提供した勉強会

移転して1ヶ月くらいですがいろいろなIT系の勉強会を開催しております!

savanna.connpass.com

ginzarb.doorkeeper.jp

medpeer.connpass.com

medpeer.connpass.com

銀座付近で勉強会したいなぁと思った際はぜひともお気軽にお声がけください。

※ 追記(2018/11/27)設備に「マイク」と「電源タップ」を追加しました。


(☝︎ ՞ਊ ՞)☝︎是非読者になってください


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html

トピック型のモバイルPush通知をRails + Amazon SNSで実装する

こんにちは。メドピアにWebエンジニアで入社して約6ヶ月の佐藤です。

メドピアは2/26から銀座に移転しました。 銀座に移転しても花粉からは逃げられませんでしたが 、移転後はなぜか空気清浄機が増えて助かっています。

書いてある事

Amazon SNSの複数ユーザーに一度に通知を送る「トピック」を使ってRailsでPush通知を実装した際の処理フローを主に書いてあります。

f:id:motsat:20180313115508p:plain:w500

トピック機能を使わず、Amazon SNSのプッシュ通知のみを実装した場合はわりとシンプルですが、

  • トピック購読(subscribe)
  • トピック購読の解除(unsubscribe)

等のトピック関連の処理が加わってくると状態管理が複雑になってきます。

また、

  • トピック
  • エンドポイント
  • ARN(Amazon Resource Name)

などAmazon SNSやAWS上での用語も理解する必要があるため、その説明も簡単に入れました。

今後Amazon SNSを使ってpush通知(とトピック機能)を実装する上で何かしら参考になれば幸いです。

また、ブログ内のサンプルコードは下記のリポジトリに格納してあります。

サンプルコード

ブログ内は処理の抽象化された部分が書かれているためaws_sdkの利用部分やDBへの反映部分は出てきませんが、 リポジトリ内の他ファイルにはそこも含めたものが上げてあります。

(ただ、弊社事情部分を削ってあるのでそのままでは動かない参考的なものです)

Amazon SNSのモバイルプッシュ通知

いろいろなデバイス(プラットフォーム)に、送信側はそれほど処理を変えずに簡単にプッシュ通知できるサービスです。

他にもこんな特徴があります。

  • 送信可否状態をAmazon SNS側で管理

 プッシュ通知の送信が失敗すると、状態変更(後述)されるまで送信しない

  • 個別の送信先、またトピック機能を使う事で複数の送信先にも一度に送信できる

 詳細  https://docs.aws.amazon.com/ja_jp/sns/latest/dg/SNSMobilePush.html

Amazon SNSのトピック

トピックは、日本語訳で「話題」や「論題」です。

Amazon SNSのトピック機能は、事前にトピック(Topic)を作成しておき、 ユーザーはそのトピックを購読(subscribe)する事でそのトピックへの通知を受け取ることが出来るようになります。

複数ユーザー(アプリ)を紐付けでおけば、APIの利用側はそのトピックに対して送った通知メッセージが複数ユーザーに届くようになります。

大量ユーザーへのPush通知も、トピックに紐付ければ1回の送信で行えるという事です。

https://docs.aws.amazon.com/ja_jp/sns/latest/dg/CreateTopic.html

アプリ側の機能概要

今回実装したアプリの、ユーザーから見たトピックの選択イメージはこんな感じです。

  • 複数プラットフォーム(iOS+Android)でプッシュ通知を受け取れる
  • ログアウト時にはpush通知が来ないようになる
  • トピックごとに通知のON/OFFができる

アプリ側の設定画面の雰囲気です。

f:id:motsat:20180312205655p:plain

Amazon SNS側作業

AWS マネジメントコンソールで事前に下記の作業を行います。 (APIのユーザー管理やアクセス権限周りも必要あれば設定)

  • アプリケーション作成
  • トピック作成

これらはAPIで作成することもできますが、今回は事前に作成する形にしたのでAWS マネジメントコンソールでの作業を前提とします。

アプリケーション作成

プラットフォームと対になるものです。 iOS、Android分であれば計2つを作成します。

必要なもの

  • iOSのp12ファイルとそのパスワード (Androidは試していませんが、GCMアクセスキーが必要となるはずです)


下記はiOSのアプリケーション作成画面です。

f:id:motsat:20180311214507p:plain

  • アプリケーション名
  • iOSの場合はDevelopmentまたはProductionの選択
  • p12ファイルパスワードを入力

です。(証明書はp12ファイルから勝手に入力されます)

これで、アプリケーションの「ARN」である、「application_arn」が生成されます。(APIでの送信時に必要)

ARN(Amazon Resource Name)は、AWS上に何かリソースを作成した時に与えられる名前です。

この先に出てくる他のリソース(トピック、エンドポイント等)にもARNが設定され、 同じくAPIでそのリソースを指定する時などに使います。

トピック作成

トピックA、トピックBであれば計2つです。 iOS + Androidの2つのアプリケーションがある場合でも、通知単位を分けないのであればトピックは同じ物を利用します。 (逆に、iOSとAndroidで通知単位を分けたければ別に作ります)

f:id:motsat:20180311214515p:plain

これで、トピックの「ARN」である、「topic_arn」が生成されます。(APIでの送信時に必要)

Rails側のモデルのイメージ

今回の実装例に出てくるモデル(DB)については、下記のようなイメージです。

f:id:motsat:20180313113150p:plain

topic_1_…などtopicごとにカラムを持っていますが、topicごとにレコードを持つ形なども良いと思います。

また、実際にはプッシュ通知のメッセージを格納しているモデルなどもいろいろあるのですが、説明用に省きます(実際のものはリポジトリをご覧下さい)

user_push_notification_settings

アプリ(ユーザー)の通知許可フラグを持つテーブルです。 ユーザーに対して1レコード持つ形です。iOS側で反映したらAndroid側も同じ設定となります。

  • user_id

 ユーザーID

  • topic_1_enabled

 トピック1が有効かどうか

  • topic_2_enabled

 トピック2が有効かどうか

user_push_notification_tokens

アプリ(ユーザー)のデバイストークンと、それに関連付けられるARN(Amazon Resource Name)を保存するテーブル。 ユーザーに対し、アプリを利用するプラットフォーム分のレコードを持ちます。

下記の属性を持つイメージです。

  • user_id

 ユーザーID

  • mobile_platform

 プラットフォーム(ios or android)の指定

  • device_token

 アプリから取得したトークン

  • endpoint_arn

 Amazon SNSのendpoint作成時に取得するARN(Amazon Resource Name)

  • topic_1_subscription_arn

 Amazon SNSのトピック1をsubscribeした時に取得するARN(Amazon Resource Name)

アプリサーバー(Rails)の実装

Amazon SNSのモバイルPushとトピック通知を使うためにRails側の実装部分です。

  • アプリからトークンを取得
  • 取得したトークンをAmazon SNSのエンドポイントに紐付ける
  • そのエンドポイントをトピックに紐付ける
  • また、不要になったらエンドポイントやトピックとの紐付けを削除する

という処理です。 主に下記リクエスト時の処理が必要になります。

  • トークンの新規保存、更新時
  • トークンの破棄(ログアウト)時
  • トピックの購読ON/OFF切り替え時

トークン(token)の変更時

f:id:motsat:20180311213737p:plain

1. アプリからtokenを取得

まずはアプリからtokenを取得し、Railsサーバーにリクエストします。

以下、実コードはAmazon SNSへのリクエスト処理はsidekiqで非同期処理で行っていますが、説明上Railsサーバーとしています

endpoint未作成? => YES の時

2. Amazon SNSのcreate_endpointを実行

まずはAmazon SNS側にendpointを作成します。

作成時に、そのendpointを表す 「endpoint_arn」がレスポンスに含まれます。

endpoint_arnはpush通知やトピックの紐付け(subscribe)を実行する時に必要になるのでDB等に保存します。

3. Amazon SNSのsubscribeを実行

2.で取得したendpoint_arnに、トピックの紐付け(subscribe)を行います。

subscribeのレスポンスには、「subscription_arn」が含まれます。 これは、トピックへの送信時には必要ありませんがunsubscribe時に必要になるのでDB等に保存します。

また、一度に複数トピックをsubscribeするAPIは無いため、トピック分subscribeを行う必要があります。

endpoint未作成? => NO の時

2.Amazon SNSのset_endpoint_attributesを実行

すでに作成されたendpoint内の属性を更新する、Amazon SNS のset_endpoint_attributesを実行します。 下記のパラメータを更新します。

  • Token

 アプリから送信された最新のtokenに上書き保存します。

  • Enabled

 送信可否状態です。trueまたはfalseです。  endpoint作成時の初期値はtrueですが、アプリへのPush通知が失敗すると自動的にfalseに更新されます。

アプリがPush受信可能な状態だと信じてtrueを設定します(アプリから送信された最新のtokenなので)。

サンプルコードです。 (メソッド化されているので、Amazon SNS APIの実行や具体的な処理の詳細はリポジトリをご覧ください)

  # トークン(token)の変更時
  def on_updated_token(user_push_notification_token)
    has_device_token = user_push_notification_token.device_token.present?
    has_endpoint_arn = user_push_notification_token.endpoint_arn.present?

    if has_endpoint_arn
      if has_device_token
        set_endpoint_attributes(user_push_notification_token) # b-2.Amazon SNSのset_endpoint_attributesを実行
      else
        delete_endpoint(user_push_notification_token)
        unsubscribe_all_topics([user_push_notification_token])
      end
    else
      return unless has_device_token
      create_platform_endpoint(user_push_notification_token) # a-2. Amazon SNSのcreate_endpointを実行

      on_updated_setting(user_push_notification_token.user) # a-3. Amazon SNSのsubscribeを実行(後述のトピック通知設定の時と同じ処理)
    end
  end

private

  def set_endpoint_attributes(user_push_notification_token)
     requester = AwsSnsRequester.new
     requester.set_endpoint_attributes(user_push_notification_token.endpoint_arn,
                                       user_push_notification_token.device_token)
  end

  def create_platform_endpoint(user_push_notification_token)
    requester = AwsSnsRequester.new
    response = requester.create_platform_endpoint(user_push_notification_token.user_id,
                                                  user_push_notification_token.device_token,
                                                  user_push_notification_token.mobile_platform.value)
    user_push_notification_token.endpoint_arn = response.endpoint_arn
    user_push_notification_token.save
  end

  def on_updated_setting(user)
    user_push_notification_setting = user.user_push_notification_setting
    UserPushNotificationSetting::SUBSCRIBE_TOPICS.each do |topic|
      if user_push_notification_setting.enabled_by(topic)
        subscribe_topics(topic, user.user_push_notification_tokens)
      else
        unsubscribe_topics(topic, user.user_push_notification_tokens)
      end
    end
  end

トークンの破棄(ログアウト等)時

f:id:motsat:20180311214119p:plain

1.アプリからtoken削除リクエストを送信

アプリからのログアウト時など、Railsサーバーに削除リクエストを送信します。

2.Amazon SNSのdelete_endpointを実行

作成済みのAmazon SNS上のendpointを削除します。 DB上に保存されたendpoint_arnも同時に削除します。

3.Amazon SNSのunsubscribe を実行

delete_endpointでendpointを削除しても、自動でそれに関連付けされたsubscription_arnも削除されるわけではありません(自動でやってほしい…)

なので、subscribeをしていた場合には、unsubscribeをしておく必要があります。

ドキュメントにも注意書きがあります。

https://docs.aws.amazon.com/ja_jp/sns/latest/api/API_DeleteEndpoint.html

When you delete an endpoint that is also subscribed to a topic, then you must also unsubscribe the endpoint from the topic

DB上に保存されたsubscription_arnも同時に削除します。

実装を一部抜粋してコメントを追記したものです。(メソッド化されているので、Amazon SNS APIの実行や具体的な処理の詳細はリポジトリをご覧ください)

※ トークンの更新時とコードと同じです。delete_endpointが追加されています。

  def on_updated_token(user_push_notification_token)
    has_device_token = user_push_notification_token.device_token.present?
    has_endpoint_arn = user_push_notification_token.endpoint_arn.present?

    if has_endpoint_arn
      if has_device_token
        refresh_attributes(user_push_notification_token) 
      else
        delete_endpoint(user_push_notification_token) # 2.Amazon SNSのdelete_endpointを実行
        unsubscribe_all_topics([user_push_notification_token]) # 3.Amazon SNSのunsubscribe を実行
      end
    else
      return unless has_device_token
      create_platform_endpoint(user_push_notification_token)

      on_updated_setting(user_push_notification_token.user)
    end
  end

private
  def delete_endpoint(user_push_notification_token)
    AwsSnsRequester.new.delete_endpoint(user_push_notification_token.endpoint_arn)
    user_push_notification_token.endpoint_arn = ""
    user_push_notification_token.save
  end

トピックの購読ON/OFF切り替え時

トピック購読の切り替えはかなり単純です。 購読状態を見て、subscribeまたはunsubscribeするだけです。

f:id:motsat:20180311214133p:plain

1.アプリからトピックの選択状態を送信

トピックごとにtrue/falseなど、subscribeまたはunsubscribeするための状態を送信します。

2.Amazon SNSのsubscribe/unsubscribeを実行

アプリから送信されたトピックの選択状態に合わせ、Amazon SNSのsubscribeまたはunsubscribeを実行します。

DB上に保存されたsubscription_arnにも同時に反映します。

サンプルコードです。 (メソッド化されているので、Amazon SNS APIの実行や具体的な処理の詳細はリポジトリをご覧ください)

※トークンの更新時に行うコードと同じものです。

  def on_updated_setting(user)
    user_push_notification_setting = user.user_push_notification_setting
    UserPushNotificationSetting::SUBSCRIBE_TOPICS.each do |topic|
      # 2.Amazon SNSのsubscribe/unsubscribeを実行
      if user_push_notification_setting.enabled_by(topic)
        subscribe_topics(topic, user.user_push_notification_tokens)
      else
        unsubscribe_topics(topic, user.user_push_notification_tokens)
      end
    end
  end

その他

テスト(RSpecなど)の実装はClientStubsが便利

外部APIが絡むテストを実行する場合、スタブやモックなどが必要になる事も多いですが、aws_sdkには標準でスタブに関する機能があります。 これを使う事で、テストの実装もかなり楽になりました。 https://docs.aws.amazon.com/sdkforruby/api/Aws/ClientStubs.html

例えば、RSpecでのテスト実行時は全ての処理をスタブとするのであれば、spec/rails_helper.rb等に下記のように記述します。

# テスト実行時、aws_sdkは全体的にスタブにする
if Rails.env.test?
  Aws.config[:stub_responses] = true
end

aws_sdkでテスト時のレスポンスなどを任意のものにする

また、テスト時に場合によってはレスポンス内容を変えたくなったりする事もあると思います。 その場合は、下記のように一部のレスポンスを指定する事ができます。

aws_sdkのcreate_platform_endpointを実行した時にendpoint_arnが「new_endpoint_arn」としてレスポンスされるようにしたものです。

sns_response = Aws::SNS::Types::CreateEndpointResponse.new(endpoint_arn: "new_endpoint_arn")
Aws.config[:sns][:stub_responses][:create_platform_endpoint] = sns_response

まとめ

実装面やその他ふくめて、良かった所/つらかった所です。

良かった所

  • 大量配信の負荷をあまり気にしなくて良い

 トピック機能を使った場合は、大量配信時にもAPIを使う側は1件送信するだけ。

  • 事前準備が楽

 Amazon SNS側のアプリケーションの設定やトピックの設定も、シンプルで設定しやすく特に迷うこともありませんでした。

  • 料金安い

 Amazon SNS リクエストのうち、毎月最初の 100 万件は無料

 それ以降、Amazon SNS リクエスト 100 万件ごとに 0.50¬USD

 https://aws.amazon.com/jp/sns/pricing/

つらかった所

  • Amazon SNS用語やエンドポイントとトピックの関係などに慣れる必要がある

  • 大量配信先を事前に決められない時がつらい

  Amazon SNSのpush通知送信API(publish)は、1つのエンドポイントARN、もしくはトピックARNを対象するため、トピックに紐付かない複数のエンドポイントへの送信ができないようです。送信数分ループ処理をする事になりますが、送信数によってはサーバー負荷となってしまいそうです。

以上、トピック型のモバイルPush通知をRails + Amazon SNSで同じく実装した時に整理したことなどを纏めてみました。

これからPush通知の運用する中で、新たに問題点や改善点も出てきそうですが何かしらの形で共有できればと思います。


(☝︎ ՞ਊ ՞)☝︎是非読者になってください


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html

Vue.jsとRailsの最適な融合を考える

もう新年を迎えて2ヶ月が経ちますね。 多くの人は新年の目標を立てますが、皆さんは何かしら立てましたでしょうか? 英語を毎日勉強するという目標を立てましたが、既に挫折してしまったエンジニアの村上(pipopotamasu (pipopotamasu) · GitHub)です。

本日はその懺悔も込めてVue.jsとRailsの話をお送りします。

f:id:ec0156hx39:20180221120941p:plain

この記事を書く背景

以前ブログで書いた通り、現在Webpackerへの移行を機にフロントエンド周りの改善を進めています。 tech.medpeer.co.jp その中でVue.jsとRailsをいい感じに組み合わせるにはどうしたら良いかについて悩むことがあったので、本記事にて共有させていただきます。 悩んだ内容としては以下になります。
1. Ajax通信時にCSRFトークンをどう設定すればいいか?
2. 単一ファイルコンポーネントで書くHTMLをもっと効率よく書けないか?
3. 単一ファイルコンポーネントとフォームヘルパーの兼ね合い

実行環境

今回の記事ではサンプルコードを多く載せています。主なもののバージョンは以下になります。

  • Rails 5.1.4

  • Webpacker 3.2.1

  • Vue.js 2.5.13

  • axios 0.17.1

  • pug 2.0.0-rc.4

1.Ajax通信時にCSRFトークンをどう設定すればいいか?

RailsではAjax通信時、破壊的なHTTPメソッド(POSTとかDELETEとか)を送る場合はCSRFトークンの検証が必要となります。 jquery-railsのGemで上記のようなAjax通信を行う場合は、Gemの方でCSRFトークンをリクエストヘッダーに自動的に含めてくれるのですが、jQuery以外だとそうはいきません。 自身でCSRFトークンをリクエストヘッダーに含める必要があります。

ではどういった風にVue.jsでCSRFトークンをリクエストヘッダーに含めたら良いのでしょうか? 私自身どうしたらいい感じに実装できるか悩んだため、ここで悩んだ末にたどり着いた実装をご紹介します。

なお、今回のケースではHTTPクライアントとしてaxiosを使用します。

リクエストヘッダーにCSRFトークンを仕込んでみる

通常、Railsが提供しているフォームヘルパーを利用せずにAjax通信を行う場合は明示的にCSRFトークンを設定する必要があります。

設定しない場合

まずはAjaxを送る部分のソースを見てみます。
f:id:ec0156hx39:20180212221613p:plain:w220

上記のようにCSRFトークンを設定しない状態だと...

f:id:ec0156hx39:20180212222555p:plain

このようなエラーが起こるので、ちゃんと通るようにしてみましょう。

設定する場合

今度はCSRFトークンを設定する場合です。
f:id:ec0156hx39:20180212221532p:plain:w700

上記の10行目でCSRFトークンの取得、11行目でリクエストヘッダーにトークンを設定しています。

f:id:ec0156hx39:20180212222617p:plain

今回は上手く処理されました。

この方法はViewのDOM内のCSRFトークンを直接取得し、axiosに設定する方法です。

実はこれと同じ方法を提供してくれるパッケージがあります。 次はそのパッケージ、「rails-ujs」を用いた方法を見てみましょう。

rails-ujsを使ってみる

まずはrails-ujsをインストールします。

yarn add rails-ujs

次にrails-ujsからcsrfTokenを取得するメソッドをimportし、CSRFトークンを設定してみましょう。

f:id:ec0156hx39:20180214161913p:plain:w500

これで先ほどの例のように破壊的なリクエストを送ることができるようになります。

Vue.jsのPluginにしてみる

ここまででCSRFトークンを設定するところまでみてきました。 上記の例だと、1つのエントリーファイルに1つのコンポーネントしかありませんでしたが、実際には複数のコンポーネントを使用することが多いと思います。

その場合、上記の例だとaxiosで破壊的なHTTPメソッドを使用する全てのコンポーネントに...

  • axiosをimport

  • rails-ujsをimport

  • リクエストヘッダーにCSRFトークンを設定

しなければなりません。正直面倒です。

そこで、Vue.jsのプラグインを作成し上記の処理を一纏めにしてしまいましょう。

まずはVue.jsのPluginを実装します。Pluginを実装することにより、Vueオブジェクトからaxiosを呼び出せるようにしてみましょう。

  • app/javascript/plugins/vue-axios.js f:id:ec0156hx39:20180214165844p:plain

次はエントリーファイルにて上記で作成したPluginをVueに組み込みます。

  • app/javascript/packs/chapter1/index.js f:id:ec0156hx39:20180214175736p:plain

最後にコンポーネントでVue経由でaxiosを呼び出してみましょう。

  • app/javascript/components/chapter1/App.vue f:id:ec0156hx39:20180214175851p:plain:w250

これで各々のコンポーネントで設定しなくていいようになりました。


以上のようにCSRFトークンの設定からコンポーネント内でのaxiosの呼び出しについて、メドピア内でどうしているかをみていきました。 皆さんはVue.jsとRailsを組み合わせる時、どのようにしているでしょうか?

2.単一ファイルコンポーネントで書くHTMLをもっと効率よく書けないか?

RailsのViewは独自のテンプレートエンジンを用いることで、効率的にHTMLコーディングをすることが可能になります。 ほとんどの人はHamlやSlimといったテンプレートエンジンを使って効率化しているのではないでしょうか?

Vue.jsの単一ファイルコンポーネントのデフォルトのテンプレートは通常のHTMLです。 せっかくRailsはHamlやSlimを使用しているのに、単一ファイルコンポーネントはHTMLって非効率です。

JSのテンプレートエンジンといえば、Vue.jsに組み込まれているMustachやEJSが有名ですが、今回の目的はHTMLの効率的なコーディングです。どうやらHTMLの省略記法に対応しているのはそれほど多くなく、調べてみた結果「Pug」の一強のようです(もし他に有名なやつがあればこっそり教えてください)。

そこで、メドピアではPugを採用することにしました。

Pugとは

Hamlに影響を受けたテンプレートエンジンです。ただHamlに影響を受けたという割にSlimに近いシンタックスだったりします笑

元々Jadeという名前でしたが、すでにJadeが商標登録?されていたためPugにしたらしいです。 上記でも書いたように、
・HTMLの省略記法に対応
・JSの実行

という特徴があります。 それでは次にPugを使って単一ファイルコンポーネントを書くとどのようになるのかを見ていきましょう。

Pugを使用しない場合

と言いつつも、Pugを使用した場合と使用しない場合のbefore/afterが見れた方が良いので、まずはpugを使用しない場合の単一ファイルコンポーネントを見てみます。 今回の例ではみんな大好きTodoリストです。

  • app/javascript/components/chapter2/TodoApp.vue f:id:ec0156hx39:20180215114443p:plain

まあ普通のHTMLですね笑

次はPugを使用する例を見ていきます。

Pugを使用する場合

まずはPugのパッケージと、WebpackでPugをコンパイルするためのpug-loaderをインストールします。

yarn add pug pug-loader

後は単一ファイルコンポーネントのtemplateタグにlang="pug"とpugの設定をするだけです。 Webpackerの場合はコンフィグファイルに設定を追記する必要はありません。

どうなるかというと...

  • app/javascript/components/chapter2/PugTodoApp.vue f:id:ec0156hx39:20180215120942p:plain

このようにかなりスッキリさせることができました。

みなさんも是非pugを使って見てください!

3.単一ファイルコンポーネントとフォームヘルパーの兼ね合い

Vue.jsの単一ファイルコンポーネントはとても便利です。 以前技術ブログでも書きましたが、単一ファイルコンポーネントは以下のようにとても便利です。 * シンタックスハイライト * 1ファイル内にテンプレートとテンプレートに適用するJavaScript、cssを書くことができる(コンポーネント化できる) * テンプレート内でES6以降のものが使用できる

しかし残念なことに、単一ファイルコンポーネントではRailsのフォームヘルパーを使うことができません。 かといって本来フォームヘルパーで生成されるはずのHTMLを単一ファイルコンポーネント内のテンプレートに直書きするのも効率が悪いです。

比較

そこで、単一ファイルコンポーネントでテンプレートを書くかRailsのViewでテンプレートを書くか比較してみることにしました。 今度はユーザーの新規作成画面を例にしてみます。

単一ファイルコンポーネント内にテンプレートを書く場合
  • app/javascript/components/chapter3/UserRegisterForm.vue f:id:ec0156hx39:20180220122803p:plain

全体としてこのような感じになりました。 このテンプレートを作ってみて面倒だったのはformタグとhidden要素です。

f:id:ec0156hx39:20180220123017p:plain

フォームヘルパーならform_forやform_withで一行で作成できる部分をわざわざ手打ちしてHTMLを書かなければなりません。 またhiddenのinputタグのauthenticity_tokenのvalueですが、フォームヘルパーなら自動的にCSRFトークンが設定されるところ、わざわざ自身で設定しなければなりません。

f:id:ec0156hx39:20180220123451p:plain

結果、テンプレートを作成するのにそこそこ手間がかかってしまいました。

RailsのViewにテンプレートを書く場合

今度はフォームヘルパーを活用する場合です。全体としてはこんな感じになります。

  • app/views/chapter3/new.html.erb f:id:ec0156hx39:20180220123722p:plain

先ほどのformタグとhiddenの件はフォームヘルパーのおかげでかなり楽になりました。 他にもtext_fieldやlabelなどのヘルパーのおかげで、全体的にコード量が減っているのが見て取れます。

しかし、もちろんデメリットもあります。

  • Vue.js部分がシンタックスハイライトが効かないため若干見にくい

  • polyfillが効かない

  • テンプレートとコードが離れる

デメリットこそありますが、メドピア内ではフォームヘルパーを使う時は基本的に単一ファイルコンポーネントを使わず、RailsのViewにテンプレートを書く方法にしています。

まとめ

今回の記事ではメドピア内でRailsとVue.jsを組み合わせる時に悩んだ3つのことと、その対応策を書いてみました。

使用したサンプルコードは以下のリポジトリに置いてあります。

GitHub - medpeer-inc/medpeer-dev-blog2

正直これがベストなソリューションであるという確信はありません。

もし、「もっといい方法があるよ」というアイデアがある方は是非メドピアに遊びに来てそれをご教授ください。 「俺がもっといけてるコードにしてやるぜ!」という方は一緒に働きましょう!

またメドピアでは毎週火曜日にVue.jsのもくもく会を実施しています。そちらも是非ご参加ください!

medpeer.connpass.com


(☝︎ ՞ਊ ՞)☝︎是非読者になってください


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html