ウェブサービスを作っています。

RSpec で sleep を stub する

上記の方法でうまくいかない、ライブラリ内部などで sleep されている場合を考えてみました。

spec/support/stub_sleep.rb

module Kernel
  def sleep_with_stub(*args)
    # do nothing
  end
end

RSpec.shared_context 'Stubbing sleep', stub_sleep: true do
  before { Kernel.send :alias_method_chain, :sleep, :stub }
  after { Kernel.send :alias_method, :sleep, :sleep_without_stub }
end

実行時には、stub_sleep タグをつけます。

describe Foo, :stub_sleep do
  ...
end

Sunspot の search ブロックをメソッドに分割する

Sunspot で複雑な検索を行う場合、検索ブロックが長くなることがあります。

Post.search(include: [:author, :comments]) do
  # complex1
  if complex_conditions1
    with :blog_id, 1
  else
    with(:published_at).less_than Time.now
  end

  # complex2
  if complex_conditions2
    with :author_id, 1
  else
    with(:published_at).less_than_or_equal_to Time.now
  end
end

search メソッドに、ブロック引数が渡されることを利用すると、以下のように書き直すことができます。

Post.search(include: [:author, :comments]) do |query|
  complex1 query
  complex2 query
end

def complex1(query)
  if complex_conditions1
    query.with :blog_id, 1
  else
    query.with(:published_at).less_than Time.now
  end
end

def complex2(query)
  if complex_conditions2
    query.with :author_id, 1
  else
    query.with(:published_at).less_than_or_equal_to Time.now
  end
end

Trigram という gem を作りました

2 つの文字列の類似度を計算する Trigram という gem を作りました。

https://github.com/milk1000cc/trigram

Trigram.compare 'he is genius', 'he is genius'  # => 1
Trigram.compare 'he is genius', 'he is very genius'  # => 0.5625
Trigram.compare 'he is genius', 'she is cute'  # => 0.26666666666666666
Trigram.compare 'he is genius', 'I can fly'  # => 0

文字列を 3 文字ずつに分割して、重複率を出す感じです。

以下の記事を読んでもらうと、よくわかると思います。

livedoor Techブログ : String::Trigram でテキストの類似度を測る

最近は PONPON の作り直しをしていて、似ているクーポンをまとめる処理などで使っています。

acts_as_digested_on という gem も更新しているので、よかったらご利用ください。

RSpec + VCR で HTTP テストデータを簡単に作成する

HTTP リクエストをスタブ化する WebMock というライブラリがあります。

これは便利なのですが、リクエストの数が増えてくると、テストデータの管理が大変な場合があります。

VCR を使うと、自動でテストデータを作成してくれて便利です。

参考: VCRを使うとRSpecのwebmockの作成が超絶楽になった! - 酒と泪とRubyとRailsと

ここでは、参考記事に載っていない、RSpecメタデータで記述する方法を紹介します。

Gemfile

group :test do
  gem 'webmock'
  gem 'vcr'
end

spec/spec_helper.rb

RSpec.configure do |config|
  ...
end

VCR.configure do |c|
  c.cassette_library_dir = 'spec/cassettes'
  c.hook_into :webmock
  c.configure_rspec_metadata!
end

spec/foo_api_spec.rb

describe 'foo api', vcr: { cassette_name: 'foo_api' } do
  it 'gets a respose from foo api' do
    response = call_foo_api(foo_api_url)
    expect(response.first).to eq 'hello world'
  end
end

これで、1 回目のみ実際のアクセスが行われ、spec/cassettes/foo_api.yml にレスポンスが保存されます。

2 回目からは spec/cassettes/foo_api.yml のデータが使用され、実際のアクセスは行われません。

参考: Usage with RSpec metadata - Test frameworks - Vcr - VCR - Relish

CarrierWave で Amazon S3 にアップロードする

という前提です。 CarrierWave のバージョンは 0.10.0 です。

Gemfile

gem 'carrierwave'
gem 'fog'
gem 'dotenv-rails'

config/initializers/carrierwave.rb

CarrierWave.configure do |config|
  if Rails.env.production?
    config.fog_credentials = {
      provider: 'AWS',
      aws_access_key_id: ENV['S3_ACCESS_KEY_ID'],
      aws_secret_access_key: ENV['S3_SECRET_ACCESS_KEY'],
      region: ENV['S3_REGION'],
      path_style: true,
    }
    config.fog_directory = ENV['S3_BUCKET_NAME']
    config.storage :fog
    config.asset_host = ENV['S3_ASSET_HOST']
  else
    config.storage :file
  end
end

.env

S3_ACCESS_KEY_ID=XXX
S3_SECRET_ACCESS_KEY=XXX
S3_REGION=ap-northeast-1
S3_BUCKET_NAME=img.example.com
S3_ASSET_HOST=http://img.example.com

登録後からログインしたままにする Devise の設定

Devise (v3.2.2) で会員登録した後、ブラウザを閉じるとログアウト状態になってしまいます。

これを回避し、会員登録の直後から、常にログインしたままとする方法を考えてみました。

app/controllers/users/registrations_controller.rb

class Users::RegistrationsController < Devise::RegistrationsController
  def create
    super { |resource| resource.remember_me = true }
  end
end

Devise::RegistrationsController#create は、ブロックを渡すと処理してくれるのでこのようにします。

パスワードリセット後もログイン状態を維持するには、app/controllers/users/passwords_controller.rb に同様の処理を書きます。

class Users::PasswordsController < Devise::PasswordsController
  def update
    super { |resource| resource.remember_me = true }
  end
end

あとは config/routes.rb で、これらのコントローラを参照するようにすれば OK です。

devise_for(:users,
    controllers: {
      registrations: 'users/registrations',
      passwords: 'users/passwords'
    })

ログイン状態の有効期限を延ばす設定

このようにして毎日アクセスしていても、ある日突然ログアウトしていることがあります。

これを防ぐには、config/initializers/devise.rb で以下の設定をします。

config.extend_remember_period = true

これで毎日アクセスするような場合は、勝手にログアウトされることがなくなります。