chefで頑張って冪等性を確保する

packageリソースなどは、勝手に冪等になってくれるけど、executeとかは自分で冪等になるように書かないといけない。

特定の文字列を、ファイルの末尾に追記したい場合

例:/etc/resolv.conf に 'options single-request-reopen' を追加する。 二重に追加されないように not_if で制限する。

execute 'add single-request-reopen' do
    command "echo 'options single-request-reopen' >> /etc/resolv.conf"
    not_if "cat /etc/resolv.conf | grep 'options single-request-reopen'"
end

パスを通す場合

例:rbenvコマンドにパスを通す。 パスが通っている場合は not_if で制限する。

user = 'vagrant'
home_dir = "/home/#{user}"
execute 'export path' do
    not_if 'which rbenv'
    user user
    group user
    command <<-EOC
        echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> #{home_dir}/.bash_profile
        echo 'eval "$(rbenv init -)"' >> #{home_dir}/.bash_profile
    EOC
end

とりあえず、このへんを知っておくと、たいていは冪等に書けるんじゃないかと。

久しぶりにiOSアプリをビルドしようとしたらエラー

ビルドしようとしたら下記のエラー。

No architectures to compile for (ONLY_ACTIVE_ARCH=YES, active arch=x86_64, VALID_ARCHS=i386).

ググったら、こんなん http://stackoverflow.com/questions/22328882/xcode-5-1-no-architectures-to-compile-for-only-active-arch-yes-active-arch-x 出てきた。

なので、言われたとおり、

  • Xcode上でPodsプロジェクトを右クリック→Delete→Remove Reference
  • Xcodeを終了
  • コマンドラインから pod install
  • .xcworkspace を再度開く
  • Podsプロジェクトを選択し、Build Settings→Build Active Architecture Only をNOに (debugがYESになってた)
  • 同じく、自分のプロジェクトのBuild Active Architecture Only もNOに (debugがYESになってた)

したら、iOSシミュレータで動くようになった。よかった。

久しぶりにpod installしたらエラー

rubyのバージョン変えてたので、まずはgemのインストール

gem install cocoapods
rbenv rehash

で、

pod install

すると、エラーが出てくる。 こんなの

Psych::SyntaxError - (/Users/〜〜〜/.cocoapods/repos/master/CocoaPods-version.yml): mapping values are not allowed in this context at line 3 column 4

ググったら、 https://github.com/CocoaPods/CocoaPods/issues/1853 こんなのが出てきたので、書いてあったコマンドを実行

rm -rf ~/.cocoapods/repos/master
pod setup

で、再度

pod install

すると正常に動いた模様。

Vagrantをprovisionしたときにディレクトリを作る

メモ:

ステージング・本番ではcapistranoでデプロイするけど、 ローカルのVagrantではそんなことしないので、 pidを格納するディレクトリが無くて困った。
(nginxで見に行く先を変えればいいけど、そんな部分のchefをいじるのも面倒。。。)

んで、Vagrantfileのsynced_folderとして、テキトーなディレクトリを指定したけど、 sockファイルを作ろうとして、エラーになった。

http://stackoverflow.com/questions/16388342/unicorn-fails-to-start-on-vagrant-box-due-to-errnoeperm
synced_folderしたディレクトリには作れないっぽい。

どうせなので、provision時のshellを使ってみた。

  $script = <<SCRIPT
mkdir -p /srv/web/shared/pids
chown vagrant:vagrant -R /srv/web/shared
SCRIPT
  config.vm.provision "shell", inline: $script

単純にmkdirすると、root権限で作られたので、chownでログインユーザのものに変更。

無事、sockファイルが作れて、nginx -> unicornでページが表示された。

メモ:rspecで変更が無いことをテストする

自分用メモ

expect{hoge}.not_to change{fuga}

で変更が無いことをテストできる

Rank.get_rank_id では、パラメータによって次のランクが返却されるイメージ User#calc_rank!では、Rank.get_rank_id を呼び出し、その結果をDBに保存するイメージ

require 'spec_helper'

describe User do
  describe '#calc_rank!' do
    let!(:user) { FactoryGirl.create(:user, rank_id: 2) }
    it do
      Rank.stub(:get_rank_id).and_return(2)
      expect{user.calc_rank!}.not_to change{User.find(user.id).rank_id}
    end
  end
end

Alfred workflowで新規ウィンドウを開く

最近、Alfredを使い始めました。

どうせなら、ということでPowerPackも買って、workflowを使えるようにしました。

とか入れてます。

で、それなりに便利に使ってたんですが、FinderとかiTerm2を起動しようとして、別のデスクトップに起動してたのがアクティブになるのが不便。。。

というわけで、下記を追加してみた。

Finderを新規に起動する

  • Alfredの設定画面を開く
  • Workflowsタブを開く
  • 左ペインの右下にある"+"を押す
  • Templates -> Essentials -> Keyword to AppleScript を選択する
  • keywordをダブルクリックする
    • Keywordに"nfinder"を入力
    • No Argumentを選択
    • titleに"new finder"とかを入力
  • Run NSAppleScriptをダブルクリックする
on alfred_script(q)
  tell application "Finder"
    make new Finder window
    set frontmost to true
  end tell
end alfred_script

上記手順により、Alfred呼び出し -> "nfin"とか入力 -> Enter でFinderが新規に起動します。

iTerm2を新規に起動する

Finderの時と手順は同様。
keywordは"niterm"にしました。
AppleScriptは下記の通り。

on alfred_script(q)
    if application "iTerm2" is running or application "iTerm" is running then
        run script "
          on run {q}
              tell application \":Applications:iTerm.app\"
                  create window with default profile
                  select first window
                  tell the first window
                      if onlywindow is false then
                          create tab with default profile
                      end if
                      tell current session to write text q
                  end tell
              end tell
          end run
      " with parameters {q}
    else
        run script "
          on run {q}
              tell application \":Applications:iTerm.app\"
                  activate
                  try
                      select first window
                  on error
                      create window with default profile
                      select first window
                  end try
                  tell the first window
                      tell current session to write text q
                  end tell
              end tell
          end run
      " with parameters {q}
    end if
end alfred_script

これも、Alfred呼び出し -> "nit"とか入力 -> Enter でiTerm2が新規に起動します。
https://gist.github.com/reyjrar/1769355 を参考にしたのですが、zshを利用していたので書き換えました。

アイコンとかサクッと作れれば、フツーに公開するんだけどな。。。

KFCocoaPodsPlugin をインストールしてみる

前に iOSアプリ開発が捗るXcodeプラグイン - Qiita [キータ]ricobeck/KFCocoaPodsPlugin · GitHub というプラグインが紹介されていました。

便利そうだなーと思いつつ、放置していたのですが、年末年始で少し時間が出来たので、導入してみました。

ただし、githubの説明が

Clone this repo or download the zip, run 'pod install' in the terminal, open the workspace file in Xcode and compile. After a restart of Xcode you have a 'CocoaPods' Menu Item in the Products menu.

しか無かったので、念のためメモ。
※cocoapodsはインストール済みの前提

手順

今回、 ~/bin に入れておくとします。

cd ~/bin
git clone git@github.com:ricobeck/KFCocoaPodsPlugin.git
cd KFCocoaPodsPlugin
pod install
open KFCocoaPodsPlugin.xcworkspace

Xcodeが開くので、Product -> Buildとし、Xcodeを一旦完全に終了させ、再度起動。

すると、公式のgithubにあるように、Productメニュー無いにCocoaPodsが表示されます。
これで入力補完も効くし、黒い画面が無くてもpod install が出来ます。

SeleniumをRubyから呼んでみる

Gemfileを作って編集する。

bundle init
vim Gemfile

入力内容は下記の通り。

source "https://rubygems.org"
gem "selenium-webdriver", "‾> 2.38.0” 

bundle installを実行する。

bundle install --path vendor/bundle 

rubyスクリプトファイルを作成する。

vim drive.rb

内容は下記の通り。

require 'rubygems'
require 'selenium-webdriver'

driver = Selenium::WebDriver.for :firefox
driver.get "http://google.com"

element = driver.find_element :name => "q"
element.send_keys "Cheese!"
element.submit

puts "Page title is #{driver.title}"

wait = Selenium::WebDriver::Wait.new(:timeout => 10)
wait.until { driver.title.downcase.start_with? "cheese!" }

puts "Page title is #{driver.title}"
driver.quit 

実行してみる。

ruby drive.rb

怒られる。selenium-webdriverが読み込めない模様。

`require': cannot load such file -- selenium-webdriver (LoadError)

bundle exec で実行してみる。

bundle exec ruby drive.rb

怒られる。firefoxがインストールされてないから?

Could not find Firefox binary (os=macosx). Make sure Firefox is installed or set the path manually with Selenium::WebDriver::Firefox::Binary.path= (Selenium::WebDriver::Error::WebDriverError)

chromeに変えてみて、再実行。やっぱり怒られる。chromedriverが無いとダメらしい。

Unable to find the chromedriver executable. Please download the server from http://chromedriver.storage.googleapis.com/index.html and place it somewhere on your PATH. More info at http://code.google.com/p/selenium/wiki/ChromeDriver. (Selenium::WebDriver::Error::WebDriverError)

言われたとおり、http://chromedriver.storage.googleapis.com/index.html をブラウザで開いて、最新版(2.7)のmac用をダウンロード。 解凍すると、 chromedriver というファイルが出てくるので、PATHが通っているディレクトリに移動。

で、再実行。

bundle exec ruby drive.rb

無事ブラウザが開き、googleの検索結果が表示され、消えた。 コンソールには

Page title is Google
Page title is Cheese! - Google 検索

が表示されてた。

・公式のサンプルコードなど
http://docs.seleniumhq.org/docs/03_webdriver.jsp
・参考になりそうなところ
http://qiita.com/tomerun/items/9cb81d7a98150ff22f53
http://morizyun.github.io/blog/selenium-scraping-webdriver-ruby/

doubleのまま計算を続けると、計算誤差が増えるって話

doubleでの計算は、計算誤差が生まれるよ、っていう話。

a の値が正確に"0.9"ではないため、どちらにせよ誤差が生まれるけどね。

コードレビューしてて、doubleだけで計算してるのがあった。。。
小数点を含む計算って、BigDecimalを使うのが当たり前じゃないのか・・・?

iOS用ウェブアプリで出来ないこと

iOS上で動作するサービスを作成しようと思った時、下記のような選択肢があるかと思います。

  • ネイティブアプリ(Objective-C で作るようなもの)
  • ブラウザアプリ(HTML / JavaScript で作るようなもの)

それ以外にも、それらを組み合わせた

  • ハイブリッドアプリ(Objective-C で作った中に、HTML / JavaScriptで作った画面を入れるようなもの)

もありますね。

今回、ブラウザアプリ(Webアプリ?)を作った際に、ネイティブだったら出来るのに、と思ったことを中心にまとめてみます。 (Androidでも、大体同じことが言えるかもしれません)

PUSH通知

ユーザの呼び戻しには、PUSH通知が有効化と思いますが、ブラウザアプリでは実現できない。

  • メールアドレスを取得し、それを利用する
  • SNSを活用する

あたりで、うまくユーザを呼び戻す必要がある。

クリップボードの利用

PCブラウザでは、「クリップボードにコピーする」ボタンを見たことがあるかと。
これは Flash を利用しており、iOSでは動作しない。
input タグを用意しておき、そこをタップした時に、全選択→iOS標準の「コピー」が表示されるという風にする、というのが妥協案かと。

消えない(消せない)情報を保存する

ネイティブアプリなら、KeyChain が使える。これに入れておけば、アンインストールしても消えない。(はず
ブラウザアプリの場合、データの保存場所としては、Cookie, localstorage などがあるが、 「Cookieとデータを消去」で簡単に消せる。
消されて困る情報は、サーバに保存することになるが、その情報と付き合わせるために、ユーザ認証が必要になる。

特定の環境下で動作させる(Safariだけで動作させる)

ブラウザアプリの場合、Safari 以外で動作する可能性がある。
例えば、Chrome, Operaといったブラウザアプリ、Facebook, TwitterといったSNSアプリなど。
そのへんを、User Agent である程度判定できるが、その後の操作はユーザ任せになる。
そもそも、アプリ側で提供されていないと、Safariで表示させることは不可能になる。

受託の場合は、どこまで対応するか・どのように対応するかなどを、最初に決めておかないともめるかも。

フルスクリーン表示

可視領域を広げる、没入感を与えるといった効果がある、フルスクリーン表示ですが、ユーザに強制することは出来ない。
meta タグなどを利用し、「ホーム画面に追加」を行ってもらい、そこから起動してもらえば、可能。
http://mawatari.jp/archives/web-apps-like-native-apps

アプリがインストールされているかを確認(2013/12/3 追加

アプリがインストールされていたら、アプリ起動ボタンを表示し、
アプリがインストールされていなかったら、アプリダウンロードボタンを表示する。
みたいなことは出来ない。

http://havelog.ayumusato.com/develop/javascript/e564-url-scheme-fallback.html こちらにあるように、URLスキームで起動してみて、画面が切り替わって無ければ、ストアへ飛ばす。という動きなら出来る模様。
ただし、見た目はあんまり綺麗には出来ないみたい。(標準のアラートが出てしまったり

Webとネイティブを提供していて、ネイティブも紹介したいだけなら、App Bannerを使えばいいと思います。 https://developer.apple.com/library/ios/documentation/AppleApplications/Reference/SafariWebContent/PromotingAppswithAppBanners/PromotingAppswithAppBanners.html

まとめ

いろいろ制約はありますが、Objective-Cエンジニアを必要としないのは、人的リソースの確保はしやすいかも。
ただし、ちゃんとした HTML / JavaScript を書けるエンジニアを探すのも難しいかも。(有象無象が多いイメージ

appleへの申請を必要とせず、好きなタイミングで、全てのユーザに同じ動作環境を提供出来るのは、魅力的だと思います。

他にも、思い出したら・気がついたら追加しようと思います。