ンンンパ

ふとしです

移転しました

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です。

github.com

うすうす実装なのでさがせばあるんでしょうけど、人が書いたもの使いたくない場合ってあるじゃないですか。

起動

$ 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'のときはControllersession.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をコピペして編集する

environmentspec_helperに対するパス部分を本体のファイルに当たるように変更します。

require File.expand_path('../../rails_application/config/environment', __FILE__)
require File.expand_path('../../rails_application/spec/spec_helper', __FILE__)

さきのseed-fuを利用する

seed-fuconfig.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に関するメモ

認証機能再発明するべからずは有名な鉄則ですが、まるで知らないままというのもマズイので実装を読むことでお茶を濁していきたい。

saltSCryptの運用、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と一致するか見る。