Transactional fixtures in rspec prevent after_commit from being called, but even when I disable them with
RSpec.configure do |config|
config.use_transactional_fixtures = false
end
The after_commit callback does not run.
Here is a rails app with the latest rspec / rails that I have produced the issue on:
git://github.com/sheabarton/after_commit_demo.git
One way around this is to trigger the commit callbacks manually. Example:
describe SomeModel do
subject { ... }
context 'after_commit' do
after { subject.run_callbacks(:commit) }
it 'does something' do
subject.should_receive(:some_message)
end
end
end
A little late, but hope this helps others.
In my case I resolved such problem with database_cleaner's settings placed below:
config.use_transactional_fixtures = false
config.before(:suite) do
DatabaseCleaner.strategy = :deletion
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
Thanks to Testing after_commit/after_transaction with Rspec
This is similar to #jamesdevar's answer above, but I couldn't add a code block, so I have to make a separate entry.
You don't have the change the strategy for the whole spec suite. You can keep using :transaction globally then just use :deletion or :truncation (they both work) as needed. Just add a flag to the relevant spec.
config.use_transactional_fixtures = false
config.before(:suite) do
# The :transaction strategy prevents :after_commit hooks from running
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each, :with_after_commit => true) do
DatabaseCleaner.strategy = :truncation
end
then, in your specs:
describe "some test requiring after_commit hooks", :with_after_commit => true do
If you're using database_cleaner you'll still run into this. I'm using the test_after_commit gem, and that seems to do the trick for me.
This Gist helped me.
It monkey-patches ActiveRecord to fire after_commit callbacks even if using transactional fixtures.
module ActiveRecord
module ConnectionAdapters
module DatabaseStatements
#
# Run the normal transaction method; when it's done, check to see if there
# is exactly one open transaction. If so, that's the transactional
# fixtures transaction; from the model's standpoint, the completed
# transaction is the real deal. Send commit callbacks to models.
#
# If the transaction block raises a Rollback, we need to know, so we don't
# call the commit hooks. Other exceptions don't need to be explicitly
# accounted for since they will raise uncaught through this method and
# prevent the code after the hook from running.
#
def transaction_with_transactional_fixtures(options = {}, &block)
rolled_back = false
transaction_without_transactional_fixtures do
begin
yield
rescue ActiveRecord::Rollback => e
rolled_back = true
raise e
end
end
if !rolled_back && open_transactions == 1
commit_transaction_records(false)
end
end
alias_method_chain :transaction, :transactional_fixtures
#
# The #_current_transaction_records is an stack of arrays, each one
# containing the records associated with the corresponding transaction
# in the transaction stack. This is used by the
# `rollback_transaction_records` method (to only send a rollback hook to
# models attached to the transaction being rolled back) but is usually
# ignored by the `commit_transaction_records` method. Here we
# monkey-patch it to temporarily replace the array with only the records
# for the top-of-stack transaction, so the real
# `commit_transaction_records` method only sends callbacks to those.
#
def commit_transaction_records_with_transactional_fixtures(commit = true)
unless commit
real_current_transaction_records = #_current_transaction_records
#_current_transaction_records = #_current_transaction_records.pop
end
begin
commit_transaction_records_without_transactional_fixtures
rescue # works better with that :)
ensure
unless commit
#_current_transaction_records = real_current_transaction_records
end
end
end
alias_method_chain :commit_transaction_records, :transactional_fixtures
end
end
end
Put this a new file in your Rails.root/spec/support directory, e.g. spec/support/after_commit_with_transactional_fixtures.rb.
Rails 3 will automatically load it in the test environment.
Related
I've got a integration test that is run through capybara. It visits a webpage, creates an object, and renders the results. When the object is created, the controller enqueues several jobs that create some related objects.
When I run my integration test, I want to be able to examine the rendered page as if those jobs had finished. The 2 obvious solutions are:
Set the queue adapter as :inline
Manually execute/clear the enqueued jobs after creating the object.
For 1), I've attempted to set the queue adapter in a before(:each) to :inline, but this does not change the adapter, it continues to use the test adapter (which is set in my test.rb config file):
before(:each) { ActiveJob::Base.queue_adapter = :inline }
after(:each) { ActiveJob::Base.queue_adapter = :test }
it "should work" do
puts ActiveJob::Base.queue_adapter
end
which outputs: #<ActiveJob::QueueAdapters::TestAdapter:0x007f93a1061ee0 #enqueued_jobs=[], #performed_jobs=[]>
For 2), I'm not sure if this is actually possible. ActiveJob::TestHelpers provides perform_enqueued_jobs, but this methods isn't helpful, as it seems to work only for jobs explicitly referenced in the passed in block.
Assuming you're using RSpec the easiest way to use perform_enqueued_jobs is with an around block. Combining that with metatdata tags you can do something like
RSpec.configure do |config|
config.include(RSpec::ActiveJob)
# clean out the queue after each spec
config.after(:each) do
ActiveJob::Base.queue_adapter.enqueued_jobs = []
ActiveJob::Base.queue_adapter.performed_jobs = []
end
config.around :each, perform_enqueued: true do |example|
#old_perform_enqueued_jobs = ActiveJob::Base.queue_adapter.perform_enqueued_jobs
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
example.run
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = #old_perform_enqueued_jobs
end
config.around :each, peform_enququed_at: true do |example|
#old_perform_enqueued_at_jobs = ActiveJob::Base.queue_adapter.perform_enqueued_at_jobs
ActiveJob::Base.queue_adapter.perform_enqueued_at_jobs = true
example.run
ActiveJob::Base.queue_adapter.perform_enqueued_at_jobs = #old_perform_enqueued_at_jobs
end
end
Note: you need to specify the queue_adapter as :test in your config/environments/test.rb if it's not already set
You can then specify :perform_enqueued metadata on a test and any jobs specified will be run
it "should work", :perform_enqueued do
# Jobs triggered in this test will be run
end
I'm integrating Bunny gem for RabbitMQ with Rails, should I start Bunny thread in an initializer that Rails starts with application start or do it in a separate rake task so I can start it in a separate process ?
I think if I'm producing messages only then I need to do it in Rails initializer so it can be used allover the app, but if I'm consuming I should do it in a separate rake task, is this correct ?
You are correct: you should not be consuming from the Rails application itself. The Rails application should be a producer, in which case, an initializer is the correct place to start the Bunny instance.
I essentially have this code in my Rails applications which publish messages to RabbitMQ:
# config/initializers/bunny.rb
MESSAGING_SERVICE = MessagingService.new(ENV.fetch("AMQP_URL"))
MESSAGING_SERVICE.start
# app/controllers/application_controller.rb
class ApplicationController
def messaging_service
MESSAGING_SERVICE
end
end
# app/controllers/uploads_controller.rb
class UploadsController < ApplicationController
def create
# save the model
messaging_service.publish_resize_image_request(model.id)
redirect_to uploads_path
end
end
# lib/messaging_service.rb
class MessagingService
def initialize(amqp_url)
#bunny = Bunny.new(amqp_url)
#bunny.start
at_exit { #bunny.stop }
end
attr_reader :bunny
def publish_resize_image_request(image_id)
resize_image_exchange.publish(image_id.to_s)
end
def resize_image_exchange
#resize_image_exchange ||=
channel.exchange("resize-image", passive: true)
end
def channel
#channel ||= bunny.channel
end
end
For consuming messages, I prefer to start executables without Rake involved. Rake will fork a new process, which will use more memory.
# bin/image-resizer-worker
require "bunny"
bunny = Bunny.new(ENV.fetch("AMQP_URL"))
bunny.start
at_exit { bunny.stop }
channel = bunny.channel
# Tell RabbitMQ to send this worker at most 2 messages at a time
# Else, RabbitMQ will send us as many messages as we can absorb,
# which would be 100% of the queue. If we have multiple worker
# instances, we want to load-balance between each of them.
channel.prefetch(2)
exchange = channel.exchange("resize-image", type: :direct, durable: true)
queue = channel.queue("resize-image", durable: true)
queue.bind(exchange)
queue.subscribe(manual_ack: true, block: true) do |delivery_info, properties, payload|
begin
upload = Upload.find(Integer(payload))
# somehow, resize the image and/or post-process the image
# Tell RabbitMQ we processed the message, in order to not see it again
channel.acknowledge(delivery_info.delivery_tag, false)
rescue ActiveRecord::RecordNotFound => _
STDERR.puts "Model does not exist: #{payload.inspect}"
# If the model is not in the database, we don't want to see this message again
channel.acknowledge(delivery_info.delivery_tag, false)
rescue Errno:ENOSPC => e
STDERR.puts "Ran out of disk space resizing #{payload.inspect}"
# Do NOT ack the message, in order to see it again at a later date
# This worker, or another one on another host, may have free space to
# process the image.
rescue RuntimeError => e
STDERR.puts "Failed to resize #{payload}: #{e.class} - #{e.message}"
# The fallback should probably be to ack the message.
channel.acknowledge(delivery_info.delivery_tag, false)
end
end
Given all that though, you may be better off with pre-built gems and using Rails' abstraction, ActiveJob.
I am trying to test my API with an rspec integration(request) test.
I go to my api endpoint at 0.0.0.0:3000/api/regions in a browser and it returns my data, I get a session id and looks like everything is working.
I am using rack protection in my API:
module Api
class Root < Grape::API
use Rack::Protection, instrumenter: ActiveSupport::Notifications
use Rack::Protection::AuthenticityToken
prefix 'api'
helpers do
def warden
request.env['warden']
end
end
version 'v1', using: :header, vendor: 'vendor', strict: false do
mount Resources::V1::Search => '/'
mount Resources::V1::Regions => '/'
end
end
end
The api resource:
module Resources
module V1
class Regions < Grape::API
resource :regions do
get do
# Some code...
end
end
end
end
end
spec_helper.rb
[...]
RSpec.configure do |config|
config.include FactoryGirl::Syntax::Methods
config.include Rack::Test::Methods, type: :request
config.include Module.new { def app; Api::Root; end }, type: :request
config.include Warden::Test::Helpers
# ## Mock Framework
#
# If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
#
# config.mock_with :mocha
# config.mock_with :flexmock
# config.mock_with :rr
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
config.fixture_path = "#{::Rails.root}/spec/fixtures"
# If you're not using ActiveRecord, or you'd prefer not to run each of your
# examples within a transaction, remove the following line or assign false
# instead of true.
config.use_transactional_fixtures = true
# If true, the base class of anonymous controllers will be inferred
# automatically. This will be the default behavior in future versions of
# rspec-rails.
config.infer_base_class_for_anonymous_controllers = false
# Run specs in random order to surface order dependencies. If you find an
# order dependency and want to debug it, you can fix the order by providing
# the seed, which is printed after each run.
# --seed 1234
config.order = "random"
end
Here is a test:
describe Resources::V1::Regions do
describe 'GET /regions' do
it 'returns some data' do
get '/api/regions'
[... expectations - doesn't get here...]
end
end
end
Here is the error:
RuntimeError:
you need to set up a session middleware *before* Rack::Protection::RemoteToken
# ./spec/requests/api/region_spec.rb:6:in `block (3 levels) in <top (required)>'
Which comes from here: https://github.com/rkh/rack-protection/blob/master/lib/rack/protection/base.rb#L85
So I need to add rack session middleware, right?
I add use Rack::Session::Cookie, secret: '...' to my api which gets me to request.env['warden'] being nil (another question another time).
Now, however, when I load the endpoint with the browser I get:
undefined method `each' for #<ActionDispatch::Request::Session:0x7f7bf9e521e0 not yet loaded>
which raises over here: https://github.com/rack/rack/blob/master/lib/rack/session/abstract/id.rb#L158
I suspect I don't need to use Rack::Session::Cookie, as something else is doing it when loaded by the server, but I need to add something for the tests to work. No idea what that something is.
Please let me know if you need any other info.
Versions:
grape (0.6.1)
rails (4.0.2)
rack-protection (1.5.2)
rspec (2.14.1)
I was solving this by adding a 'rack.session' hash to the 3rd argument of my request i.e get '/api/regions', {}, {'rack.session' => {}}
But I found a better way: https://github.com/heavysixer/grape-on-rack/pull/1
which adds sessions and solves the warden issue at the same time.
RSpec.configure do |config|
[...]
rack_app = Module.new do
def app
#app ||= Rack::Builder.new do
use Rack::Session::Cookie
Warden::Manager.serialize_into_session { |user| user.id }
Warden::Manager.serialize_from_session { |id| User.get(id) }
use Warden::Manager do |manager|
manager.default_strategies :password, :basic
# manager.failure_app = Acme::BadAuthentication
end
run Api::Root
end
end
end
config.include rack_app, type: :request
end
Marking as answer unless anyone has anything better.
With the migration from Rails 2 to Rails 3 validation errors were moved from ActiveRecord::Error to ActiveModel::Errors.
In rails 2 the validation error had a type and a message (among other things) and you could check the type of the validation error by doing something like the following:
rescue ActiveRecord::RecordInvalid => e
e.record.errors.each do |attr, error|
if error.type == :foo
do_something
end
end
end
But with Rails 3 it seems everything but the invalid attribute and message has been lost. As a result the only way to determine the type is to compare the error message:
rescue ActiveRecord::RecordInvalid => e
e.record.errors.each do |attr, error|
if error == "foobar"
do_something
end
end
end
Which is not at all ideal (eg. what if you have several validations which use the same message?).
Question:
Is there a better way in rails 3.0 to determine the type of validation error?
Check for added? on ActiveModel::Errors:
https://github.com/rails/rails/blob/master/activemodel/lib/active_model/errors.rb#L331
That allows you to do this:
record.errors.added?(:field, :error)
I needed it not only for test purposes, but also for API. I've ended up with monkey patch:
module CoreExt
module ActiveModel
module Errors
# When validation on model fails, ActiveModel sets only human readable
# messages. This does not allow programmatically identify which
# validation rule exactly was violated.
#
# This module patches {ActiveModel::Errors} to have +details+ property,
# that keeps name of violated validators.
#
# #example
# customer.valid? # => false
# customer.errors.messages # => { email: ["must be present"] }
# customer.errors.details # => { email: { blank: ["must be present"] } }
module Details
extend ActiveSupport::Concern
included do
if instance_methods.include?(:details)
fail("Can't monkey patch. ActiveModel::Errors already has method #details")
end
def details
#__details ||= Hash.new do |attr_hash, attr_key|
attr_hash[attr_key] = Hash.new { |h, k| h[k] = [] }
end
end
def add_with_details(attribute, message = nil, options = {})
error_type = message.is_a?(Symbol) ? message : :invalid
normalized_message = normalize_message(attribute, message, options)
details[attribute][error_type] << normalized_message
add_without_details(attribute, message, options)
end
alias_method_chain :add, :details
def clear_with_details
details.clear
clear_without_details
end
alias_method_chain :clear, :details
end
end
end
end
end
# Apply monkey patches
::ActiveModel::Errors.send(:include, ::CoreExt::ActiveModel::Errors::Details)
I often see code of the form
RSpec.configure do |config|
config.include ApiHelper
# ## Mock Framework
#
# If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
#
# config.mock_with :mocha
# config.mock_with :flexmock
# config.mock_with :rr
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
config.fixture_path = "#{::Rails.root}/spec/fixtures"
# If you're not using ActiveRecord, or you'd prefer not to run each of your
# examples within a transaction, remove the following line or assign false
# instead of true.
config.use_transactional_fixtures = true
# If true, the base class of anonymous controllers will be inferred
# automatically. This will be the default behavior in future versions of
# rspec-rails.
config.infer_base_class_for_anonymous_controllers = false
config.include FactoryGirl::Syntax::Methods
end
Is there a feature in Rails that lets me do something similar? I want to be able to configure my own library with a similar syntax within config/initializers/my_class.rb
MyClass.configure do |config|
# allow configuration here
end
Nothing special is needed in Rails - it is simple Ruby code. Here is how it can be done:
class MyClass
def self.configure(&block)
a_hash = { :car => "Red" }
puts "Hello"
yield a_hash
puts "There"
end
end
MyClass.configure do |config|
puts "In Block"
puts config[:car]
end
Output:
Hello
In Block
Red
There
I am yielding a hash, but you can yield whatever object you want to.
Rails will load all the Ruby files in the config/initializers directory when starting up the server.
If you want to use the same style for your own custom configurable class then you just have to implement a configure class method that accepts a block and passes a configuration object to that block. e.g.
class MyClassConfiguration
# configuration attributes
end
class MyClass
def self.configure
yield configuration if block_given?
end
def self.configuration
#config ||= MyClassConfiguration.new
end
end
Using phoet's gem would be even easier.
Its worth taking a look at how RSpec does it if you are curious:
The RSpec.configure method is in https://github.com/rspec/rspec-core/blob/master/lib/rspec/core.rb
The Configuration class is implemented in https://github.com/rspec/rspec-core/blob/master/lib/rspec/core/configuration.rb
i don't know if rails provides a helper for that, but i wrote my own tiny solution for this problem that i use in several gems: https://github.com/phoet/confiture
it let's you define configurations:
module Your
class Configuration
include Confiture::Configuration
confiture_allowed_keys(:secret, :key)
confiture_defaults(secret: 'SECRET_STUFF', key: 'EVEN_MOAR_SECRET')
end
end
and have an easy api to do the configuration:
Your::Configuration.configure do |config|
config.secret = 'your-secret'
config.key = 'your-key'
end
besides this, there are a lot of other config tools out there like configatron or simpleconfig.
A global configuration set via the block.
module VkRobioAPI
module Configuration
OPTION_NAMES = [
:client_id,
:redirect_uri,
:display,
:response_type
]
attr_accessor *OPTION_NAMES
def configure
yield self if block_given?
self
end
end
end
module VkRobioAPI
extend VkRobioAPI::Configuration
class << self
def result
puts VkRobioAPI.client_id
end
end
end
Example:
VkRobioAPI.configure do |config|
config.client_id = '3427211'
config.redirect_uri = 'bEWLUZrNLxff1oQpEa6M'
config.response_type = 'http://localhost:3000/oauth/callback'
config.display = 'token'
end
Result:
VkRobioAPI.result
#3427211
#=> nil
If you are using rails, you can just include ActiveSupport::Configurable into your class and off you go. Outside of rails, you will have to add the activesuport gem to your Gemfile or gemspec and then call require 'active_support/configurable'.
Example
class MyClass
include ActiveSupport::Configurable
end
Usage
With block
MyClass.configure do |config|
config.key = "something"
config.key2 = "something else"
end
or inline, without block
MyClass.config.key = "something"
MyClass.config.key2 = "something else"
or like a hash
MyClass.config[:key] = "somehting"
MyClass.config[:key2] = "something else"
Note: key can be any character of your choice.