Capybaraのウェイト時間で足りない場合、さらに待たせる。
Capybaraはfind
などの要素検索メソッドで、見つかるまである程度の時間待ってくれます。
時間が足りない場合は
Capybara.default_max_wait_time = 30
などとすることで時間を増やせます。
all('hoge')[0].click
だとおなじみのundefined method `click' for nil:NilClass
で終わる
終わりますので、終わらせないために適当にrescue
します。
def re_find(wait = 0.5, count = 20, &block) yield rescue raise if count == 0 sleep wait re_find(wait, count - 1, &block) end
re_find { page.all('a.niceButton')[0].click }
これで中断もできますし、急場はしのげますね。
Access-Control-Allow-Originが設定されてないWeb APIを叩くために中継サーバー書いた。
大げさなタイトルみがありますがただのRailsです。
うすうす実装なのでさがせばあるんでしょうけど、人が書いたもの使いたくない場合ってあるじゃないですか。
起動
$ TARGET=http://you-want-to-ajax.server.com ALLOW=http://your-local-js.server.com rails s
つかう
あとはhttp://your-local-js.server.com
でうごくJavaScriptからhttp://localhost:3000
のRailsを叩くだけ。
Ajax側の実装でエンドポイントの根本ドメイン変えられるようにしておけば安心ですね。
わからんかった
method == 'OPTIONS'
のときはController
でsession.id
が取れないので、みんなたちで使えるような仕様にはできなかったので、ローカルだけで使えるものになった。
なんでかな。
vue-routerのroutesを全部出すやつ
だいたい見れる。
log(dig(router))
function digState (state, path = []) { state.nextStates.forEach((nextState) => { if (nextState.handlers) { nextState.handlers.forEach((handler) => path.push(handler)) } if (nextState.charSpec.validChars) { digState(nextState, path) } }) return path } function digNames (names, path = []) { for (let name in names) { router._recognizer.names[name].handlers.forEach((handler) => path.push(handler)) } } function dig (router, path = []) { digState(router._recognizer.rootState, path) digNames(router._recognizer.names, path) return path } function log (path) { let logged = {} let logs = [] path.forEach((hh) => { let h = hh.handler if (logged[h.fullPath]) { return } if (h.name) { logs.push(['/' + h.name, '=>', h.fullPath].join(' ')) } else { logs.push(h.fullPath) } logged[h.fullPath] = true }) logs.sort().forEach((l) => console.log(l)) }
ActiveAdmin辺りでCircular dependency detected while autoloading constant Fooが出る場合の措置。
Rails 4.2.7、ActiveAdmin 1.0.0.pre4で発生しました。
普通にやってる分には出ないんですけど、下の記事みたいに外からRails.application.require_environment!
すると、models関連でCircular dependency detected while autoloading constant Foo
が出ます。
mmmpa.mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmpa.net
措置
読み込み順序が問題なので、autoload_paths
の先頭にmodels
のディレクトリを追加してから、require_environment!
します。
Rails.application.config.autoload_paths.unshift("#{Rails.root}/app/models") Rails.application.require_environment!
ベーシック認証が必要なサイトにアクセスするテストで、ダイアログをシカトするためにNginxでProxyする。
Phantomjsじゃ動いてくれないJavaScriptライブラリがありまして(よくある)、Selenium + Firefoxでスクリーンショットおじさんになっています。
nginx.conf
実際はRubyではないのでこのようなコードではないんですけど、概念的にこんな感じです。
server { listen 8080; server_name localhost; location / { proxy_set_header Authorization "Basic #{Base64.encode64 'id:password'}"; proxy_pass https://my-secret-web-site.com; } }
Base64エンコードしたのをヘッダに含めます。
一般的にはブラウザでのアクセスの際や、エンドポイントの設定ファイルでhttps://id:password@my-secret-web-site.com
でやったりするんでしょうけど、ライブラリがパースをしくじったり、なんかブラウザに丸出しになるのがイヤって時に使えます。
Rails開発でリポジトリには入れたくないんだけどローカルではやっておきたいテストがある場合の取りあつかい
自明だったり、すごく重かったり、細かすぎるテストたちがいます。
重いテストは短期的にも長期的にもコストになり、細かすぎるテストは実装変更時のコストが必要以上に高く、よくありません。
しかし、たまにやっときたいんですよというテストがありますので、本体に影響がないようにやっていきます。
Railsのディレクトリの外にもう一層つくる
以下のような構成になりました。
- /my_test - /.git - /rails_application # 本体 - /seed # テスト用の - /spec # 自分専用テスト - /models - rails_helper.rb # 自分専用のテストのrails_helper.rb - .gitignore # 本体を含めないため - Gemfile # 自分専用テスト用のGemfile
自分だけが使うGemを用意する
テストで使いたいが、該当アプリのGemfileに入っていないので使えないGemというのがありますので、どうにかします。(今回のはテストで使う!というGemではないですが)
むりくりrequire
しても、コンテキストが変わってしまい、ダメなようですので、本体のGemfileの内容をinstance_eval
します。
instance_eval(File.read(File.expand_path('../rails_application/Gemfile', __FILE__))) # 好きなGemを入れる gem 'seed-fu'
seed-fu
ところで、ダミーデータもまた、大量だったり詳細だったりすると、コストになります。
しかしさまざまなレコード状態表示の調整をする場合など、ある局面では役に立つので、テスト以外でも使えるように用意しておきます。
#!/usr/bin/env ruby require File.expand_path('../../rails_application/config/application', __FILE__) abort("The Rails environment is running in production mode!") if Rails.env.production? Rails.application.require_environment! require 'seed-fu' 10000.times do |n| ModelA.seed( id: n, name: SecureRandom.hex(4) ) end
$ bundle exec ./seed/seed.rb
これで外側から、自分のデベロップメントや、テストに、大量のダミーデータを投入できるようになりました。
テスト
rails_helper.rb
をコピペして編集する
environment
とspec_helper
に対するパス部分を本体のファイルに当たるように変更します。
require File.expand_path('../../rails_application/config/environment', __FILE__) require File.expand_path('../../rails_application/spec/spec_helper', __FILE__)
さきのseed-fu
を利用する
seed-fu
はconfig.before :all
内などに書いておけば安泰ではないでしょうか。DatabaseCleaner
などを使っている場合は、それが動いた後である必要があります。
require File.expand_path('../seed/seed', __FILE__)
実行
require 'rails_helper' RSpec.describe ModelA, type: :model do it { expect(ModelA.create!).to be_a(ModelA) } it { expect(ModelA.count).to eq(10000) } end
$ rspec -cfd # 略 should be a kind of ModelA(id: integer, name: string, created_at: datetime, updated_at: datetime) should eq 10000 Finished in 1 minute 41.39 seconds (files took 3.8 seconds to load) 2 examples, 0 failures
できました。
これはジョークのようなテストですが、たとえばCapybara
でスクリーンショットを撮りまくるといった、半分趣味のようなテストも、本体に影響なくどんどんやっていくことができます。
Authlogicに関するメモ
認証機能再発明するべからずは有名な鉄則ですが、まるで知らないままというのもマズイので実装を読むことでお茶を濁していきたい。
salt
やSCrypt
の運用、persistence_token
の更新などで比較的安全にいけるのでは。
パスワード
salt
Authlogic::Random.friendly_token
def friendly_token # use base64url as defined by RFC4648 SecureRandom.base64(15).tr('+/=', '').strip.delete("\n") end
暗号化
authlogic/lib/authlogic/crypto_providers
以下に、各種Cryptorをラップしたアダプターがある。
SCrypt
ならAuthlogic::CryptoProviders::SCrypt#encrypt(*tokens)
で暗号化パスワードを得られる。
*tokens
には[password, salt]
が入り、salt
は保存されてログイン時のパスワード一致を見る時に使われる。
生パスワードは勿論破棄される。
パスワード確認
各種Cryptorの比較演算子を使ったりする。
# ユーザーのレコードから得る salt = user.password_salt encrypted = user.encryped_password # 入力 password = 'aaaaaaaa' # 比較 SCrypt::Password.new(encrypted) == Authlogic::CryptoProviders::SCrypt.encrypt(password, salt)
セッション
cookie
などの値を得るために、Controller
インスタンスを持つアダプターが必要になる。
Controller
経由では、初期化時に自動的に付与されるAuthlogic::ControllerAdapters::RailsAdapter
が設定する。
Authlogic::Session::Base.controller = RailsAdapter.new(self)
それ以外で使いたい場合は、必要な値を返すなにかを用意する。
class ControllerLike def initialize(request) @request = request end def cookies @request.cookies end def params @request.params end def session @request.session end def responds_to_last_request_update_allowed? true end def last_request_update_allowed? false end def method_missing(*args) false end end Authlogic::Session::Base.controller = ControllerLike.new(request)
開始
create
時にユーザーレコードにpersistence_token
を保存する。
クッキーにはそのpersistence_token
とユーザーのid
を、後にsplit
可能な形で保存する。[persistence_token, id].join('::')
など。
復元
保存しておいたクッキーの値のid
からユーザーを検索、persistence_token
と一致するか見る。