2011年9月26日月曜日

capybaraをwebkitやseleniumとかで動かす場合の注意点と解決策

■概要


capybaraとwebkit(たぶんselenium)を利用する際は、capybara側がブラウザとやりとりする為のスレッドを立ち上げる為
RSpec.configure do |config|
  ...
  config.use_transactional_fixtures = false
end
してfixtureのトランザクション制御をあきらめる必要があります。これでは少し都合が悪いので、 DatabaseCleanerを利用して代用する等行う必要があります。

ところがrailsコアチームのjosevalim氏が解決策をぼそっとつぶやいてました。実際にやってみると上手く動くだけでなく実行速度がかなり改善されました。

■解決策


つぶやきで紹介されている方法は非常に簡単です。
spec_helper(test_helper.rb)で下記の用に追記しましょう。
RSpec.configure do |config|
  ...
  config.use_transactional_fixtures = true
end

# 下記追加
class ActiveRecord::Base
  mattr_accessor :shared_connection
  @@shared_connection = nil

  def self.connection
    @@shared_connection || retrieve_connection
  end
end

ActiveRecord::Base.shared_connection = ActiveRecord::Base.connection
でいつもどおり

$ bundle exec rake spec

して見ましょう。specの実行速度が改善したのでは無いでしょうか?

ね、簡単でしょう!

@josevalim氏に多謝!

2011年9月25日日曜日

rails3.0の頃からvalidationにコンテキストを指定できる様になってた

■概要

railsは従来modelで入力チェックを行ってました。#saveメソッドを呼び出し時に、#valid?メソッドが呼び出される事により、入力チェックを行っている人が多いのでは無いでしょうか?

ただし、この仕様は、たまに上手く行かない時があり、「XXという画面ではYYの入力チェックだけしたい」というニーズに答えるにはあまり良い方法がありませんでした(泥臭い方法で解決する)。

ところが、先日railsのソースを読んでいると

activerecord-3.1.0/lib/active_record/validations.rb
   def valid?(context = nil)
      context ||= (new_record? ? :create : :update)
      output = super(context)
      errors.empty? && output
    end
とcontextが引数に渡せる仕様になってるので早速試してみました。

■検証

実験用のPostモデルを下記の用にしました。
class Post < ActiveRecord::Base
  validates_presence_of :title, :on => :bar
end
でrails cで動かしてみました。
$ rails c
Loading development environment (Rails 3.1.0)
ruby-1.9.2-p180 :001 > post = Post.new
ruby-1.9.2-p180 :002 > post.valid?(:bar)
 => false
ruby-1.9.2-p180 :003 > post.valid?(:foo)
 => true
ruby-1.9.2-p180 :004 > post.save(:context => :bar)
 => false
ruby-1.9.2-p180 :005 > post.save(:context => :foo)
  SQL (46.0ms)  INSERT INTO "posts" ("created_at", "title", "updated_at") VALUES (?, ?, ?)  [["created_at", Sat, 24 Sep 2011 18:03:18 UTC +00:00], ["title", nil], ["updated_at", Sat, 24 Sep 2011 18:03:18 UTC +00:00]]
 => true
おぉ!いい感じにcontextを意識できてますね!
ソースを読むとコンテキスト未指定時は、新規なら:create、更新なら:updateをコンテキストとして採用しており、過去との互換性を維持しているという事がわかりました。

■雑感

https://github.com/rails/rails/commit/5c245b91d2dbc0b300e8193310b3f950d0cf6c4b#activerecord/lib/active_record/validations.rb
で追加されてました。2010/05/10に追加された様です。

2011年9月24日土曜日

RewriteCondが効かずに困った

言わずと知れた、apacheのmod_rewriteエンジンですがRewriteCondとRewriteRuleの記述方法にはまってしまいました。普通の書き方は他に任せるとしてハマったポイントを書いておきます。

■RewriteCondは直後のRewriteRuleのみに適用される。
RewriteEngine On
RewriteCond %{HTTP_REFERER} $^ …①
RewriteRule ^/foo/(.*) /foo2/$1 [L] …②
RewriteRule ^/bar/(.*) /bar2/$1 [L] …③
上記の様な設定の時に①は、②にも③にも効きそうですが、実際には②にしか効きません。
だからリファラーがあっても、barはbar2にリダイレクトしてしまいます。

③にも効かせるには、(冗長ですが)下記の様に書く必要があります。

RewriteEngine On
RewriteCond %{HTTP_REFERER} $^
RewriteRule ^/foo/(.*) /foo2/$1 [L]
RewriteCond %{HTTP_REFERER} $^   # 同じ記載をする!
RewriteRule ^/bar/(.*) /bar2/$1 [L]

■RewriteCondはRewriteRuleの後に評価される。

RewriteEngine On
RewriteCond %{HTTP_REFERER} $^
RewriteRule ^/foo/(.*) /foo2/$1 [L]

上記の設定の場合「リファラーが空なら、リクエストが/foo/*の場合、/foo2/*へリダイレクトと思いたい所ですが、実際には「リクエストが/foo/*の場合、リファラーが空なら、/foo2/*へリダイレクト」という処理の流れになります。

「何でRewriteLogLevelを上げて見てみても、RewriteCond実行されてない!効いてないんちゃうか?」って時はこれが原因の可能性が高いです。

2011年9月20日火曜日

spec実行時にdb:test:prepareを呼び出したくない。


■概要

(激しく既出感ありですが。。)何年もrailsやってますが、正しく理解できていなかった事の一つに

  rake spec(test)すると、処理の一環としてdb:test:prepareタスクを呼び出す

という挙動があります。
通常問題にならないのですが、db:test:prepareが、db/schema.rbの情報を元にデーターベースを作成する為、railsが認識できないような項目は抜けて落ちてしまいます。

  • 関数適用したインデックス
  • トリガーやファンクション
  • 別スキーマに作ったオブジェクト ...etc

なので

RAILS_ENV=test rake db:drop
RAILS_ENV=test rake db:create
RAILS_ENV=test rake db:migrate

してからrake specする事を前提に、db:test:prepareを呼び出さずにspecを実行する方法を模索しました。

■案1 db:test:prepare自体を書き換える

参照先にそのまんまの解決策が書かれています。
Rakefileでtask削除用のメソッドを用意します(load_tasksの前に!)。
Rake::TaskManager.class_eval do
  def remove_task(task_name)
    @tasks.delete(task_name.to_s)
  end
end

lib/tasks/db/test.rakeとかを作って、実際にタスクを上書きします
Rake.application.remove_task 'db:test:prepare'

namespace :db do
  namespace :test do 
    task :prepare do |t|
      # rewrite the task to not do anything you don't want
    end
  end
end

参考 http://stackoverflow.com/questions/1097845/how-to-prevent-rake-test-to-call-task-dbtestprepare

■案2 自力でspecを実行する

db:test:prepareを壊すのは怖いので...という方にはこのやり方

lib/tasks/spec.rakeとかを作って
require 'rake'
require 'rspec/core/rake_task'

namespace :spec do
  RSpec::Core::RakeTask.new('no_prepare_db') do |t|
    t.pattern = ['spec/**/*_spec.rb']
  end
  namespace :no_prepare_db do
    %w(controllers helpers lib mailers models requests routing views).each do |dir|
      RSpec::Core::RakeTask.new(dir) do |t|
        t.pattern = ["spec/#{dir}/**/*_spec.rb"]
      end
    end
  end
end
で、rake spec:no_prepre_dbや、rake spec:no_prepare_db:modelsで、db:test:prepareを呼び出さずに実行できます。

参考 http://old.nabble.com/db%3Atest%3Aprepare-interfering-with-rake-spec-pattern-td31075510.html

2011年9月18日日曜日

rails3.1時代のautocompleteは(一部駄目だし)

railsを始めて触った時は、圧倒的に簡単に利用できるautocomplete機能に驚かされたものです(あ~懐かしい。。)。
あるバージョンからautocompleteはplugin側に外れたのですが、rails3時代にはどのplugin(gem)が利用されているんでしょうか?
rails3.1からはjqueryが標準になったので、rails3 - jquery - autocompleteという切り口で探してみました。
すると、そのまんまなgemがありました。
https://github.com/crowdint/rails3-jquery-autocomplete

■インストール

Gemfileに
gem 'rails3-jquery-autocomplete'

でinstall
$ bundle install

次にコード生成して
rails generate autocomplete:install

# generatorのrails3.1対応はまだみたいなのでautocomplete-rails.jsは、public/assets/javascripts
# に手動コピーしましょう。

jsを読み込みましょう
javascript_include_tag "autocomplete-rails.js"

application.jsでjquery-uiを読み込む用にしましょう。
//= require jquery-ui

後defaultではcssがしょんぼりなので、public/assets/stylesheetsに
https://raw.github.com/crowdint/rails3-jquery-autocomplete-app/master/public/stylesheets/jquery-ui-1.8.2.custom.css
をおきましょう。

■利用方法
controllerに
class ProductsController < Admin::BaseController
  # モデル名、フィールド名
  autocomplete :brand, :name
end

routes.rbに
resources :products do
  get :autocomplete_brand_name, :on => :collection
end

最後にviewに
<%= form_for @product do |f| %>
  <%= f.autocomplete_field :brand_name, autocomplete_brand_name_products_path %>
<% end %>

ね、(そこそこ)簡単でしょう!

■いくつか残念な点

調べてみて感じた残念な点
  1. rails3.1対応はまだ
  2. rspec-request用のサポートが欲しい
  3. label/valueに適応されるmethodが同じ
  4. term以外の動的要素に対応できない
ぼやいててもしょうがないので、やれる事はやろう(続く)

2011/09/18 上記4に対応してみました。https://github.com/kennyj/rails3-jquery-autocomplete
gem 'rails3-jquery-autocomplete', :git => git://github.com/kennyj/rails3-jquery-autocomplete.git
で使えると思います。

2011/09/18 上記1にも対応してみました(rails3.1 branch) 上記に, :branch => 'rails3.1' を追加すると利用できるはずです。

2011/09/19 上記2はspec_helper.rbで、require 'steak/autocomplete' して、config.include Steak::Autocomplete, :type => :request すればrspec-requestsでも動きました。

2011年9月7日水曜日

rails3.1.0でasset pipe lineの問題発見(と解決方法)

■概要
rails3.1.0で、productionモードにてasset pipe lineを使うには、一般的には rake assets:precompile を利用すると思いますが(さもなくばエラーになります)、実行すると下記の用になります。
$ rake assets:precompile
$ ls -la public/assets/
合計 544
drwxrwxr-x 2 kj kj   4096  9月  6 01:33 .
drwxrwxr-x 3 kj kj   4096  9月  6 01:33 ..
-rw-rw-r-- 1 kj kj  96110  9月  6 01:27 application-2438fd50052a4a5b81204dc6fb.js
-rw-rw-r-- 1 kj kj  33515  9月  6 01:27 application-2438fd50052a4a5b81204dc6fb.js.gz
-rw-rw-r-- 1 kj kj      0  9月  6 01:27 application-4635849c44627859332fda6a01.css
-rw-rw-r-- 1 kj kj     20  9月  6 01:27 application-4635849c44627859332fda6a01.css.gz
-rw-rw-r-- 1 kj kj  91273  8月 15 22:49 jquery-ed8d29566738ad005e19fe1c2d.min.js
-rw-rw-r-- 1 kj kj  32054  8月 15 22:49 jquery-ed8d29566738ad005e19fe1c2d.min.js.gz
-rw-rw-r-- 1 kj kj 198431  8月 15 22:49 jquery-ui-2a28fc84ad0e0e47e46cbf901c.min.js
-rw-rw-r-- 1 kj kj  50610  8月 15 22:49 jquery-ui-2a28fc84ad0e0e47e46cbf901c.min.js.gz
-rw-rw-r-- 1 kj kj    318  9月  6 01:33 manifest.yml
-rw-rw-r-- 1 kj kj   6646  9月  6 01:26 rails-a560b5a3a7be0808c5cd76a798.png
上記にjqueryとjquery-uiが含まれている事が理解できません。application.jsの中にはjqueryが内包されています。またjquery-uiはSprocketsのディレクティブで指定もしていません。良くわからない挙動なので追跡してみました。

■原因
actionpack-3.1.0/lib/sprockets/assets.rake にdebuggerを仕込み動きを追いましたが、
 
 26         config.assets.precompile.each do |path|
 27           env.each_logical_path do |logical_path|
 28             if path.is_a?(Regexp)
 29               next unless path.match(logical_path)
 30             else
 31               next unless File.fnmatch(path.to_s, logical_path)
 32             end
 33
...
上記の28行目のpathは、config.assets.precompileの配列のメンバーですが、デフォルトでは
[ /\w+\.(?!js|css).+/, /application.(css|js)$/ ]
になっています(railties-3.1.0/lib/rails/application/configuration.rb 38行目)
上記配列の一番目は、rails.png等jsやcss以外の為に存在しますが、このままではjquery.min.jsやjquery-ui.minjsも29行目でヒットしてしまうじゃないか!!!という事に気付きました。

■解決方法
直してやれ!と思いましたが既に問題になってました。よって
  • 上記もうすぐ取り込まれそうなので3.1.1?を待つ
  • とりあえずconfig/environments/production.rb config.assets.precompile = [ /\.(?!js$|css$)\w+$/, /application.(css|js)$/ ] とする
辺りで対応しましょう。

■2011/09/11追記
解決されている様です。あるべき姿になった感じです! https://github.com/rails/rails/commit/082f53a3bca9dce70adcf41094e246d2c9fed934