Hatena::ブログ(Diary)

HWPS別館 このページをアンテナに追加 RSSフィード Twitter

2012-04-10

[]responders と RSpec

responders で書き換えられた scaffold で RSpec が通らなくなる件。

Post の内容がエラーかどうかを判定するのに、 scaffold では AR::Base#save の戻り値で判定しているため、Spec は次のようになっている。(ここでは ItemsController の Spec と仮定する)

      it "re-renders the 'new' template" do
         # Trigger the behavior that occurs when invalid params are submitted
        Item.any_instance.stub(:save).and_return(false)
        post :create, {:item => {}}, valid_session
         response.should render_template("new")
       end

一方で responders で生成されるコントローラの判定は respond_with(@resource) で行われるため、AR::Base#errors の内容で判定される。

そのため save を stub してしまうと、 errors が生成されないためエラーと判断されない。

save を stub しないようにして、 invalid なパラメータを与え save に失敗し errors を生成するか、あるいは errors を stub してやればよい。

        Item.any_instance.stub(:save).and_return(false)
        Item.any_instance.stub(:errors).and_return({:name => 'invalid'})
        post :create, {:item => {}}, valid_session

2012-05-22追記: ライブラリ名をずっと間違えて書いていたのでしれっと直した。

2012-01-05

明けましておめでとうございます

この日記は今年も Rails 中心に淡々と気づいたメモを残していきたいと思います。だいぶ不定期ですがよろしくお願いいたします。

[][] Rack::GoogleAnalytics

leehambley/rack-google-analytics

この Rack Middleware は、 production 環境のすべての HTML を返す URL に Google Analytics を組み込みたいというニーズがあったときに便利。以下サンプル。

Gemfile:

gem 'rack-google-analytics', :require => 'rack/google-analytics'

config/environments/production.rb:

config.middleware.use Rack::GoogleAnalytics, :tracker => 'UA-xxxxxx-x'

すべての layout ファイルに入れて回ったりしないので楽だし DRY。config.middleware.use を config/environments/production.rb に書くのがポイント。development.rb や application.rb に書けば、当然その環境でも有効になる。

この middleware は Google Analytics をすべてのレイアウトで間違いなく挿入する一つの方法になるのではと。(他の手段としては、個人的には、 application.js で require されるように asset pipeline 経由で書いてしまうのもアリだと思う)

ただし

Rack Middleware のレイヤーで HTML を操作することの是非は検討すべきかなと思うし、許容できる場合のみに使用すべきかと思う。

備考

類似品として grays/rack-google_analytics というのもある。(async で打てないので実用はむずかしいか?)

2011-11-15

[][] Rack::Access で IP アドレス制限

Rails アプリケーションで IP アドレス制限が必要になった際には、基本的に上位のリバースプロキシ(Apache とか Nginx とか)で行うことが多いと思いますが、一方で Rails 本体で制限をかけなければならないこともあります(Heroku だとか)

でもそれを Rails の controller 層とかで実装するのはちょっと嫌ですよね。特にビジネスロジックではないことが多いですし。

というわけで、IP アドレス制限を Rack Middleware 層で実装した Rack::Access というモジュールを使ってみました。これは rack/rack-contrib ? GitHub に含まれています。使い方は Gemfile に書いて bundle して config/application.rb に設定を書いて完了です。

Gemfile:

gem 'rack-contrib', :require => 'rack/contrib'

config/application.rb

config.middleware.use "Rack::Access", '/' => [ '127.0.0.1',  '192.168.1.0/24' ]

これで 127.0.0.1 あるいは 192.168.1.0/24 以外からのアクセスには Forbidden を返すようになります。

当然、実行環境によって異なる制限をかけることになると思います。それぞれ environments/ 以下に書いてもいいと思うのですが、面倒なので自分は YAML に書きました。

config/application.rb

config.middleware.use "Rack::Access", YAML.load(open(Rails.root + "config/access.yml", &:read))[Rails.env]

config/access.yml

localnet: &localnet
  /:
    - 127.0.0.1
    - 192.168.1.0/24

development:
  <<: *localnet

test:
  <<: *localnet

production
  /:
    - xxx.xxx.xxx.xxx # 公開対象アドレス

動的に制限を変更するという使い方をしたければもう少し複雑にしなければいけませんが(一応できるはず)、イントラ向けアプリなどではこの程度で十分ではないでしょうか。

もちろん Rack middleware なので、 Rails 以外のアプリケーションでも同様に使えるのがいいですね。

2011-11-07

[] shoulda-mathcers での validate_uniquness_of の注意点

class User < ActiveRecord::Base
  validate :name, :unique => true 
end

ActiveRecord の↑のようなモデルの spec として、shoulda-matchers の validate_uniquness_of を使うことがある。

このときこんな風に書くと、場合によって通ったり通らなかったりするので注意が必要。

describe User do
   it { should validate_uniqueness_of(:name) }
end

validate_uniquness_of のドキュメントには以下のようにある。

      # Internally, this uses values from existing records to test validations,
      # so this will always fail if you have not saved at least one record for
      # the model being tested, like so:
      #
      #   describe User do
      #     before(:each) { User.create!(:email => 'address@example.com') }
      #     it { should validate_uniqueness_of(:email) }
      #   end
https://github.com/thoughtbot/shoulda-matchers/blob/master/lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb

てなわけで、( factory girl と組み合わせて ) こんな感じでないと安定しなかった。


describe User do
  before { create :user }  
  it { should validate_uniqueness_of(:name) }
end

元のでも他の spec の都合でレコードが残ってたりする場合に通ったりするので、「手元で通るけど jenkins だと通らないよ!」というパターンになるので要注意。

2011-10-11

[] FactoryGirl の新しいインタフェースに移行したときのメモ

えいっと新しい FactoryGirl に移行して、新しいインタフェースに対応してみた。どう移行したかは GETTEING_STARTED.md を読んでその通りに定義部分と利用部分を修正した。詳細は省く。

……が、こんな感じのエラーに悩む。

....../ruby-1.9.2-p290/gems/factory_girl-2.1.2/lib/factory_girl/registry.rb:38:in `add_as': Already defined: user (FactoryGirl::DuplicateDefinitionError)

多重定義と言っているが、特に複数個定義していない。

  • 古いインタフェースが混じってたのでそれを統一したり
  • factory の継承をやめてみたり
  • alias をやめてみたり

してみたがダメで仕方なくソースまで潜る。DIVE TO THE NIGHT.

結論としては、 spec_helper.rb で spec/factories を require していた行があったので、削除して解決した。

require Rails.root.join("spec/factories.rb")

FactoryGirl は factories ファイルを load するので注意なのだな。

2011-07-12

[] packed_field gem

RailsActiveRecord::Base.serialize を(仕方なく)使う機会があったが、使い方が気に食わなかったんでさくっと Wrapper を書いたりした。

名前をやっつけすぎたと思った。あとさりげなく jeweler 初めて使った。

あたりを参考にさせていただきました。

2011-06-09

[][] Capistrano で GitHub の CA Cert 関連のエラーが出てしまうとき

対象: capistano で Rails をデプロイして bundle install の際に GitHub で証明書のエラーになっちゃう人

require 'bundle/capistrano'
*** [err :: user@xxx.xxx.xxx.xxx] error: SSL certificate problem, verify that the CA cert is OK. Details:*** [err :: user@xxx.xxx.xxx.xxx] error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed while accessing https://github.com/holysugar/sandbox.git/info/refs
*** [err :: user@xxx.xxx.xxx.xxx]
*** [err :: user@xxx.xxx.xxx.xxx] fatal: HTTP request failed
 ** [out :: user@xxx.xxx.xxx.xxx] Git error: command `git clone 'https://github.com/holysugar/sandbox.git' "/u/app/shared/bundle/ruby/1.9.1/cache/bundler/git/sandbox-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" --bare --no-hardlinks` in directory /home/user has failed.

選択肢は

  • 証明書の検証を無視する
  • 証明書を追加する

前者はいろいろなことを放棄していると思うので後者を選びたい。

一般的には http://sakuratan.biz/archives/2812 などに解決策がある。だけど、全部のサーバーで(例えば) /etc/pki/tls/cert.pem 書き換えたりするのは面倒。

結局のところ bundle install のタイミングで証明書が使えればいいので、追加したい証明書をレポジトリのどこか(ここでは config/etc/pki/ourcert.pem とする)に入れた上で、さらに deploy.rb に以下の設定を行う。

set (:bundle_cmd) { "GIT_SSL_CAINFO=#{release_path}/config/etc/pki/ourcert.pem bundle" }    

これで bundle コマンド中の git コマンドでは自動的に参照されるようにして、まとめて解決できた。:)

証明書は Firefoxhttps://github.com/ にアクセスして 「アドレスバー左の鍵クリック - 証明書を表示 - 詳細 - エクスポート - 証明書パスを含む X.509 証明書(PEM)」 と選択していけば保存できる。

2011-05-13

[] 今更ながらシリーズ(2) StringInquirer

Rails.env == 'production'

という式は、

Rails.env.production?

と書ける。これは Rails.env が単なる文字列ではなく、ActiveSupport::StringInquirer という String を継承したクラスに変換されているから。

ActiveSupport.StringInquirer.new("foo").foo? #=> true

ちなみに、Rails 3.1 では、String#inquiry で String から StringInquirer を生成できる。

Rails.version      # => "3.1.0.beta1" 
s = "hoge".inquiry # => "hoge" 
s.class            # => ActiveSupport::StringInquirer
s.hoge?            # => true 
s.fuga?            # => false 

2011-05-12

[] 今更ながら CaptureHelper

本当に今更だけど CaptureHelper なる存在を知った。

maiha 氏が説明してる程度に昔からある。なんてこった。

view:

<% div_wrapper do %>
<p>ほげほげ</p>
<% end %>

helper:

  def div_wrapper(&block)
    render :inline => "<div>#{capture(&block)}</div>"
  end

結果

<div><p>ほげほげ</p></div>

2011-04-21

[] Rails3 で routes.rb を分割・追加する

故あって routes.rb を routes/frontend.rb と routes/backend.rb に分割したいとする。

とりあえず、 routes.rb の中で require なり load なりしてもよいが、これだと開発中に routes/frontend.rb を編集しても、再起動しない限り反映されない。

SampleApp::Application.routes.draw do
 # :
 # :
end

require_relative 'routes/frontend.rb'
require_relative 'routes/backend.rb'

そうしないためには、該当のファイルを routes ファイルパス設定に追加する。

config/application.rb の中で以下のように記述する。

module SampleApp
  class Application < Rails::Application
    # :
    # : その他の設定
    # :
    
    config.paths.config.routes.concat Dir[Rails.root.join("config/routes/*.rb")]
  end
end

あるいは追加したいパスが一つならば、 config.paths.config.routes << Rails.root.join("config/routes/aroute.rb") としてもよいようだ。

(config.paths.config.routes は Rails::Paths::Path なのでそのメソッドを参照。)

そして各ファイルに普通に routes.rb のようにルーティングを記述すればよい。

読み込まれる順番に注意。追加する際と 逆順で読み込まれる 。すなわち、先の例であれば以下のような順で load されることになる。

  1. config/routes/frontend.rb
  2. config/routes/backend.rb
  3. config/routes.rb

2011-04-12

[] Pound で Proxy させるとデフォルトではバックエンドよりタイムアウトの設定が短い件

[UserAgent] - Pound - Varnish - AppServer

例えば↑のような HTTP Proxy が連なった構成のとき、AppServer がうっかり固まってると(ここでは Read timeout を想定)、プロキシがそれぞれタイムアウトするわけだけど、 Pound はデフォルト設定では 15 秒後に 500 (!!) レスポンスを返す。このデフォルト値は Varnish (60s) や Nginx (60s), Apache+mod_proxy (300s) のデフォルト値より短く、またそれらが返すステータスコード 503 と異なる。

返すステータスコードはパッチをあてない限り変更できないので、通常はタイムアウト値をそれらの設定より伸ばすしかない。

TimeOut 305

こういうこともあるので、HTTP Proxy のタイムアウト値の設定については、デフォルト値を使わずにちゃんと要件に合わせて設定を書く必要がある。(もちろん、タイムアウトにならないよう AppServer を十分速くするのは前提。)

2011-02-22

[] escape_utils

escape_utils を以前紹介したけど、その時のバージョンはバグがあったので、0.2.1 以上にした方がよいです。特に日本語のリクエストがあった場合に、情報の欠落が発生する可能性があります。

gem "escape_utils", ">= 0.2.1"

あとは実は厳密には Rack の unescape_url とはエンコーディング関係で挙動違うんだけど、今のところそれは問題になっていないはず。

2011-01-19

[] Model.scoped

ActiveRecord で Model の空のリレーションを作成するには、scoped メソッドを使うらしい。(WEB-DB PRESSに載ってたらしいけど引っ張り出せない。)

https://github.com/rails/rails/blob/master/activerecord/lib/active_record/named_scope.rb

ri scoped する。

Returns an anonymous scope.

  posts = Post.scoped
  posts.size # Fires "select count(*) from  posts" and returns the count
  posts.each {|p| puts p.name } # Fires "select * from posts" and loads post objects

  fruits = Fruit.scoped
  fruits = fruits.where(:colour => 'red') if options[:red_only]
  fruits = fruits.limit(10) if limited?

Anonymous scopes tend to be useful when procedurally generating complex
queries, where passing intermediate values (scopes) around as first-class
objects is convenient.

こんな感じか。

# model
class Item < ActiveRecord::Base
  # ..
  def self.rel(condition = nil)
    if condition.present?
      where(condition)
    else
      scoped
    end
  end
end
# controller
  cond = { :cond => :somthing }
  @items = Item.rel(cond).paginate(:page => 1) #...