表題のようなバグに遭遇し、結構苦労したのでメモです。

<div class="parent">
  <select>
    <option>1</option>
    <option>2</option>
  </select>
</div>

となっている場合に、jQueryで、

$(".parent").hide()
$(".parent").show()

していると、2回目以降で表示が変更されない場合がありました。 結構謎な現象でしたが、 http://tachesimazzoca.blogspot.jp/2011/12/android-mobile-safari-select.html を参考に、

$(".parent").hide()  #=> $(".parent").attr("style","display:none")
$(".parent").show()  #=> $(".parent").attr("style","display:block")

と変更することで動きました。 なぜこれで動くのかは不明です。

追記 by kosaki

上記の設定をし、animationで画面を表示したのちスライドインで表示していましたが、結局同じ問題が出てきたので、animationを使わずスライドインさせない事で問題は解決。

//$(.parent).attr('style', 'display;block; right:400px;').animate('right:0px', 550);
=> $(.parent).attr('style','display:block;');

前提

現象

  • safariでエージェントをiphoneに変更して、ページを表示したときにsjis(自動選択)になる。
  • ヘッダー部分の定義はutf8になっている。
  • jqueryのajax通信でputすると、railsサイドのcontrollerで文字化けしていた。

原因

  • スマホの時の通信でもmobile_filterが有効になっていて、utf8の文字を送信したが、sjisとして自動的にutf8へ文字変換され、文字化けを引き起こしていた。

修正方法

mobile_filterの処理を飛ばすのはSoftbankとVodaphoneのみになっていたので、IphoneとAndroidを追記する。 config/initialize/jpmobile.rb というファイルを新規作成して、下記のコードでオーバーライドする。

-- coding: utf-8 --

# module Jpmobile # =文字コードフィルタモジュール。 module Filter # Shift_JISとUnicodeのフィルタ(NKFを使用) class Sjis < Base # to_internalを適用するべきかどうかを返す。 def apply_incoming?(controller) # Vodafone 3G/Softbank(Shift-JISにすると絵文字で不具合が生じる)以外の # 携帯電話の場合に適用する。 # 20120510 kosaki add Iphone & Android mobile = controller.request.mobile mobile && !(mobile.instance_of?(Jpmobile::Mobile::Vodafone)||mobile.instance_of?(Jpmobile::Mobile::Softbank)||mobile.instance_of?(Jpmobile::Mobile::Iphone)||mobile.instance_of?(Jpmobile::Mobile::Android)) end end end end

fontfaceでアイコンを使っているけれど、さらに一歩進んで、枠と内容に分けて組み合わせるアイディアを実現化しているフォント。 素晴らしい。

rubyと違って、coffeescript(javascript)だと色々false判定される場合があったのでメモしておきます。

if val then "TRUE" else "FALSE"
valresult
undefinedFALSE
undefined?FALSE
nullFALSE
null?FALSE
falseFALSE
false?TRUE
0FALSE
0?TRUE
-1TRUE
-1?TRUE
NaNFALSE
NaN?TRUE
””FALSE
””?TRUE

以上です。?関係は、 a? が、

typeof a !== "undefined" && a !== null;

なので、当然かもしれませんが。

iPad版のView作ってくれという依頼がきたので、responsiveなcss入れたりして色々やっていました。

日付系の動作についてまとまっているところがなかったので、まとめておきます。 基本的には、iPad版のSafariの方がバグっぽいです。

type iPad Safari(iOS5.0.1) mac Safafi(5.1.3)
datetime local時間から開始、stepは効かない UTC時間から開始、stepもちゃんと効く
datetime-local local時間から開始、stepは効かない local時間から開始、stepもちゃんと効く
time/date local時間から開始、stepは効かない local時間から開始、stepもちゃんと効く

以上です。

嵌ったので、メモおよび共有しておきます。 嵌ったコードは下記です。

hoge = Backbone.View.extend
  initialize:(opts={})
     @options = opts

一見何も問題なさそうなんですが、Backbone.Viewが内部的に@optionsを処理していて、2回目以降のrenderでdelegateEventsがbindしないということになってしまいました。

しかも、よくdocを読むと、

constructor / initializenew View([options]) 
    When creating a new View, the options you pass are attached to the view as this.options, for future reference. 
    There are several special options that, if passed, will be attached directly to the view: model, collection, el, id, className, tagName and attributes. 
    If the view defines an initialize function, it will be called when the view is first created.
    If you'd like to create a view that references an element already in the DOM, pass in the element as an option: new View({el: existingElement})

と、書いており、@optionsは標準で提供されているようですね。

つまり、初期化のときに代入したオプションは、@optionsで無条件で取れるということでした。嵌ってしまったorz

下記の記事は、完璧に誤解してました。 同じ名前で、別のclusterってのがあるようでした。

本家に取り込まれている(http://nodejs.org/api/cluster.html)と、(https://github.com/LearnBoost/cluster) は別物でした。


node.jsを運用に載せる際に、webサーバーをどうしようかなと思っていましたが、 遅ればせながら、clusterが本家に取り込まれていることを知りました。

http://nodejs.org/api/cluster.html

ということで、今後の継続的な開発は全く心配無し。 内容的にも、ダウンタイムゼロでの再起動など、やりたいことを十分満たしていました。

https://github.com/LearnBoost/cluster

  • zero-downtime restart
  • hard shutdown support
  • graceful shutdown support
  • resuscitates workers
  • maintains worker count, even if worker was _SIGKILL_ed.
  • workers commit suicide when master dies
  • spawns one worker per cpu (by default)
  • extensible via plugins

十分すぎるので、しばらくこいつでテストしてみようと思います。

クライアントサイドjsでのi18nと言えば、様々なツールがありますが、 シンプルに自分でも実装できるかなと思い、運用方法も含めて自分たちにあった方法を考えてみました。

方針

基本的には、window.i18nという大きなObjectを作成して、keyにi18nのkey+localeを、valueに翻訳された値を入れておく

後は、適当にsyntax sugar用の関数を用意してあげる

Objectの構造

普通の翻訳ファイルでは、翻訳者が異なることを想定し、言語毎にファイルを作成して運用することが多い。 そのため、トップレベルで言語が分かれるObjectになる。

i18n = 
  ja:
    users:
      name: "名前"
  en:
    users:
      name: "name"

社内ではこの形式で弊社でも運用しているが、 各言語毎にファイルを作るため、言語ファイルが大きくなってくると、 各言語毎に翻訳項目の抜け漏れがないか調べにくくなってきてしまっていた。

そのため、script等を作成してチェックしていたが、なかなか億劫な作業でもある。

そこで、今回は思い切って標準的なトップレベルでの言語切り分けから、 下記のような、最末端での言語切替のObjectで運用してみようと考えた。

i18n = 
  users:
    name:
      ja: "名前"
      en: "name"

メリットとしては、属性毎に翻訳していくので、自然な構造なので、抜け漏れなどが分かりやすく、システム化もしやすいと判断した。

Editor

次に、このObjectをどのように作って行くか考えた。 最終的には、jsonからjsのObjectにするのだが、jsonを手で書くのが面倒なので、 思い切ってnumbers=csv等で管理するのはどうかなと思いついた。

例としてはこんな感じ

key ja en
users.name 名前 name
users.email メールアドレス email

で、このcsvをjsonに変換して、そいつをjsとして読み込んでしまおうという作戦にすることに。

csvにすることで、ささっと手で直すこともできるし、 mergeとかが面倒であれば、GoogleSpreadSheet使っても良いし、Editorとしてはなかなか良い感じがする。

csv2js

次に、csvからjsに変換するscriptを書いた。

まずは、csvからjsonに変換するコードをささっとruby(1.9.3)で。

csv2json

 1 #!/usr/bin/env ruby
 2 
 3 require 'csv'
 4 require 'json'
 5 
 6 reader = CSV.open("./js/i18n.csv","r")
 7 header =  reader.take(1)[0]
 8 
 9 i2s   = header.each.with_index.inject({}){|h,(attr,i)| h[i.to_s]=attr.strip;h }
10 i18n  = {}
11 
12 class Hash
13   def nset(keys=[],val)
14     k = keys.shift
15     keys.empty? ? self[k] = val : (self[k]||={}; self[k].nset(keys,val))
16   end
17 end
18 
19 reader.each do |row|
20   keys = []
21   val = {}
22   row.each.with_index do |c,i|
23     if (attr = i2s[i.to_s]) == "key"
24       keys = c.split(".")
25     else
26       case c
27       when "BLANK"; val[attr] = ""
28       when nil; ""
29       else;     val[attr] = c
30       end
31     end
32   end
33   i18n.nset(keys,val)
34 end
35 
36 puts i18n.to_json
37 

で、こいつを使って、csv2jsをさくっと作成

csv2js

1 #!/bin/bash
2 
3 echo -n "window.i18n="
4 ./script/csv2json

あとは、こいつを所定のファイルに吐き出してあげればOKです。 ここまでで、numbersでcsvを作って、jsのObjectにするまでができました。

syntax sugar 用の関数の作成

で、最後にRailsっぽく、tというメソッドを作りました。 なので、t(“users.name”)とかで呼べます。

 1 $ ->
 2   #### t
 3   # keyとlocaleを設定して翻訳Objectから値を持ってくる
 4   # localeに"obj"が渡されたときは、objを表示する
 5   # * 翻訳があるもの #=> 設定した文字列を表示
 6   # * 翻訳していないものやそもそも存在していないもの #=> keyの最後の部分を表示
 7   window.t =(key,locale)->
 8     try
 9       locale ||= app.currentUser().locale
10       if locale=="obj"
11         val = _.nget window.i18n, key
12       else
13         val = _.nget window.i18n, "#{key}.#{locale}"
14       if val?
15         return val
16       else
17         return key.split(".").last()
18     catch e
19       console.log e
20       return key.split(".").last()

少し拡張して、下記のような仕様にしています。 t(“user.name”) #=> “名前” t(“user.name”,”en”) #=> “name” t(“user.name”,”ja”) #=> “名前” t(“user.name”,”??”) #=> “name”(keyの最後) t(“user.nam”,”ja”) #=> “nam”(keyの最後) t(“user.name”,”obj”) #=> {en: “name”, ja: “名前”}

これで、あとはjsから使えば楽しく使えそうです。 Objectの構造も言語を最後にしているので、

t("user.name","obj")  #=> {en: "name", ja: "名前"}

のようなこともできるようになっています。

まとめ

numbers -> csv -> jsのObject のフローを運用を考えながら作りました。 numbersで見ると翻訳の抜け漏れも分かりやすかったり、ソートできたりと便利な上に、 yamlを翻訳者に書いてもらう必要もなくなりました。

jsだとシンプルにこんなツールが作れるので楽しいですね。

websocketをIEでも使えるらしいjs

前回のポストの値で大丈夫かなと色々調べてみると、mysqltuner.plというものがあることを発見。 早速使ってみました。

利用まで

とりあえずインストールして回してみました。

$ wget https://raw.github.com/rackerhacker/MySQLTuner-perl/master/mysqltuner.pl
$ chmod +x mysqltuner.pl
$ ./mysqltuner.pl
$ ./mysqltuner.pl 
    省略
    -------- Recommendations -----------------------------------------------------
    General recommendations:
        Enable the slow query log to troubleshoot bad queries
        When making adjustments, make tmp_table_size/max_heap_table_size equal
        Reduce your SELECT DISTINCT queries without LIMIT clauses
        Increase table_cache gradually to avoid file descriptor limits
    Variables to adjust:
        query_cache_size (>= 8M)
        tmp_table_size (> 32M)
        max_heap_table_size (> 16M)
        thread_cache_size (> 900)
        table_cache (> 900)
        innodb_buffer_pool_size (>= 2G)

とオススメ値が色々出ました。 これは楽チン

ということで、設定を下記に変更しておきました。

[mysqld]
max_connections=300
query_cache_size=32M
tmp_table_size=1024M
max_heap_table_size=1024M
thread_cache_size=1200
table_cache=1200
innodb_buffer_pool_size=2G