yamotonalds's blog

Webアプリケーション開発における技術メモが中心です。たまにWebサービス、興味を持ったデバイス、自作PCに関する話題もあるかも。Amazon好きなのでAmazon.co.jpアソシエイト使ってます。

ActiveAdminを使用する場合はassetの汚染に注意

ActiveAdmin を使用すると簡単にデータの管理画面が作れますがデフォルトだとasset周りで問題が発生しやすいのでメモしておきます。

使用したバージョン

  • rails 4.0.2
  • activeadmin 1.0.0.pre

発生した問題

  • 管理画面以外の画面にもActiveAdminのcssが読み込まれ、ボタン等が管理画面と同じものになる
  • 管理画面以外の画面にもActiveAdminが保持するjQueryが読み込まれ、jQuery Pluginが動作しなくなる

どちらもActiveAdminを使用しないページにまでActiveAdminのassetが読み込まれてしまうのが原因です。

rails generate active_admin:install

を実行した際にapp/assets/javascripts/active_admin.jsapp/assets/stylesheets/active_admin.cssが作成されますが、これらがapplication.jsapplication.cssによって読み込まれてしまっています。

解決方法

管理画面でのみActiveAdminのassetが読み込まれるようにします。
以下のページを参考にしました。

Excluding active_admin JS and CSS from your Rails app - Dan Adams

まず、app/assets/javascripts/adminディレクトリを作成し、そこにactive_admin.jsを移動します。
次に、app/assets/javascripts/mainディレクトリを作成し、管理画面以外の画面のjsファイルを移動します。
そして、application.js//= require_tree .//= require_tree ./mainに変更します。

cssについても同様です。

それらが済んだらconfig/initializer/active_admin.rbに以下の内容を追加します。

config.clear_stylesheets!
config.register_stylesheet 'admin/active_admin.css'

config.clear_javascripts!
config.register_javascript 'admin/active_admin.js'

Herokuを使用している場合はさらにconfig/application.rbに以下の内容を追加します。

config.assets.precompile += %w[admin/active_admin.css admin/active_admin.js]

rails serverを再起動して意図した通りにassetが読み込まれていればOKです。

ActiveAdminに限らずjQueryをバンドルしているgemはたまにあるので同様の問題に注意する必要があります。
speed_gun gemはconfigでjQueryの読み込み設定が可能でした。

なお、解決方法を探してるとActiveAdminのassetをvendor/assetsに配置するという情報もありましたが手元で試した限りでは解決しませんでした。

GitHubの方に今回の問題に関するIssueがあったのでそのうち公式に解決されるかもしれません。

Chromeでダイアログが開いていると全画面表示の切り替えができない

Macを再起動したら再起動前にフルスクリーンで開いてたchromeのウィンドウがフルスクリーン解除できなくなった。

原因は開いてるタブの中にダイアログを開いているものがあったから。ダイアログを閉じるとフルスクリーンを切り替えられる状態に戻った。

ショートカットキーを押してもダメ、メニューバーやchromeのオプション内のボタンを押してもダメ。
メニューの表示全画面表示を終了も無効(グレーアウト)で困っていたけどBASIC認証のダイアログが原因だったとは。

rspec-rails3.0でのControllerテストの書き方

Controllerテストの書き方のメモ。

通常のページ

require 'spec_helper'

describe HogesController do
  describe "GET show" do
    let(:request) { get :show, id: 1 }

    subject do
      request
    end

    it { should be_ok }
    it { should render_template "show" }

    describe "@hoge" do
      subject do
        request
        assigns(:hoge)
      end

      it { should be_present }
    end
  end

  describe "DELETE destroy" do
    let(:request) { delete :destroy, id: 1 }

    before do
      @hoge = stub_model(Hoge, destroy: nil)
      allow(Hoge).to receive(:find).and_return(@hoge)
    end

    subject do
      request
    end

    it { should be_success }
    it { should redirect_to hoges_path)

    describe "@hoge" do
      subject do
        request
        assigns(:hoge) 
      end

      it { should be_present }
      it "#destory called once" do
        expect(@hoge).to receive(:destroy).once

        subject
      end
    end
  end
end

getpostResponseオブジェクトを返すのでそれをテストする。
ロジックの呼び出しに関してはモックでテスト。

ステータスコードは、

its(:status) { should eq 200 }
# または
its(:status) { should eq Rack::Utils.status_code(:ok) }

でも確認できるが可読性やタイプ数を考えるとbe_*で済むものは済ませたい。
使いそうなのは、

  • be_ok # 200
  • be_success # 200番台
  • be_successful # 200番台
  • be_redirect # 301, 302, 303, 307のどれか
  • be_forbidden # 403
  • be_not_found # 404
  • be_missing # 404

あたりか。

アサインした変数と意図したテンプレートが使われたかはチェックするがそのレンダリング内容についてはViewのテストで行うのでここではテストしない。

@hogeFactoryGirl.create(:hoge)でも良いのだがstub_modelの方がテストの実行時間が短くなるので積極的に利用する(そもそもコントローラのテストは本質的にはDBは不要)。

※2014-03-07 stubを使う部分のコードがmochaのものになっていたのでrspecのものに修正
mochaをインストールしていたせいかspec_helper.rbでmochaを使う設定にしていないのにstubsreturnsを使っていても表面上はエラーにならなかった。
しかし、specファイルを指定して実行してもエラーにならないがall testだとエラーになる現象が発生するようになった(100%ではなくall testが通る場合もある)。調べるとあるspecファイルで使ったstubsが他のspecファイルでも有効になっていた(そのためテストの実行順によってはall testが通った)。

Deviseを使う場合

spec/support/devise.rbを以下の内容で作成。

RSpec.configure do |config|
  config.include Devise::TestHelpers, type: :controller
end

これでsign_in, sign_outが使える。

また、DeviseControllerを継承したコントローラのテストではリクエスト前に

@request.env["devise.mapping"] = Devise.mappings[:user]

が必要になる。

Ajaxを使う場合

Viewのlink_to等でremote: trueとした場合、.jsがリクエストされるのでテストでは次のようにリクエストする。

delete :destroy, id: 1, format: :js

これで406 Not Acceptableになるようならアクションのrespond_toformat.jsが書かれていないと思われる。
また、401になる場合はログイン状態、403になる場合はCanCan等承認周りを確認する。

foremanでターミナルウィンドウを減らす

railsでWebアプリを作っていると実行させっぱなしにしておく必要があるコマンドがいくつかある。
例えば以下のようなもの。

作りがマズい(粗い)とポートを変えてWebサーバを複数立ち上げたりすることもある。
今まではその数だけターミナルを開いてそれぞれ実行していたが、パラメータを含めてコマンドを毎回打つのは面倒だし、ターミナルの切り替えも面倒だった。

foremanがそれを解決してくれた。

前提

  • rails serverが実行可能
  • bundle exec guardが実行可能
  • foremanがインストールされている

Procfileの作成

アプリのrootディレクトリに以下の内容のProcfileを作成する。

web: rails server
guard: bundle exec guard -i

foremanを実行

foremanを実行する。

bundle exec foreman start

2つのプロセスが実行状態になり、出力が色分けして表示される。

foremanを停止

foremanを実行中のターミナルでctrl-cを押すと実行していたすべてのプロセス停止する。

Herokuを使用する場合

Herokuもforemanを使用しているためProcfileを含めたソースをアップロードするとHerokuのforemanがそれを使用してしまう。

その場合はProcfileをProcfile.dev等にリネームし、

bundle exec foreman start -f Procfile.dev

のように開発環境ではファイルを指定すれば良い。

感想

まだ使い始めたばかりだけれど便利そう。

SSL証明書の更新

RapidSSLからSSL証明書期限切れ90日前の通知が来たのでギリギリになって焦らないように早めに更新作業を行うことにした。

Netowl上での作業

新規取得手順については以下の記事に書いたが、「新規」という文言が「更新」に変わり入力項目が減ったくらいで更新手順はほとんど同じだった。

CSRの作成とSSL証明書の取得 - yamotonalds's blog

なお、CSRは以前のファイルを使い回した。

すんなり終わるかと思っていたら問題が発生した。

更新のはずが新規扱いで処理された

証明書詳細画面を確認すると申請区分は更新になっているのに有効期限が今日から1年間になっていた。今日は有効期限90日前だったので90日分損していることになる。
そもそもこれでは更新ではなく新規と何も変わらない。

SSLボックスの問い合わせフォームから問い合わせると(問い合わせを受け付けた旨のメール等は無かったが)しばらくしてカスタマーサポートからメールが来た。一部抜粋すると、

発行元とこちらの時差が原因で「90日前」に達する前に
手続が行われてしまい、新規の取り扱いとなってしまったようです。

とのこと。
発行元がRapidSSLだとするとそこからのメールで今回の作業を始めたはずだがどういうことだろう。

詳しい原因はさておき、新規扱いとなった更新をキャンセルして更新申請をし直すことになった。
念のためキャンセルの流れをメモしておく(可能性は低いが誰かの参考になれば良い)。

まず、キャンセル申請はネットオウル側行ったらしく、数日後に

RapidSSL Request for Order Cancellation

というメールが届いた。I confirm the cancellationと返信しろと書いてあったのでそれだけ書いて返信した。
ちなみに、

Order Information Request for <ドメイン名>

というメールもほぼ同時に届き、こちらはWebページ上でキャンセル確認ができそうだったがサポートセンターの指示通り前述のメールにだけ返信し、こちらのメールは無視した。

翌日、RapidSSLから以下のメールが届いた。

AUTO-REPLY: Re: RapidSSL Request for Order Cancellation (#<番号>)

キャンセルを受け付けた旨と作業に2営業日程度かかる旨が書かれていた。

3日後、RapidSSLから以下のメールが届いた。

RE: Re: RapidSSL Request for Order Cancellation (#<番号>)

キャンセルが完了した旨が書かれていたのでサポートセンターに転送。サポートセンターからキャンセル作業を進める旨のメールが届く。
そして数時間後、キャンセル完了のメールがサポートセンターから届いたので最初にやったように管理画面から更新申請し、今度はちゃんと更新された有効期限になっていることを確認した。

AWS上での作業

話は戻って更新作業の続き。
以前設定した時の手順をブログに書いてるつもりだったが書いてなかったようなので改めて書く。
まったくの新規からの作業ではないので抜けがあるかもしれないがあしからず。

前提
  • AWS Elastic Beanstalkを使用している
  • AWS EC2 Elastic Load Balancers(elb)を使用している
  • ブラウザだけで操作する
手順
  1. ブラウザでAWS ConsoleにログインしてEC2の管理画面へ移動
  2. サイドバーのNETWORK & SECURITYからLoad Balancersをクリック
  3. 表示されたelbの一覧からSSL証明書を適用したいものを選択し、下の詳細パネルのListenersタブをクリック
  4. 自分の場合、httpとhttpsを両方使うので、elbの80番ポートからinstanceの80番ポートへのSSL無しの行と、elbの443番ポートからinstanceの80番ポートへのSSL有りの2行が表示されている。
  5. SSL有りの行のSSL Certificate列のChangeをクリックする。
  6. SSL証明書選択画面がポップアップするのでUpload a new SSL Certificateを選択。
  7. Certificate Nameに今から登録する内容に適切な名前を入力する。対象ドメイン名と有効期限が分かるものが良いと思う。(例: example_com__2014-04-01_2015-03-31)
  8. Private Keyにkeyファイルの中身(-----BEGIN RSA PRIVATE KEY-----から-----END RSA PRIVATE KEY-----まで)を入力。今回は以前のCSRを使い回しているのでkeyファイルも前と同じもの。
  9. Public Key Certificateに証明書詳細画面のCERT(SSL証明書)からダウンロードしたファイルの中身(-----BEGIN CERTIFICATE-----から-----END CERTIFICATE-----まで)を入力。
  10. Certificate Chainには証明書詳細画面の中間証明書の内容を入力しても良いが、Android 2.2より古いスマートフォン等の新しい証明書に対応していないものに対応するなら、 RapidSSL - Knowledge Center - SSL Certificates SupportDownload the RapidSSL CA Bundleからダウンロードしたファイルの中身(-----BEGIN CERTIFICATE-----から-----END CERTIFICATE-----までが2つ)を入力する。
  11. SaveボタンをクリックするとChoose from your existing SSL Certificatesが選択され、先ほど作成した名前がプルダウンで選ばれた状態になるがここから先はBeanstalkの設定で行うためCancelをクリックする。
  12. Beanstalkの管理画面に移動し、SSLに対応したい環境名をクリック、サイドバーのConfigurationをクリックする。
  13. 表示された画面の下部、Network TierLoad Balancingの歯車をクリックする。
  14. Load BalancerSSL certificate IDプルダウンから先ほど作成したものの名前を選択し画面右下のSaveをクリックする。
  15. 環境の更新が始まるので正常に終わったらサイトにアクセスしてSSL証明書が更新されていることを確認する(更新されていなければスーパーリロードも試す)。

EC2の画面でSSL証明書のアップロードをしておいて適用はBeanstalkの画面から行うというのが少しわかりにくいかもしれない。
アップロードされたSSL証明書はIAMに保存されるようなのでIAMの画面で操作できれば良いのだが見つからなかった。
今後IAMの画面で操作できるようになるかもしれない。

rspec-rails3.0でのViewテストの書き方

rspec-rails3.0でのViewテストの書き方のメモ。

まずは公式に目を通しとくべき。

Upgrade - RSpec Rails - RSpec - Relish

view spec - View specs - RSpec Rails - RSpec - Relish

重なる内容もあるけど個人的に気になった点を以下に記述する。

Capybaraのmatchersを使う

have_tagは無くなったようなのでCapybaraをインストールしてそのmatchersを使う。

Module: Capybara::Node::Matchers — Documentation for jnicklas/capybara (master)

spec_helper.rbに以下の行を追加。

require 'capybara/rspec'

テストコードは以下のような感じになる。

describe "layouts/_navbar.html.haml" do
  subject do
    render partial: "layouts/navbar"
    rendered
  end

  describe "session info" do
    context "user not signed in" do
      it { should have_text I18n.t('layouts.navbar.sign_in') }
    end
  end
end
have_cssではオプションのキーを指定する

have_css(have_xpath, have_selector)のオプションには以下のようにtext:等のキーを付ける。

it { should have_css(".username", text: "アリス さん") }

付けなくてもテストは動くが、表示されるメッセージに違いがある。

# text: を付けた場合
should have css ".username" with text "アリス さん"

# text: を付けずに文字列のみを渡した場合
should have css ".username"
Library not loaded

発生条件まで追っていないが、自分の環境ではCapybaraをインストールするとrails起動時に以下のエラーが発生するようになった。

/Users/yamotonalds/Work/project_dir/vendor/bundle/ruby/1.9.1/gems/activesupport-3.2.13/lib/active_support/dependencies.rb:251:in `require': dlopen(/Users/yamotonalds/Work/project_dir/vendor/bundle/ruby/1.9.1/gems/nokogiri-1.6.0/lib/nokogiri/nokogiri.bundle, 9): Library not loaded: /Users/yamotonalds/.rvm/rubies/ruby-1.9.3-p327/lib/libruby.1.9.1.dylib
  Referenced from: /Users/yamotonalds/Work/project_dir/vendor/bundle/ruby/1.9.1/gems/nokogiri-1.6.0/lib/nokogiri/nokogiri.bundle
  Reason: image not found - /Users/yamotonalds/Work/project_dir/vendor/bundle/ruby/1.9.1/gems/nokogiri-1.6.0/lib/nokogiri/nokogiri.bundle (LoadError)

nokogiriを1.6.0から1.6.1にバージョンアップするとエラーは発生しなくなった。

bundle update nokogiri

Deviseを使っている場合

ReadmeにRSpecについての記述がある。

plataformatec/devise · GitHub

spec/support/devise.rbを作成して以下を記述する。

RSpec.configure do |config|
  config.include Devise::TestHelpers, type: :controller
  config.include Devise::TestHelpers, type: :view
end

これで以下のようにsign_in等が使える。

before do
  sign_in FactoryGirl.create(:user)
end

view.stub(:current_user, stub_model(User))とする方法もあるようだが、user_signed_in?等の他のメソッドもあるためUserクラスが使えるなら上記の方法で良いと思う。

helperのstub

before do
  view.stub(:admin?).and_return(true)
end

stub_modelとmock_modelの使い分け

大体以下のような感じだと思う。

stub_model

呼び出すごとに固有のid(正確にはto_param)を持ったインスタンスを作成する。指定するクラスは実在しなければならない。

つまり、実際にDBにデータを保存せずに永続化されたオブジェクトのdoubleを作るメソッド。

stub_model(Widget).as_new_record

とすることで永続化していないオブジェクトのdoubleも作れる。

mock_model

基本的にはstub_modelと同じで、ActiveModelのメソッドがstub化されたdoubleを作るメソッド。指定するクラスはActiveModel::Namingを継承している必要がある。

stub_modelとの大きな違いは実在しないクラスでも文字列で渡せばOKなところ。自動的にActiveModel::Namingを継承したクラスを作ってくれる。
分業しててViewのテストを作る時にまだ必要なクラスが作成されていない場合等に使える。

注意: Ruby 1.9.3では"Hoge::Foo"のようにモジュール内のクラスを指定するとNameErrorが発生する。Ruby 2.1.0では修正されていた。