CookPadのchanko勉強会に行ってきたのでサンプルを作ってみた(View編)

6/8(金)にCookPadで開催された「chanko勉強会」に行ってきました。


chankoとはCookPadが開発したrails用のライブラリで、chankoを使うと
プロトタイプで作成した機能を素早くかつ安全にプロダクション環境で
実際のユーザーに使ってもらう事が出来ます。
https://github.com/cookpad/chanko


プロトタイプを実験的に特定のユーザーにのみ機能を公開することが出来るので、
自社のスタッフのみに公開したり、ある属性を持ったユーザーに対してのみ公開して
反応を見たりすることが出来るため、開発した機能を全体に公開する前に実際のユーザーからの
フィードバックを得られるというメリットがあります。

リーンスタートアップでいう「仮説と検証」のループを素早く回せるという事ですね。


以前、別のアジャイル系のイベントでchankoを使ったCookPadの取り組みを聞いていて
興味があったので申し込んでみたものの、落選…orz


と思ったら後日欠席者が出たとのことで、繰り上げ当選になりました(∩´∀`)∩ワーイ


勉強会の内容についてはid:antipopさんが詳しく書いてましたのでこちらで
http://kentaro.hatenablog.com/entry/2012/06/08/231419


せっかくなので忘れないうちに簡単なサンプルを作ってみようと思います。


chankoのサンプルプロジェクトの作成

rails new chanko_test


必要なgemの設定。chanko以外に、今回はtwitter-bootstrapとdeviseも入れておきます。

vi Gemfile

gem 'chanko', :git => 'git://github.com/cookpad/chanko.git'
gem 'twitter-bootstrap-rails'
gem 'devise'

bundle install


chankoのインストール

rails generate chanko:install


moduleのinclude

vi app/helpers/application_helper.rb

module ApplicationHelper
    include Chanko::Invoker
    include Chanko::Helper
end


chankoは「Unit」という単位で機能を作成します。
Unitはrails generateで生成出来るようになっています。

rails generate chanko deco     
      create  app/units/deco/deco.rb
      create  app/units/deco/views/_show.html.haml
      create  app/units/deco/specs/controllers/deco_controller_spec.rb
      create  app/units/deco/specs/models/deco_model_spec.rb
      create  app/units/deco/specs/helpers/deco_helper_spec.rb
      create  app/units/deco/stylesheets/deco.scss
      create  app/assets/stylesheets/units/deco
      create  app/units/deco/javascripts/deco.js
      create  app/assets/javascripts/units/deco
      create  app/units/deco/images/logo.png
      create  app/assets/images/units/deco


生成されたUnitを見てみましょう。
コメントアウトされたサンプルソースが記述されています。

vi app/units/deco/deco.rb

module Deco
  include Chanko::Unit

=begin
  active_if :always_true do |context, options|
    # "context' is invoking context such as controller
    true
  end

  scope(:controller) do
    function(:show) do
    end
  end

  scope(:view) do
    function(:show) do
      render :partial => "/show"
    end
  end

  models do
    expand("Recipe") do
      def your_method
      end

      class_methods do
        def your_class_method
        end
      end
    end
  end

  shared("your_shared_method") do
    'hello'
  end

  helpers do
    def your_helper_method
    end
  end
=end

end


chankoで作った機能を載せるためのアプリケーションを作ります。

rails g scaffold blog title:string content:text
rake db:migrate


デフォルトだと見た目が悪すぎるので、twitter bootstrapを入れます

rails g bootstrap:install
rails g bootstrap:layout application fixed

tableの見た目を少しいじりましたが省略します。


この状態でwebrickを起動するとこんな感じ。

ボタンはtwitter bootstrapのclassを使っていないので、chankoを使って少し見た目を変えてみます。

vi app/units/deco/deco.rb

module Deco
  include Chanko::Unit

  active_if :always_true do |context, options|
    # "context' is invoking context such as controller
    true
  end 
 
  scope(:view) do
    function(:show_button) do
      link_to :show, blog_path(blog), :method => :get, :class => 'btn'
    end 
  end 

先程生成されたUnitを編集しました。
active_ifの戻り値がtrueの場合にこのUnitで定義した機能が公開されます。


scope(:view)はViewに機能を追加する際に使用します。
渡すブロックの中でtwitter bootstrapのclassを設定したリンクを表示するようにしています。



次にアプリケーションから先ほど作成した機能を呼び出します。

vi app/views/blogs/index.html.erb

でViewのファイルを開いて

<td><%= link_to 'Show', blog %></td>

の行を以下のように変更します。

<td><%= invoke(:deco, :show_button, :locals => {:blog => blog}) %></td>

機能の呼び出しは
invoke(ユニット名, function名)
のように書きます。 :localsにはローカルスコープの変数を渡す事が出来ます。


この状態でアプリケーションを見てみると

おぉ、リンクがボタンに変わっていますね!
chankoのUnitで追加した機能が実行されてtwitter bootstrapのclassが反映されています。


では次に特定のユーザーだけにこの機能を見せるようにしましょう。

今のアプリケーションにdeviseをinstallしてユーザー管理の機能の追加します。
※deviseの手順は省略


表示の切り替えは app/units/deco/deco.rb の active_if で行います。
ユーザー名が"siso9to"の場合だけボタンが表示され、それ以外はテキストのリンクが
表示されるようにします。

active_if :always_true do |context, options|
    if context.current_user.username == "siso9to"
      true
    end 
end 

contextにはアプリケーションのコンテキストが渡されるので、ヘルパーなども使えるそうです。


画面の方はどうなったか見てみましょう。

ユーザー siso9toの場合

ボタンが表示されています。


ユーザー siso8toの場合

おぉ、ちゃんと指定したユーザー以外ではデフォルトの挙動をしました(テキストリンクを表示)。
※デフォルトの挙動については後述

今回は単純な例でしたが、例えば男性ユーザーのみに表示したい場合や、20代以上のみに
表示したい場合など、ユーザーのセグメントごとに表示を切り替えて様々な実験をすることが
出来そうです。


ちなみにUnitの方でエラーが発生した場合はどうなるのでしょうか?
chankoのUnitの中で発生したエラーはchankoの中で処理されます。

また、以下のように記述すると、Unitが無効の場合やエラーが発生した場合はデフォルトの
処理が実行されます。

invoke(ユニット名, function名) do
  #デフォルトの処理
end

この機能のおかげで、開発者は障害を恐れずにプロトタイプをプロダクション環境に
どんどんリリース出来ます。


もしエラーが発生した場合でも、プロトタイプの機能が表示されないだけでユーザーは
既存のアプリケーションを使用することが出来ます。
追加機能のリリースに失敗してアプリケーションが全て真っ白な画面に・・・という悲劇は
かなり回避できそうです。


デフォルトでは開発環境ではこの機能は無効になっている(例外がアプリケーション本体までthrowされる)
ので、もし開発環境でも本番環境と同じ振る舞いを確認したい場合は、

config/initializers/chanko_initializer.rb を開き、

Chanko.config.raise = true if Rails.env.development?

のtrueの部分をfalseにします。

Chanko.config.raise = false if Rails.env.development?


こうしておくと、先程追加した :show_buttonの中でraiseしても

  scope(:view) do
    function(:show_button) do
      raise 
      link_to :show, blog_path(blog), :method => :get, :class => 'btn'
    end 
  end 


画面にはエラーが表示されません(ログにはraiseの結果が出力されます)

これは便利ですね!


ここまでのソースはこちらにUPしておきました。
(大した事してないので公式のサンプル見たほうがいいですが・・・)
https://github.com/siso9to/chanko_test.git

今回はviewだけ記事に書きましたが、controllerやmodelも試してみたので、
整理して次回以降に書きたいと思います。