RSpec+Capybaraによるチェックボックスの存在確認とCookieの有効期限日を取得する方法
Ruby on Rails Tutorial*1の9.6 Exercisesを終えた後、"Remember me"というチェックボックスをSign inページに追加しようと思いました。
この本で作成するマイクロブログ「Sample App」は、ログイン後、Cookieに保存する"remember_token"の有効期限日を20年後*2に設定しています*3。[Remember me]チェックボックスをチェックせずに[Sign in]ボタンを押下した場合、このクッキーの有効期限日をセッション終了時になるよう改修してみました。
Sample Appは、RSpec+CapybaraによるTDDで開発されていますので、私もそれに従います。
[追記: 8/29 23:19]
第3版の第8章で[Remember me]チェックボックスを追加するそうです。本記事は、第3版が出版される以前に書いたもので、関係はありません。*4
Solrのコアに写真の撮影日をTika経由で登録する
Apache Solrのコアに、Exifで記録された撮影日をTika経由で登録してみました。Solrのバージョンは、4.9.0です。
solrconfig.xml
uprefixの「ignored_」が、ここで設定しなかった撮影情報の接頭語になります。
<requestHandler name="/update/extract" startup="lazy" class="solr.extraction.ExtractingRequestHandler" > <lst name="defaults"> <str name="lowernames">true</str> <str name="uprefix">ignored_</str> <str name="captureAttr">true</str> <str name="fmap.date_time_original">date_time_original</str> </lst> <lst name="date.formats"> <str>yyyy:MM:dd HH:mm:ss</str> </lst> </requestHandler>
最後の「date.formats」は、Tikaが撮影情報を読み取ると「2014:07:16 21:19:47」という形式でSolrに返します。しかし、この日時形式は標準ではサポートされていない為、日時の書式を追加しました。追加しなかった場合、写真をPOSTすると以下のエラーがSolrから返ってきます。
<lst name="responseHeader"> <int name="status">400</int> <int name="QTime">1455</int> </lst> <lst name="error"> <str name="msg">Invalid Date String:'2014:07:16 21:19:47'</str> <int name="code">400</int> </lst>
schema.xml
コアのスキーマに、撮影日(DateTimeOriginal)のフィールドを追加しました。ダイナミックフィールドの「ignored_*」は、スキーマに定義されていない撮影情報に関する設定です。
<field name="id" type="string" indexed="true" stored="true" required="true" multiValued="false"/> <field name="date_time_original" type="tdate" indexed="true" stored="true"/> <dynamicField name="ignored_*" type="string" indexed="false" stored="false" multiValued="true"/> <uniqueKey>id</uniqueKey>
写真のアップロード
curlコマンドで写真をアップロードしてテストしました。
$ curl 'http://127.0.0.1:8983/solr/exif/update/extract?literal.id=1&commit=true' -F "myFile=@DSCN0005.jpg" <?xml version="1.0" encoding="UTF-8"?> <response> <lst name="responseHeader"><int name="status">0</int><int name="QTime">1297</int></lst> </response>
余談
日付型のフィールドに撮影日を登録する方法で悩んだので記事にしました。日付型にしたかったのは、撮影日でファセット検索したかったからです。
テーブルのヘッダと左列を固定するプラグイン
fixedTblHdrLftColというjQueryのプラグインを作りました。 これは、テーブルのヘッダ(THEAD要素)と左列(固定するTD要素数を指定)を固定します。
仕様
- jQuery v2.1.1+
- ヘッダはTHEAD要素内、内容はTBODY要素内。
設定
Option 1 | Option 2 | Option 3 | 既定値 | データ型 | 備考 |
---|---|---|---|---|---|
scroll {} | height | null | String | スクロール領域の高さ | |
width | null | String | スクロール領域の幅 | ||
headRow {} | className | 'fTHLC-head-row' | String | 固定したTH要素のクラス名 | |
enabled | true | Boolean | スクロールイベントの有効・無効 | ||
overflow | 'auto' | String | CSSのoverflowプロパティ | ||
leftCol {} | className | 'fTHLC-left-col' | String | 固定したTD要素のクラス名 | |
enabled | true | Boolean | スクロールイベントの有効・無効 | ||
overflow | 'auto' | String | CSSのoverflowプロパティ | ||
fixedSpan | 1 | Number | 固定する左列の数 | ||
syncWith | null | String | 同期するテーブルのjQueryセレクタ | ||
wrapper {} | outer {} | idName | null | String | 外側DIV要素のID名 |
className | 'fTHLC-outer-wrapper' | String | 外側DIV要素のクラス名 | ||
inner {} | idName | null | String | 内側DIV要素のID名 | |
className | 'fTHLC-inner-wrapper' | String | 内側DIV要素のクラス名 | ||
corner {} | append | true | Boolean | 左上の角を追加 | |
deepClone | false | Boolean | ディープクローンする | ||
outer {} | idName | null | String | 外側DIV要素のID名 | |
className | 'fTHLC-outer-corner' | String | 外側DIV要素のクラス名 | ||
inner {} | idName | null | String | 内側DIV要素のID名 | |
className | 'fTHLC-inner-corner' | String | 内側DIV要素のクラス名 |
未対応
COLSPAN属性やROWSPAN属性には対応していません。
サンプル
HTML5/CSS3で書いています。jQuery CDNとYUI CSS Reset CDNを使っています。
ライセンス
MIT licenceでリリースしました。
余談
GitHubで公開し、jQuery Plugin Registryに登録したのは、20日以上も前の事なのですが、やっと記事を追加できる時間ができたので、READMEの日本語版を書いてみました。
Ruby on Rails Tutorialのsample_appをさくらVPSにインストールした手順
Ruby on Rails Tutorialで作る「sample_app」をさくらVPSにインストールしました。これは、その備忘録です。
構築した環境は、CentOS 6.4+Apache Httpd 2.2.15-28+MySQL 5.1.69をベースに、rvm 1.20.13+Ruby 1.9.3+Passenger 4.0.5+Rails 3.2.13です。
複数のRailsアプリが、同一バージョンのRubyで稼働するものとして、Passengerは「global」にインストールし、アプリ毎にgemsetを用意するという方針にしました。
謝辞
インストールにあたって、以下のサイトを参考にさせていただき、誠にありがとうございました。
前提
Apache HttpdとMySQLは、yum installでインストールし、設定済みです。
この順番で実行したかと思います。申し訳ありませんが、試行錯誤した結果なので、記載漏れなどがあるかもしれません。
rvmとRubyのインストール
$ sudo bash -s stable < <(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer ) $ sudo usermod -a -G rvm root $ sudo gpasswd -a [USER_NAME] rvm $ sudo gpasswd -a apache rvm $ source /etc/profile.d/rvm.sh $ su # rvm get head # exit (この後、一旦、再起動させたと思います。) $ sudo reboot $ rvm install 1.9.3 $ gem install bundler --no-ri --no-rdoc
Passengerのインストール
$ vi .gemrc gem: --no-rdoc --no-ri $ rvm gemset use global $ gem install passenger $ passenger-install-apache2-module $ sudo vi /etc/httpd/conf.d/passenger.conf LoadModule passenger_module /usr/local/rvm/gems/ruby-1.9.3-p429@global/gems/passenger-4.0.5/libout/apache2/mod_passenger.so PassengerRoot /usr/local/rvm/gems/ruby-1.9.3-p429@global/gems/passenger-4.0.5 PassengerDefaultRuby /usr/local/rvm/wrappers/ruby-1.9.3-p429@global/ruby
sample_appのインストール
sample_appで必要なgemは、「sample」というgemsetにインストールしています。
$ git clone git@github.com:[USER_NAME]/sample_app.git $ cd sample_app $ vi Gemfile gem 'execjs' gem 'therubyracer' group :production do gem 'mysql2' end $ rvm gemset create sample $ rvm gemset use sample --default $ bundle install $ vi config/setup_load_paths.rb if ENV['MY_RUBY_HOME'] && ENV['MY_RUBY_HOME'].include?('rvm') begin gems_path = ENV['MY_RUBY_HOME'].split(/@/)[0].sub(/rubies/, 'gems') ENV['GEM_PATH'] = "#{gems_path}:#{gems_path}@global" require 'rvm' RVM.use_from_path! File.dirname(File.dirname(__FILE__)) rescue LoadError # RVM is unavailable at this point. raise "RVM ruby lib is currently unavailable." end end ENV['BUNDLE_GEMFILE'] = File.expand_path('../Gemfile', File.dirname(__FILE__)) require 'bundler/setup' $ vi .ruby-version 1.9.3-p429 $ vi .ruby-gemset sample $ mkdir tmp $ sudo chown apache:apache tmp $ touch log/production.log $ sudo chmod 0666 log/production.log $ rake db:create RAILS_ENV=production $ rake db:migrate RAILS_ENV=production $ sudo rake assets:precompile RAILS_ENV=production
httpd.confの設定
VirtualHostの設定を既存のhttpd.confに追加しました。
一般公開するわけではありませんので、Digest認証をかけています。
また「/home/[USER_NAME]」ディレクトリは、予め「sudo chmod o+r /home/[USER_NAME]」して、apacheユーザが読めるようにしています。
$ cd /var/www $ sudo ln -s /home/[USER_NAME]/sample_app sample_app $ sudo vi /etc/httpd/conf/httpd.conf <VirtualHost *:80> ServerName [FQDN] RackEnv production DocumentRoot /var/www/sample_app/public ErrorLog /var/log/httpd/sample_app.error_log CustomLog /var/log/httpd/sample_app.access_log combined <Directory /var/www/sample_app/public> AuthType Digest AuthName "Auth Zone" AuthUserFile /etc/httpd/conf/.htdigest Require valid-user Options -Indexes -MultiViews AllowOverride all </Directory> </VirtualHost>
[追記:7/1 19:08]
Apacheのhttpd.confで「KeepAlive On」に変更。
[追記: 7/2 0:19]
さらに、「Header always unset X-Powered-By」を追加。
httpdの再起動
$ service httpd configtest Syntax OK $ sudo service httpd restart
余談
インストール中につまずいた箇所。
- gemsetを「global」から「sample」に変更する方法がよくわからず丸2日悩む。→「config/setup_load_paths.rb」を作成して対処。
- assetsのprecompleに「execjs」などがGemfileに必要だったこと。
GitHubにpushしたら、自動的にテストして、問題なければ本番環境にデプロイする仕組みを入れたい。Jenkins CIにチャレンジしてみようかな...
[追記: 8/20 13:42]
その後、Jenkins CIを入れてみたり、nginx+unicornに変えてみるなど、さくらVPSの1Gプラン(月980円)でかなり遊ばせてもらってます。
exif_thumbnailを作った理由と実験
libthumbを作ったのは、『サムネイルの一覧を表示する際、Exifの全タグを読み込む必要はないのでは?』という単純な思いつきからでした。
アプリがネイティブであろうがウェブであろうが、サムネイルの一覧はできるだけ早く表示し、後でユーザがサムネイルをクリックしたら、その画像ファイルのExifのタグを改めて読み込めばいい。
また、libthumbのRuby拡張ライブラリ、exif_thumbnailを作ったのは、Ruby on RailsやSinatraなどでlibthumbを使いたかったからです。
そこで、exifrとexif_thumbnailをRubyのBenchmarkモジュールで比較してみました。
前提
- MacBook Air (13-inch, Mid 2012, Intel Core i7 2GHz, RAM 8GB, SSD 256GB)
- OS X 10.8.3
- ruby 1.9.3p429 (2013-05-15 revision 40747) [x86_64-darwin12.3.0]
- exifr (1.1.3)
- exif_thumbnail (0.0.1)
- FOOD.JPG, 1.2MB, 1536x2048, Apple iPhone 3GS(テスト画像)
ソース
第一引数で指定したExifファイルからサムネイルを取り出し、それを第二引数で指定したファイル名で保存するプログラムです。test.rbとして保存し、実行しました。
require 'benchmark' require 'exifr' require 'exif_thumbnail' def use_exifr File.binwrite(ARGV[1], EXIFR::JPEG.new(ARGV[0]).thumbnail); end def use_exif_thumbnail File.binwrite(ARGV[1], EXIFThumbnail.read(ARGV[0]).data) end n = 1000 Benchmark.bm(17) do |x| x.report("exifr : ") { for i in 1..n; use_exifr; end } x.report("exif_thumbnail : ") { for i in 1..n; use_exif_thumbnail; end } end
結果
$ ruby test.rb /Users/nkmrshn/Pictures/FOOD.JPG /Users/nkmrshn/Desktop/Thumbnail.jpg user system total real exifr : 1.040000 0.080000 1.120000 ( 1.116702) exif_thumbnail : 0.030000 0.080000 0.110000 ( 0.163770)
考察
systemは変わらないのに、userを見るとexifrの方が遅いです。これは、Exifのタグをexifrは全て読み込んでいるのが原因かは、このテストではわかりません。しかし、サムネイルだけ*1を取得したい場合は、exif_thumbnailの方が早いことがわかりました。
余談
これは、愚考だ。そもそも、サムネイルが必要なら画像ファイルをサーバなどに保存する時点で、サムネイルを抽出・保存しておけばいいだけの話ではないか。
*1:サムネイル(data)以外にサムネイルサイズ(length)と回転方向(orientation)も取得しています。
libthumbのRuby拡張ライブラリ
Exifファイルのサムネイルを取得するライブラリ(libthumb)をRubyで使いたいと思い、拡張ライブラリを作ってみました。
gemファイル
Exifファイルのサムネイルを取得するライブラリ
指定したExifファイルから、サムネイルを取得するライブラリを作ってみました。サムネイルのサイズと回転方向も取得します。