I have a specific test I'd like to write to check for regressions. I've inherited a Rails environment that had no unit tests and there have been very few guards against what is put into the database via external (outside of Rails) means.
The unit test would iterate over a specific model in the database and render a view, ensuring that every instance could still be rendered correctly in that view. I'd like to run this against a development database instance which is usually a clone of the production database (mysql).
The data is too large to put into fixtures... I'd like to keep the rest of the tests using the 'test' sqlite database.
So - is there a way to override for a single test which database Rails is connected to?
I'm using Rails 3.2.22.
Edit:
Based on Babar's answer, I'm using:
class MyTest < ActionController::TestCase
def setup
#controller = MyController.new
end
test "should successfully render all" do
begin
connection_config = ActiveRecord::Base.connection_config()
ActiveRecord::Base.establish_connection('development')
MyModel.find_each do |i|
print "#{i.id} - #{i.name}\n"
get(:show, {'name' => i.name})
end
ensure
ActiveRecord::Base.establish_connection(connection_config)
# or ActiveRecord::Base.establish_connection(ENV['RAILS_ENV'])
end
end
end
But I get an exception at teardown of trying to rollback. I assume this is because the test was originally wrapped around a transaction, and since I swapped databases, the connection is now closed and we aren't really in a transaction anymore.
What's the most straight forward workaround?
Edit:
Adding this looks to be the most straightforward:
def setup
#controller = DashboardController.new
#use_transactional_fixtures = false
end
There is a class method called establish_connection which can do what you want. Just connect to a different db while running that specific test using ActiveRecord::Base.establish_connection.
ActiveRecord::Base.establish_connection(
:adapter => "mysql",
:host => "localhost",
:username => "myuser",
:password => "mypass",
:database => "somedatabase"
)
You can use the database alias from your database.yml file. It would be something like this:
ActiveRecord::Base.establish_connection('development')
You may code it right into your specific test file.
Check the doc: http://apidock.com/rails/v3.2.1/ActiveRecord/Base/establish_connection/class
Related
Is it possible to modify the Rails obj?
I only want to modify it briefly and change it back.
My Reasoning:
I am trying to work on my seeds file and make it a little more robust.
In my model there is a process that looks at the current controller and the current user, it tracks this user during there session.
It throws an error though during my seed tests because there is no controller based user session.
What I wanted to do was to add
Rails.seed = true
at the start of my seed, it would get to the model and in the model I would wrap a control flow(if statement) for this property around the block that setups up tracking.
Then I would remove
Rails.seed = true
at the end of the seed file.
Instead of putting it directly on the Rails object, you can use custom configuration
config/initializers/custom_config.rb (name unimportant, just in an initializer)
Rails.configuration.seeding = false
db/seeds.rb
Rails.configuration.seeding = true
User.create
app/models/user.rb
class User < ApplicationRecord
# just as an example
after_initialize do
if Rails.configuration.seeding
puts "Seeding DB"
else
puts "Normal"
end
end
end
output
$ bin/rake db:seed
# Running via Spring preloader in process 19017
# Seeding DB
$ bin/rails c
User.create
# Normal
# => #<User ...>
I wouldn't necessarily recommend modifying the Rails class but to achieve that you could do something like:
class Rails
attr_accessor :seeding
def seeding=(bool)
#seeding = bool
end
def seeding?
#seeding ||= false
end
end
Then you could use Rails.seeding = true to set it and Rails.seeding? to access it. Also it will default to false if it is unset.
Another solution might be wrapping the part that is blowing up in a being rescue block to catch the error.
Continue to try to make the ability to connect to multiple databases in one application in Rails. This capability is a specific production requirement and I am a complete newbie...
So I took a prior recommendation. Note that I could not get the application to read the 'deals_qa' environment from the database.yml, or at least this is how it appeared.
Right now, the end result is that it's sending me to an Application generated 'Oops' page, which, I'm assuming, was created to account for any errors, so nothing specific as far as why this isn't working.
Please review and provide any suggestions, or other things that I should could be looking at to get this working... Thanks!
Here is the code changes based on previous recommendation:
This is the new model file, named 'dealsdb':
module Dealsdb
#abstract_class = true
conn = { :adapter => 'mysql',
:encoding => 'utf8',
:reconnect => 'true',
:database => 'deals_qa',
:username => 'xxxxxx',
:password => 'xxxxxx',
:host => 'xx.xx.xx.xx',
:port => 'xxxx'
}
establish_connection(conn["deals_qa"])
end
end
class Members < Dealsdb::Base
def email_exists?(email)
unless email.blank? || SYSTEM_EMAILS.include?(email)
returning find_by_email(email) do |user|
end
end
end
end
This is the corresponding code snippet in the existing Account Controller file, file name Account_Controller.rb. Note that I'm just trying to get past the first condition.
if Dealsdb::Members.email_exists?(#email)
#Redirect_Flag = true
else
flash.now[:error] = 'Didnt MMS database check didnt work'
end
Thank You!
You don't have anything inheriting from ActiveRecord::Base. The way I would do this is to have my models inherit from ActiveRecord::Base (the norm in rails). Then inside the model change the connection with establish_connection(db_name).
Example:
class Members < ActiveRecord::Base
establish_connection(:foo)
def email_exists?(email)
unless email.blank? || SYSTEM_EMAILS.include?(email)
returning find_by_email(email) do |user|
end
end
end
end
In database.yml:
foo:
adapter: mysql
host: localhost
database: deals_qa
username: xxxxxx
password: xxxxxx
Here's a reference link I found: https://groups.google.com/forum/?fromgroups=#!topic/rubyonrails-talk/OpHh6FdNELo
There is a previous question speaking to connecting to multiple databases in Rails
Rails RSpec with Multiple Databases
I references Rspec but the pattern works in general and I have used it successfully many times.
I'll try here since the mailing list for DM doesn't seem to have much input from other users unfortunately.
I'm reasonably sure this isn't something we have to do manually, but maybe I'm wrong. I've removed ActiveRecord from my project and have started creating models in DataMapper. It's all working, but I want to write unit tests for my models (and functional for my controllers). However, my test database is not cleaned between test runs (easily proven with a test). AR takes care of this for you, but it seems like the DM guys haven't considered this in their dm-rails project.
In a desperate attempt to wipe the slate clean, I dropped all tables in my test database. Now instead of my unit tests failing because the environment is dirty, they fail because the schema doesn't exist. Looking at the rake tasks available to me, I cannot restore my test DB without also wiping my development database. I'm starting to go insane and hoping a fellow DM + Rails 3 user can nudge me in the right direction.
Specifically, when I run my unit tests, all test data should be removed between the test methods. Also, if I make a change to the schema, I should be able to run my tests and they should work.
I tried putting DataMapper.auto_migrate! in a setup callback in my test_helper.rb, but this doesn't seem to create the schema (the tests still fail due to the tables not existing when they try to insert/select records).
I've seen https://github.com/bmabey/database_cleaner, but do we really have to bring an external library into Rails just to do something that DM probably already has (seemingly undocumented) support for? This also doesn't address the issue of recreating the schema.
The answer came back on the mailing list that it's basically a do-it-yourself situation, so to save others the hassle if they end up having to do this too:
Create a .rake file under lib/tasks, called something like test_db_setup.rake:
require File.dirname(__FILE__) + '/../../test/database_dumper'
# Custom logic that runs before the test suite begins
# This just clones the development database schema to the test database
# Note that each test does a lightweight teardown of just truncating all tables
namespace :db do
namespace :test do
desc "Reset the test database to match the development schema"
task :prepare do
Rake::Task['db:schema:clone'].invoke
end
end
namespace :schema do
desc "Literally dump the database schema into db/schema/**/*.sql"
task :dump => :environment do
DatabaseDumper.dump_schema(:directory => "#{Rails.root}/db/schema", :env => Rails.env)
end
desc "Clones the development schema into the test database"
task :clone => [:dump, :environment] do
DatabaseDumper.import_schema(:directory => "#{Rails.root}/db/schema", :env => "test")
end
end
end
task 'test:prepare' => 'db:test:prepare'
This uses the :test:prepare hook that Rails provides, which runs just before the test suite begins. It copies the schema from your development database into .sql files under db/schema/ (one per table/view), then it imports those .sql files into your test database.
You'll need the utility class I wrote for this to work (currently it's written for MySQL >= 5.0.1. You'll have to adjust the logic if you need a different database.
# Utility class for dumping and importing the database schema
class DatabaseDumper
def self.dump_schema(options = {})
options[:directory] ||= "#{Rails.root}/db/schema"
options[:env] ||= Rails.env
schema_dir = options[:directory]
clean_sql_directory(schema_dir)
Rails::DataMapper.configuration.repositories[options[:env]].each do |repository, config|
repository_dir = "#{schema_dir}/#{repository}"
adapter = DataMapper.setup(repository, config)
perform_schema_dump(adapter, repository_dir)
end
end
def self.import_schema(options = {})
options[:directory] ||= "#{Rails.root}/db/schema"
options[:env] ||= "test"
schema_dir = options[:directory]
Rails::DataMapper.configuration.repositories[options[:env]].each do |repository, config|
repository_dir = "#{schema_dir}/#{repository}"
adapter = DataMapper.setup(repository, config)
perform_schema_import(adapter, repository_dir)
end
end
private
def self.clean_sql_directory(path)
Dir.mkdir(path) unless Dir.exists?(path)
Dir.glob("#{path}/**/*.sql").each do |file|
File.delete(file)
end
end
def self.perform_schema_dump(adapter, path)
Dir.mkdir(path) unless Dir.exists?(path)
adapter.select("SHOW FULL TABLES").each do |row|
name = row.values.first
type = row.values.last
sql_dir = "#{path}/#{directory_name_for_table_type(type)}"
Dir.mkdir(sql_dir) unless Dir.exists?(sql_dir)
schema_info = adapter.select("SHOW CREATE TABLE #{name}").first
sql = schema_info.values.last
f = File.open("#{sql_dir}/#{name}.sql", "w+")
f << sql << "\n"
f.close
end
end
def self.directory_name_for_table_type(type)
case type
when "VIEW"
"views"
when "BASE TABLE"
"tables"
else
raise "Unknown table type #{type}"
end
end
def self.perform_schema_import(adapter, path)
tables_dir = "#{path}/tables"
views_dir = "#{path}/views"
{ "TABLE" => tables_dir, "VIEW" => views_dir }.each do |type, sql_dir|
Dir.glob("#{sql_dir}/*.sql").each do |file|
name = File.basename(file, ".sql")
drop_sql = "DROP #{type} IF EXISTS `#{name}`"
create_sql = File.open(file, "r").read
adapter.execute(drop_sql)
adapter.execute(create_sql)
end
end
end
end
This will also leave the .sql files in your schema directory, so you can browse them if you want a reference.
Now this will only wipe your database (by installing a fresh schema) as the test suite starts up. It won't wipe the tests between test methods. For that you'll want to use DatabaseCleaner. Put it in your test_helper.rb:
require 'database_cleaner'
DatabaseCleaner.strategy = :truncation, {:except => %w(auctionindexview helpindexview)}
class ActiveSupport::TestCase
setup :setup_database
teardown :clean_database
private
def setup_database
DatabaseCleaner.start
end
def clean_database
DatabaseCleaner.clean
end
end
Now you should be good to go. Your schema will be fresh when you start running the tests, you'll have a copy of your SQL in the db/schema directory, and your data will be wiped between test methods. A word of warning if you're enticed by the transaction strategy of DatabaseCleaner... this is rarely a safe strategy to use in MySQL, since none of the MySQL table types currently support nested transactions, so your application logic will likely break the teardown. Truncate is still fast, and much safer.
I am testing create method of BranchController using ActionController::TestCase (code below) . I check if object is created by calling find_by_name method(assume name is unique here).
test runs succesfully but when i check same record in mysql db, its not there.
class Security::BranchControllerTest < ActionController::TestCase
test "the create" do
post(:create, :branch => {:name => "test branch", :details=> "test branch details"})
#replace find with where searching with all of fields
assert_not_nil Company::Branch.find_by_name("test branch")
end
end
If you're using a database that supports transactions (as most do these days), rails tests will by default set a savepoint before running each test and do a rollback at the end.
This means that the insertion is actually happening, but the result is not visible outside the test.
You should see the savepoint and rollback operations in your test logs.
If you want to disable this, you can add
self.use_transactional_fixtures = false
in your test class, or patch it in for all your tests by adding something like
class ActiveSupport::TestCase
self.use_transactional_fixtures = false
end
in your test_helper class.
It's likely not a good idea to disable this generally though, as it's a nice way to keep your tests independent.
I'm developing a Rails application in which each subdomain has a separate database. And I'm doing something like this.
#app/controller/application_controller.rb
class ApplicationController < ActionController::Base
before_filter :select_database
private
def select_database
MyModel.use_database(request.subdomains.first)
end
end
#app/model/my_model.rb
class MyModel < ActiveRecord::Base
def self.use_database path
establish_connection :adapter => 'sqlite3', :database =>
File.join(RAILS_ROOT, "db", "sqlite3", path)
end
end
Now, in production mode, requests come and execute in this order.
Request "A" comes for subdomain a.example.net and MyModel establishes a connection with database "a".
Now another request "B" comes for subdomain b.example.net and MyModel establishes a connection with database "b".
Now if request "A" tries to do a MyModel.find_*() which database would it access? "a" or "b"?
I believe that this is what we call "thread safety" or "race condition", and if this is so, then how can we avoid it while implementing an application using one database per subdomain?
In the above question, my assumption is that executing two requests simultaneously is a normal behaviour for production servers. Or is there some better approach. I don't have much experience of production servers, so please advice.
It seems like you are using Replications... However I do suggest that you watch the following casts from newrelic to get a better idea on DB scaling:
Scaling Your Database - Part 1
Scaling Your Database - Part 2
If there are two models and two databases,
why not subclass your MyModel for each database?
class MyModelDomainA < MyModel
DBA_PATH = "first/db/path"
def self.use_database
establish_connection :adapter => 'sqlite3', :database => File.join(RAILS_ROOT, "db", "sqlite3", DBA_PATH)
end
end
# then in controller:
def select_database
# or whatever string-manipulation you need to do...
# even have a lookup hash to get the correct model?
model_klass = "MyModel#{request.subdomains.first.capitalize}"
model_klass.constantize.use_database
end
etc.
Obviously this only works if you have a small, fixed number of domain/database pairs.
But if so - it means that anybody coming into DomainA will always access the database for Domain A = no race conditions.
Generally, I find that unless you're hacking kernel code, the solution to a race condition is not thread-safety... but to re-think the problem.