あるモデルの配列から、モデルの1要素を値とする配列を作るメモ

きれいな書き方を教えて頂いたので、メモ


たとえば、id, name, age, job_id といったカラムからなるUserモデルがあったときに、
特定の job_id の User の id 配列が欲しいという際にどう書くか。

# たとえば User.find_all_by_job_id(1)でこんな配列が取れるとする
[
  {1, 'hoge', 21, 1},
  {3, 'bar', 15, 1}
]

# さらにそこから idの配列を作るには・・・?
[1,3]


自分ではこんな感じで書いていたのですが、

ids = User.find_all_by_job_id(1).map{|u| u.id}


こう書けるそうです。

ids = User.find_all_by_job_id(1).map(&:id)

session の保存方法(session_store)の違いによるパフォーマンス(処理速度)テスト

cookie (default), ActiveRecord (:active_record_store), memcached (:mem_cache_store) を比較した。

なお、セッション新規作成時(record insert)と、既にあるセッションでアクセスする時も比較した。

環境

findテストと同じですが、

  • App Server
    • Amazon EC2: Large Instance
    • Apache: デフォルト設定
    • Ruby: 1.8.7
    • Passenger
      • RailsEnv: production
      • RailsMaxPoolSize: 30
      • RailsPoolIdleTime: 1200
    • Rails: 2.3.8
  • Request 兼 DB Server
    • Amazon EC2: Large Instance
    • DB: MySQL
      • my-innodb-heavy-4G.cnf を元にmax_connectionsなど4,5つ値を調整した設定を使用
    • ab (Apache Bench) でApp Serverに対してリクエス
      • 100件同時接続を5秒間続けて、どれくらいリクエストを処理できたかを見る
# セッション新規作成
$ ab -c 100 -t 5 http://ec2-***.amazonaws.com/session_check

# 既にあるセッションでアクセス
$ ab -c 100 -t 5 -C '_session_id=****' http://ec2-***.amazonaws.com/session_check

結果

  off cookie (ins) cookie (slct) activerecord (ins) activerecord (slct) memcache (ins) memcache (slct)
Complete requests 2128.7 2026.6 1981.2 1025.6 1128.2 1600.7 1457.8
Requests per second 425.463 404.809 395.485 204.838 225.427 319.79 291.229

  • off: セッション無し
  • (ins): セッション新規作成時
  • (slct): 既にあるセッションでアクセス時


cookie > memcache > activerecord の順番は予想通りだけど、
思った以上に activerecord 遅い。。

routes の(記述量と)記述箇所の違いによるパフォーマンス(処理速度)テスト

下のような config/routes.rb を用意して、
routes_check の map.connect をループの上に書いたときと、
ループの下(コメントアウトされてる箇所)に書いたときで比較した。

ActionController::Routing::Routes.draw do |map|
  map.connect '/routes_check', :controller => 'routes_check', :action => 'index'

  1.upto(100) do |i|
    map.resources "route1_#{i}"
  end
#  map.connect '/routes_check', :controller => 'routes_check', :action => 'index'

  map.connect ':controller/:action/:id'
  map.connect ':controller/:action/:id.:format'
end

環境

フィルタテスト の時と同じ。

  • App Server
    • Amazon EC2: Large Instance
    • Apache: デフォルト設定
    • Ruby: 1.8.7
    • Passenger
      • RailsEnv: production
      • RailsMaxPoolSize: 30
      • RailsPoolIdleTime: 1200
    • Rails: 2.3.8
  • Request Server
    • Amazon EC2: Large Instance
    • ab (Apache Bench) でApp Serverに対してリクエス
      • 100件同時接続を5秒間続けて、どれくらいリクエストを処理できたかを見る
$ ab -c 100 -t 5 http://ec2-***.amazonaws.com/routes_check

結果

上記、abコマンドを10回実行した平均の「Complete requests*1」「Requests per second*2」を結果とした

  ループの上 ループの下
Complete requests 2225.8 1171
Requests per second 444.536 233.508

map.resources 100個は極端な例かもしれないが、
それにしても記述箇所によって想像以上に速度が違ったので、驚いた。

*1:処理できたリクエスト総数

*2:1秒間に処理したリクエスト数

find方法の違いによるパフォーマンス(処理速度)テスト

環境

前回のフィルタテストと同じですが、

  • App Server
    • Amazon EC2: Large Instance
    • Apache: デフォルト設定
    • Ruby: 1.8.7
    • Passenger
      • RailsEnv: production
      • RailsMaxPoolSize: 30
      • RailsPoolIdleTime: 1200
    • Rails: 2.3.8
  • Request 兼 DB Server
    • Amazon EC2: Large Instance
    • DB: MySQL
      • my-innodb-heavy-4G.cnf を元にmax_connectionsなど4,5つ値を調整した設定を使用
    • ab (Apache Bench) でApp Serverに対してリクエス
      • 100件同時接続を5秒間続けて、どれくらいリクエストを処理できたかを見る
$ ab -c 100 -t 5 http://ec2-***.amazonaws.com/find_check

「select *」 VS 「select id」

全カラムを取得するのと、対象カラムをidだけに絞って取得する場合の比較

カラム数13のモデルの場合

13カラムの内訳: id, stringカラム1-5, integerカラム1-5, created_at, updated_at

下記結果の表の項目定義

  • all: Model.all
    • 全カラム取得(select *)
  • slct: Model.find(:all, :select => "id")
    • idのみ取得(select id)
  • all, slct の後の数字(1, 10, 100): 取得されるレコード数

 all 1slct 1all 10slct 10all 100slct 100
Complete requests 1877.1 1922.8 1776.5 1891.5 1219.8 1482.2
Requests per second 375.029 384.227 354.799 377.934 243.623 296.103

カラム数103のモデルの場合

103カラムの内訳: id, stringカラム1-50, integerカラム1-50, created_at, updated_at

 all 1slct 1all 10slct 10all 100slct 100
Complete requests 1801.6 1877 1509.7 1878.3 511.8 1529.2
Requests per second 359.862 374.8 301.463 375.075 102.025 305.372


予想通りだけど、取得対象を絞った方が速い。

「find_all_by」 VS 「conditions」

条件が1つの場合
  • 13カラムのモデルを使用(id, str1-5, int1-5, created_at, updated_at)
  • data
    • 計100レコード
      • id=1-50 のレコードには、str1='hoge', int2=1, str3='foo' が入っている (他はNULL)
      • str1, int2, str3 のマルチカラムインデックス

下記表の項目定義

  find_by cond placeholder cond
Complete requests 1422.4 1476.3 1457.2
Requests per second 284.058 294.529 290.91

条件が3つ
  • find_by: find_all_by_str1_and_int2_and_str3('hoge', 1, 'foo')
  • cond placeholder: find(:all, :conditions => ["str1 = ? AND int2 = ? AND str3 = ?", "hoge", 1, "foo"])
  • cond: find(:all, :conditions => "str1 = 'hoge' AND int2 = 1 AND str3 = 'foo'")

  find_by cond placeholder cond
Complete requests 1339 1395.9 1471.6
Requests per second 267.582 278.882 293.923


これはまぁ、どっち使ってもOKかな

「find」 VS 「find_by_sql」 VS 「named_scope」

  • 13カラムのモデルを使用(id, str1-5, int1-5, created_at, updated_at)
  • 計100レコード

下記表の項目定義

  • find: find(:all, :conditions => "id < 51 AND str1 = 'hoge'", :order => "id DESC", :limit => 10, :offset => 5)
  • sql: SELECT * FROM find_check10s WHERE id < 51 AND str1 = 'hoge' ORDER BY id DESC LIMIT 5, 10
  • ns all: named_scope で、all_in_one
    • Model.all_in_one
  • ns partial: named_scope で、ばらばら
    • Model.id_lt_51.str1_is_hoge.default_order.default_limit.default_offset

  find sql ns all ns partial
Complete requests 1753 1849.4 2313.6 2162.5
Requests per second 350.258 369.645 462.212 432.27


なぜ named_scope がこんなに良い結果になるのか。。。
find と find_by_sql は大して差は出ないらしい。

filterのパフォーマンステスト

before_filter, after_filter, around_filter を枚数重ねて、
どのくらいパフォーマンスに影響が出るのかをざっくり調べてみた

環境

  • App Server
    • Amazon EC2: Large Instance
    • Apache: デフォルト設定
    • Ruby: 1.8.7
    • Passenger
      • RailsEnv: production
      • RailsMaxPoolSize: 30
      • RailsPoolIdleTime: 1200
    • Rails: 2.3.8
  • Request Server
    • Amazon EC2: Large Instance
    • ab (Apache Bench) でApp Serverに対してリクエス
      • 100件同時接続を5秒間続けて、どれくらいリクエストを処理できたかを見る
$ ab -c 100 -t 5 http://ec2-***.amazonaws.com/filter_check

結果

先に結果を

上記、abコマンドを10回実行した平均の「Complete requests*1」「Requests per second*2」を結果とした

表の項目名定義

  • ctrler_10: コントローラ内の同一メソッドを10枚
  • ctrler_100: コントローラ内の同一メソッドを100枚
  • cls_10: filterクラス(new無し)を10枚
  • cls_100: filterクラス(new無し)を100枚
  • cls_new_10: filterクラス(new有り)を10枚
  • cls_new_100: filterクラス(new有り)を100枚

なお、filterの中身は空

filter 無し
  • Complete requests: 2442.1
  • Requests per second: 487.897
before_filter

  ctrler_10 ctrler_100 cls_10 cls_100 cls_new_10 cls_new_100
Complete requests 2255.1 1543.9 2148 1492.4 2244.6 1467.5
Requests per second 450.536 308.538 429.212 298.123 448.455 293.194

after_filter

  ctrler_10 ctrler_100 cls_10 cls_100 cls_new_10 cls_new_100
Complete requests 2245.1 1482 2205 1555.8 2187.3 1444.5
Requests per second 448.375 295.654 440.341 310.75 436.62 288.5

around_filter

  ctrler_10 ctrler_100 cls_10 cls_100 cls_new_10 cls_new_100
Complete requests 1864 167 1465.3 15 1453.9 11.8
Requests per second 372.369 33.291 292.664 2.902 290.409 2.292

before_filter & after_filter

  ctrler_10 ctrler_100 cls_10 cls_100 cls_new_10 cls_new_100
Complete requests 2079 1071.6 2077.3 1011.8 2038.7 1042.4
Requests per second 415.254 214.024 415.154 201.969 407.193 208.08

感想

before_filter, after_filter 10枚くらいだと、
コントローラ内メソッドでも、filterクラスでもそれほど差はないし、
問題になるほどではなさそう。


around_filter は結構重い
おまけに、コントローラ内メソッドではなく、filterクラスだとさらに。。。

コード

### app/controllers/filter_check_controller.rb
class FilterCheckController < ApplicationController
  # ctrler_10,100
  #before_filter :ctrler_before_filter, :ctrler_before_filter, :ctrler_before_filter, ...

  # cls_10,100
  #before_filter TestFilter, TestFilter, TestFilter, ...

  # cls_new_10,100
  #before_filter TestFilter2.new, TestFilter2.new, TestFilter2.new, ...


  def index
    render :text => "done"
  end

  private
  # コントローラ内のfilter用メソッド
  def ctrler_before_filter
  end

  def ctrler_after_filter
  end

  def ctrler_around_filter
    yield
  end
end

### lib/test_filter.rb
# filterクラス(new無し)
class TestFilter
  def self.before(controller)
  end
  def self.after(controller)
  end
end

### lib/test_filter2.rb
# filterクラス(new有り)
class TestFilter2
  def before(controller)
  end

  def after(controller)
  end
end

*1:処理できたリクエスト総数

*2:1秒間に処理したリクエスト数

paperclip の保存ディレクトリ名あるいはファイル名をid連番ではなく、MD5とかSHA1のハッシュ値にするメモ

画像を扱う際のrailsプラグインpaperclip
GitHub - thoughtbot/paperclip: Easy file attachment management for ActiveRecord

基本的な使い方は、githubとか紹介ページを参照してもらうとして、
ここでは画像保存のディレクトリ名あるいはファイル名をid連番ではなく、
推測しにくい値にする方法をメモしておきます

前置き

まず、デフォルトの設定だと保存pathは、

:rails_root/public/system/:attachment/:id/:style/:filename

らしいです。

なので、例えばimageモデルで、

  has_attached_file :image,
    :styles => {
      :thumb   => "100x100#",
      :mini   => "30x30#"
    }

のようにしていたとすると、
hoge.jpgをアップロードして保存した際には、各画像は下記のpathで保存されます


public/system/images/1/original/hoge.jpg
public/system/images/1/thumb/hoge.jpg
public/system/images/1/mini/hoge.jpg


上のようにデフォルトのまま使わないにしても、
画像ごとにユニークなpathを作るために、
ディレクトリ名かファイル名のどこかにidを入れる可能性は高いかなと思います
例えば、

  has_attached_file :image,
    :styles => {
      :thumb   => "100x100#",
      :mini   => "30x30#"
    },
    :path => ":rails_root/public/sys_img/:id/:style.:extension",
    :url  => "#{ActionController::Base.relative_url_root}/sys_img/:id/:style.:extension"


public/sys_img/1/original.jpg
public/sys_img/1/thumb.jpg
public/sys_img/1/mini.jpg


ただこのような連番idでは、URLのid部分を変更して直アクセスすることで、
他の画像を見ることができるということを、ユーザに想像される可能性は高いです


別に、見られて問題ない画像しかなければ良いですが、
例えば、予め画像データだけはアップロードしてあるが、
まだ、サイトにはリンクや埋め込みをしておらず、
それをするまでは画像を見られたくない場合において、連番idは危険度が高いと思います


確実な解決方法としては、
「画像の公開日付みたいなカラムを持ち、
画像をユーザが直接アクセスできない場所に保存して、
コントローラ経由で画像を出力する」、ことだとは思いますが、
そこまでしなくても、「連番idをやめて、推測しにくいpathに画像を保存する」
ことができればそれで良い場合もあると思います

で、どうするかというと

で、前置きが長くなっちゃいましたが、どうするかというと、
config/initializers/ の下に適当なファイル名(例えば、paperclip.rb)でファイルを作って、その中に次のように記述します

Paperclip::Attachment.interpolations[:id_sha1] = proc do |attachment, style|
  Digest::SHA1.hexdigest(attachment.instance.id.to_s)
end

で、モデルでは定義した :id_sha1 を使って、

  has_attached_file :image,
    :styles => {
      :thumb   => "100x100#",
      :mini   => "30x30#"
    },
    :path => ":rails_root/public/sys_img/:id_sha1/:style.:extension",
    :url  => "#{ActionController::Base.relative_url_root}/sys_img/:id_sha1/:style.:extension"

とすることで、idのSHA1ハッシュ値ディレクトリ名にすることができます


public/sys_img/356a192b7913b04c54574d18c28d46e6395428ab/original.jpg
public/sys_img/356a192b7913b04c54574d18c28d46e6395428ab/thumb.jpg
public/sys_img/356a192b7913b04c54574d18c28d46e6395428ab/mini.jpg

もちろんURLも:urlに書いておけばちゃんと作ってくれます

# viewファイルにて
image.image.url(:mini)  # /sys_img/356a192b7913b04c54574d18c28d46e6395428ab/mini.jpg


画像の created_at とか secret_key を混ぜれば、ほぼ推測されることはないかと思います
config/initializers/paperclip.rb

Paperclip::Attachment.interpolations[:mix_sha1] = proc do |attachment, style|
  secret_key = "paperclip_test's secret key string"
  Digest::SHA1.hexdigest("#{attachment.instance.id}#{attachment.instance.created_at.to_i}#{secret_key}")
end


参考:http://d.hatena.ne.jp/hichiriki/20081130

iPhone, Android(desire) ブラウザのhtml5対応状況

手元にあるスマートフォンhtml5対応状況を、http://html5test.com/ で確認したのでメモ。

(※) ユーザエージェントは、「javascript:alert(navigator.userAgent)」で表示されたもの

iPhone4 iPhone3G Android2.1
Parsing rules <!DOCTYPE html> triggers standards mode YES YES YES
HTML5 tokenizer NO NO NO
HTML5 tree building NO NO NO
SVG in text/html NO NO NO
MathML in text/html NO NO NO
Canvas canvas element YES YES YES
2D context YES YES YES
Text YES YES YES
Video video element YES YES YES
Subtitle support NO NO NO
Poster image support YES YES YES
MPEG-4 support YES YES NO
H.264 support YES YES NO
Ogg Theora support NO NO NO
WebM support NO NO NO
Audio audio element YES YES YES
PCM audio support YES YES NO
MP3 support YES YES NO
AAC support YES YES NO
Ogg Vorbis support NO NO NO
WebM support NO NO NO
Local devices device element NO NO NO
Elements Embedding custom non-visible data NO NO NO
Section elements 6 out of 7 NO NO
Grouping content elements NO NO NO
Text-level semantic elements NO NO NO
hidden attribute NO NO NO
Scroll into view YES YES YES
contenteditable attribute YES YES YES
Forms input element types YES 5 out of 13 6 out of 13
input element attributes 8 out of 10 1 out of 10 5 out of 10
Other form elements NO NO 1 out of 5
Form validation YES NO NO
User interaction Drag and drop YES NO YES
Undo history NO NO NO
Session history YES NO NO
Text selection YES YES YES
Microdata Microdata NO NO NO
Web applications Application Cache YES YES YES
Custom scheme handlers YES NO NO
Custom content handlers YES NO NO
Geolocation Geolocation YES YES YES
WebGL 3D context NO NO NO
Communication Cross-document messaging YES YES YES
WebSocket NO NO NO
Server-Sent Events YES NO NO
Files FileReaderAPI NO NO NO
Storage Session storage YES YES YES
Local storage YES YES YES
IndexedDB NO NO NO
Web SQL Database YES YES YES
Workers Web Workers NO NO YES

WebSocket。。。