2013年1月10日木曜日

vanityとchankoを使ってRailsで簡単安全にABテストをする

vanityはRailsのABテスト用ライブラリ。導入が一番簡単っぽい。
 元々はテキストとか画像をのABテストを行うためのもののようだが、 chankoという限定公開用のライブラリを使って、機能単位でもABテストができるようにしてみる。

まずはGemfileに
gem 'chanko', :git => 'git://github.com/cookpad/chanko.git'
gem "vanity"
を記述して、
# bundle install

それぞれに必要な初期設定をする。
# rails generate chanko:install
# rails generate vanity
# rake db:migrate

config/vanity.ymlを作成
development:
  adapter: active_record
  active_record_adapter: mysql2
  host: localhost
  database: DBNAME
  user: USERNAME
  password: PASSWORD

development.rbに以下を記述
 Vanity.playground.collecting = true

vanityのDashboard用コントローラを作成
class VanityController < ApplicationController
  include Vanity::Rails::Dashboard
end 

ルーティングを設定
match '/vanity(/:action(/:id(.:format)))', :controller=>:vanity

測定用のユーザアイデンティティを設定。
class ApplicationController < ActionController::Base
  use_vanity :current_user
end

Railsのルートディレクトリ下にテストと測定用のファイルを作成
#mkdir -p experiments/metrics

postした回数測定用のファイルを作成
experiments/metrics/post.rb
metric "Post" do
  description "Postした回数"
end

コントローラの好きな場所に、 track! :post を入力すれば、その場所がよばれた回数を測定できるようになります。
class PostController < ApplicationController
  def create
     track! :post
       # ...投稿する処理
     end
  end
end

テスト用ファイルを作成
experiments/post_labels.rb
ab_test "Post labels" do
  description "テスト"
  alternatives true, false
  metrics :post
end

次にchanko用設定
# rails generate chanko sample
module Sample
  include Chanko::Unit

  active_if do |context, options|
    ab_test(:price_options) 
  end
  scope(:controller) do
    function(:controller_show) do
      # controller code here
    end
  end
  scope(:view) do
    function(:view_show) do
      render :partial => "/show"
    end
  end
end
で、好きな場所にinvoke(:sample, :controller_show) をおいたり、
invoke(:sample, :show) を置けばOK。
chankoの詳しい使い方はこちらを参考に。

どちらが優位かなんてのは、Vanityが表示してくるので、 評価しやすいはず。

production環境でうまく動かなかったので、
そのときはredisによる接続をためしてみるといいかも。
production:
  adapter: redis
  host: localhost



 参考:
http://eccyan.hatenablog.com/entry/2011/12/08/223603
https://github.com/assaf/vanity
http://webandy.com/articles/a-b-testing-with-vanity

2012年12月5日水曜日

rubymotionとrailsの連携を試行錯誤した話

この記事はRubyMotion Advent Calendar 2012の5日目記事です。

rubymotionメリットのひとつに、サーバサイド(Rails)とクライアントサイドが同じ言語でかけることがあると思いますが、
思いの外Railsとどう連携していくかの情報が少ないような気がします。
なので他の人の参考になればと、個人的に試行錯誤したことを残しておこうと思います。

ちなみに、one minutesというアプリを、rubymotionで作りましたので、よければダウンロードしてください。

試行錯誤1、babble-wrapで都度処理を書く

先述のone minutesでは、表示させるニュース情報をサーバからjsonで取得するだけの単純作業なので、以下の様な感じで実装しました。

BW::HTTP.get(SERVER_URL) do |response|
  if response.ok?
    @data = BW::JSON.parse(response.body.to_str)
  else
    App.alert(response.error_message)
  end
end

実装はかなり楽でしたが、複数情報を取得したい先がある場合や、postを行うときには、かなり面倒なことになってしまいました。
それが↓です。

試行錯誤2、babble-wrapで都度処理を書く(その2)

ソーシャルアプリを作ろうとした際の実装です。
アプリ概要は、facebookみたいなものなので、記事の投稿、投稿に対するコメント、投稿に対するいいね、それぞれを表示させるタイムラインの実装が必要でした。
それが下記コード群です。

#記事読み込み
BubbleWrap::HTTP.get("#{SERVER_URL}/entries.json?page=#{@page}") do |response|
  if response.ok?
    json = BubbleWrap::JSON.parse(response.body.to_str)
    unless json.count == 0
      @table_dates << json
      self.tableView.reloadData
      @page += 1
      @readMoreButton.enabled = true
      @readMoreButton.hidden = false if @page
    else
      @page = nil
      @readMoreButton.hidden = true
    end
  else
    App.alert(response.error_message)
   end
end
#記事投稿
  BubbleWrap::HTTP.post("#{SERVER_URL}/entries.json", {payload: data})
#コメント
 BubbleWrap::HTTP.post("#{SERVER_URL}/comments.json", {payload: data}) do |response|
    if response.ok?
      App.alert("コメントしました")
    elsif response.status_code.to_s =~ /40\d/
     App.alert("comment failed")
    else
     App.alert(response.error_message)
   end
 end
#いいね
BubbleWrap::HTTP.post("#{SERVER_URL}/likes.json", {payload: data}) do |response|
   if response.ok?
      App.alert("likeしました")
    else
     App.alert(response.error_message)
   end
 end

企画自体が没になったため、コレ以上コードは書いていないのですが、
重複が多く、メンテナンス性も非常に悪いものができてしまいました。

で、このあとECアプリを作ることになりました。
これまでの経験を踏まえ、もう少しRailsライクに実装したいと感じるように。

具体的には、
1.new、find、save、allなどで情報が取得や保存ができるように
2.バリデーションエラーは、その内容までも分かるように
3.ネットワークエラーなどがでたら、バリデーションエラーなどとは別に判別できるように

という理想を掲げ、作ってみた現実(ライブラリ)はこちら。
https://github.com/face-do/motion-rails-model

初めて作ったライブラリでもあり、非常にできはよくないのですが、それでも一応の理想は実現できました。
(といっても、babble-wrapのさらにwrapperライブラリだったりしますが。。。)

使い方としては、最初にアクセスしたいURLやbasic認証の情報を入力


    RM::Model.set_url("#{URL}/api/")
    RM::Model.set_username("username")
    RM::Model.set_password("password")

その後モデル用のクラスを作り、ライブラリを継承。

class Orders < RM::Model
  attr_accessor :name
 
  def attributes_update(json)
    @name = json['name']
    super
  end
end

これで、

order = Orders.new
order.name = "hogehoge"
order.save do |x|
  if x.ok?
    App.alert("保存しました。")
  end
end

とかすると、
railsに対して
/api/orders.json?name=hogehoge
のpostメソッドを実行するし、

Orders.find(1) do |x|
  if x.ok?
    @order = x.body
  end
end

で、
/api/orders/1.json
のgetをしたりします。

またgetしてきた、Objectに対してsaveをすると、
/api/orders/1.json
に対してputをするようになってます。

これを作った後、メタプログラミングrubyを読みまして、
もう少し抽象化ができそうな気がしているので、機会を見て修正していく予定です。

2012年10月29日月曜日

今ウェブサイトを作るなら必須のアイコン画像サイズ一覧


漏れがないようにまとめてみました。

サイト全体
favicon
16×16

facebook(OGP)対策
200x200

ウェブクリップアイコン(ios、android)対策
114×114(iPhone、iPod touchのRetina)
57×57(iPhone、iPod touchの非Retina)
144×144(iPadのRetina)
72×72(iPadの非Retina)

一括作成サービスができてくれるといいなあ。

2012年9月29日土曜日

rubymotion用ライブラリの作り方

ライブラリのコードは予め書いておき、またrubygemsに公開するなら、
そのアカウントを取得しておく。

適当なフォルダで
# bundle gem motion-hogehoge
で、gemの雛形を作成

.gemspecに必要な項目を入力。
依存ライブラリがある場合は、ここに
gem.add_dependency "bubble-wrap", "~>1.1.4"
などと追記しておく。

次に、lib/motion-hogehoge.rbというファイルができているので、ここを修正。
rubymotionではrequireが対応していないので、rubymotion用に書き直す必要がある。
以下がサンプル

unless defined?(Motion::Project::Config)
  raise "This file must be required within a RubyMotion project Rakefile."
end

Motion::Project::App.setup do |app|
  Dir.glob(File.join(File.dirname(__FILE__), 'motion-hogehoge/*rb')).each do |file|
    app.files.unshift(file)
  end
end

ライブラリのコードは、lib/motion-hogehoge/以下に入れておく。

rubygemsに登録する場合は、テストが通ってる必要がある。
とりあえず通すだけなら、以下でOK。

app/app_delegate.rbに
class AppDelegate
  def application(application, didFinishLaunchingWithOptions:launchOptions)
    @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
    @window.rootViewController = UIViewController.alloc.init
    @window.makeKeyAndVisible
    true
  end
end
spec/main_spec.rbに
describe "Application 'modeltest'" do
  before do
    @app = UIApplication.sharedApplication
  end

  it "has one window" do
    @app.windows.size.should == 1
  end
end

で、
#bundle install
#rake spec
でテストが通ることを確認する。

問題がなければ、
gem build motion-hogehoge.gemspec
で、gemファイルを作成し、

gem push motion-hogehoge-0.0.1.gem
で、rubygemsにアップする。

アップするときに取得したアカウント情報が聞かれるので入力すればOK。

2012年9月28日金曜日

Railsとjsonでやり取りするmotion-rails-modelというrubymotion用ライブラリを作ってみた

Railsと連携する際、babble-wrapを使っていたのですが、
取得先が複数になったときに面倒だったのでラッパーライブラリを作ってみました。
Active Record風のメソッドで、Rest風にアクセスをします。

github

使い方
rails側はindex,show,create,update,destoryをjsonで返信するようにします。 アプリ側では適当にmodelクラスを作って、motion-rails-modelを継承し、
attr_accessorとattributes_updateに項目を追加します。
class Entries < RM::Model
  attr_accessor :title, :description
  
  def attributes_update(json)
    @title          = json['title']
    @description    = json['description']
    super
  end
end
利用するときは、まずurlを設定します。
class AppDelegate
  def application(application, didFinishLaunchingWithOptions:launchOptions)
    RM::Model.set_url("http://localhost:3000/")

    @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
    @window.rootViewController = RootViewController.alloc.init
    @window.makeKeyAndVisible
    true
  end
end
あとは、
Entries.all do |x|
  if x
    p x
  else
    p "error"
  end
end
で、
http://localhost:3000/entriesにアクセスし
Entries.find(1) do |x|
  if x
    p x
  else
    p "error"
  end
end
で、
http://localhost:3000/entries/1
にアクセス。
@entry = Entries.new
@entry.title = "title"
@entry.description = "description"
@entry.save do |x|
  if x
    p x
  else
    p "error"
  end
end
で、
http://localhost:3000/entries
にpost。
@entry.title = "title2"
@entry.save do |x|
  if x
    p x
  else
    p "error"
  end
end
で、http://localhost:3000/entries/1
にput。
@entry.destory do |x|
  if x
    p x
  else
    p "error"
  end
end
で、http://localhost:3000/entries/1
にdeleteでアクセスするようになってます。

TODOとして
attributes_updateを設定しなくてもいいようにする。 エラー処理をもう少ししやすく。

2012年9月26日水曜日

現在のフォルダにあるファイルをリネームする


files = Dir::entries(Dir::pwd)

files.each do |f|
File.rename(f, f.sub(/aaa-/, '')) if f =~ /aaa-/
end

csvを読み込んで、別のcsvの項目のうち2つ一致した項目を表示する

# -*- coding: utf-8 -*-
require 'csv'

a_csvs=[]
b_csvs=[]
succsess = []
failed = []
a_csvs_tmp = CSV.open('a.csv', 'r')
b_csvs_tmp = CSV.open('b.csv', 'r')


a_csvs_tmp.each{|x| a_csvs << x }
b_csvs_tmp.each{|x| b_csvs << x }

a_csvs.each do |x|
a = b_csvs.select{|b| x[0] == b[0] and x[1] == b[1] }
unless a.empty?
  succsess << a
else
  failed << x
end
end

puts "見つかったモノ"
p succsess.flatten
puts "失敗したモノ"
p failed.flatten