47

It seems the most commonly accepted way to deal with Selenium and tests is to avoid using transactional fixtures and then using something like database_cleaner between tests/scenarios. I recently ran into the following article which suggested doing the following:

spec_helper.rb

class ActiveRecord::Base
  mattr_accessor :shared_connection
  @@shared_connection = nil

  def self.connection
    @@shared_connection || retrieve_connection
  end
end

# Forces all threads to share the same connection. This works on
# Capybara because it starts the web server in a thread.
ActiveRecord::Base.shared_connection = ActiveRecord::Base.connection

This seems loads better for performance than the alternatives. Does anyone have any reason why this shouldn't be used?

Ryan
  • 2,460
  • 1
  • 21
  • 22

6 Answers6

33

Actually there are issues with it. If you use the gem mysql2, for example, you'll start seeing some errors like:

Mysql2::Error This connection is still waiting for a result

Please use this instead. It was written by Mike Perham, all credits to him.

class ActiveRecord::Base
  mattr_accessor :shared_connection
  @@shared_connection = nil

  def self.connection
    @@shared_connection || ConnectionPool::Wrapper.new(:size => 1) { retrieve_connection }
  end
end

ActiveRecord::Base.shared_connection = ActiveRecord::Base.connection

You'll need to install gem connection_pool too. This will spare you from many headaches.

hsgubert
  • 2,266
  • 1
  • 22
  • 23
  • 1
    I am still getting the same error with this patch. [A blog post](http://www.spacevatican.org/2012/8/18/threading-the-rat/) suggests another solution but it didn't help me too. – Tsutomu Oct 24 '12 at 01:44
  • Eventually, the cause of my problem is Ajax. If we use this hack, we should wait until the Ajax call ends before we make a query to the database. See Mike Gehard's awesome blog entry [Waiting for jQuery Ajax calls to finish in Cucumber](http://pivotallabs.com/users/mgehard/blog/articles/1671-waiting-for-jquery-ajax-calls-to-finish-in-cucumber). – Tsutomu Oct 24 '12 at 03:44
  • 3
    http://www.spacevatican.org/2012/8/18/threading-the-rat/ explains why this works. – Paul Brannan Jan 07 '13 at 18:42
15

This solution was written by Jose Valim - well respected in the Rails community and a member of the Rails core team. I doubt he would recommend using it if there were issues with it. I personally haven't had any issues.

Just be aware that if you use Spork this needs to be in the each_run block to work.

FWIW - I have had intermittent capybara test issues with the above patch on Postgres. The Mike Perham solution that @hsgubert has below appears to have solved those issues. I am now use that solution.

nmott
  • 9,454
  • 3
  • 45
  • 34
2

The DatabaseCleaner gem readme answers your "why not" question this way:

One common approach is to force all processes to use the same database connection (common ActiveRecord hack) however this approach has been reported to result in non-deterministic failures.

Arcolye
  • 6,968
  • 4
  • 34
  • 28
1

I have encountered a problem using the code you mentioned in my spec_helper.rb file.

What happens when your tests depend on using connections to multiple databases? I have two databases I need to connect to when I run my tests. I did a simple test to check what was happening to the database connections I establish.

class ActiveRecord::Base
   mattr_accessor :shared_connection
   @@shared_connection = nil

   def self.connection
     @@shared_connection || retrieve_connection
  end
end

# Forces all threads to share the same connection. This works on
# Capybara because it starts the web server in a thread.
puts "First Record cxn: #{FirstDatabase::Record.connection}"
# => First Record cxn: #<ActiveRecord::ConnectionAdapters::Mysql2Adapter:0xe59b524>
puts "AR Base cxn: #{ActiveRecord::Base.connection}"
# => AR Base cxn: #<ActiveRecord::ConnectionAdapters::Mysql2Adapter:0xc52761c>
ActiveRecord::Base.shared_connection = ActiveRecord::Base.connection

puts "First Record cxn: #{FirstDatabase::Record.connection}"
# => First Record cxn: #<ActiveRecord::ConnectionAdapters::Mysql2Adapter:0xc52761c>
puts "AR Base cxn: #{ActiveRecord::Base.connection}"
# => AR Base cxn: #<ActiveRecord::ConnectionAdapters::Mysql2Adapter:0xc52761c>

As you can see, before I call the shared connection method, I have two different database connections. After, the shared connection method call, I have only one.

So any test that requires going to the second database connection to retrieve information will fail. :(

I'm going to post this problem and see if anyone has arrived at a solution.

  • When I dumped that code into my ruby script, I got this. ruby(43221,0x109043000) malloc: *** error for object 0x7f915136e8c0: pointer being freed was not allocated I'd consider that a flaw. – baash05 Dec 04 '13 at 00:22
1

I was just doing a little reading on this myself. I discovered the snippet you shared here in this blog post:

http://blog.plataformatec.com.br/2011/12/three-tips-to-improve-the-performance-of-your-test-suite/

To answer your question directly, the database cleaner github page cautions that it can " result in non-deterministic failures". I'd go right ahead and use it, but if you start running into weird failures, maybe this is a good place to start looking.

counterbeing
  • 2,721
  • 2
  • 27
  • 47
1

There's a good thing at the end of this post. It may explain why I get a MALLOC error when I attempt to a very simple threading script.

http://apidock.com/rails/ActiveRecord/Base/connection

leente - March 15, 2011 0 thanks
Don't cache it!

Don’t store a connection in a variable, because another thread might try to use it when it’s already checked back in into the connection pool. See: ActiveRecord::ConnectionAdapters::ConnectionPool

connection = ActiveRecord::Base.connection

threads = (1..100).map do
  Thread.new do
    begin
      10.times do
        connection.execute("SELECT SLEEP(1)")  # WRONG
        ActiveRecord::Base.connection.execute("SELECT SLEEP(1)")  # CORRECT
      end
      puts "success"
    rescue => e
      puts e.message
    end
  end
end

threads.each(&:join)
baash05
  • 4,394
  • 11
  • 59
  • 97