あるモデルの配列から、モデルの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
# セッション新規作成 $ 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
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
$ 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 1 | slct 1 | all 10 | slct 10 | all 100 | slct 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 1 | slct 1 | all 10 | slct 10 | all 100 | slct 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 のマルチカラムインデックス
- 計100レコード
下記表の項目定義
- find_by: find_all_by_str1('hoge')
- cond placeholder: find(:all, :conditions => ["str1 = ?", "hoge"])
- プレースホルダー使用
- cond: find(:all, :conditions => "str1 = 'hoge'")
- プレースホルダー未使用
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
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
iPhone, Android(desire) ブラウザのhtml5対応状況
手元にあるスマートフォンのhtml5対応状況を、http://html5test.com/ で確認したのでメモ。
- iphone4 (4.0.2): Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_0_2 like Mac OS X; ja-jp) AppleWebKit/532.9 (KHTML, like Gecko) Version/4.0.5 Mobile/8A400 Safari/6531.22.7
- iphone3G (3.1.2): Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_1_2 like Mac OS X; ja-jp) AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0 Mobile/7D11 Safari/528.16
- android2.1: Mozilla/5.0 (Linux; U; Android 2.1-update1; ja-jp; HTCX06HT Build/ERE27) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17
- (HTC Desire X06HT, ファームウェアバージョン: 2.1-update1, ブラウザバージョン: WebKit3.1)
(※) ユーザエージェントは、「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。。。