If you are using multiple databases (e.g. to shard tables across databases) for a given environment is it possible to use the built in Rails rake tasks to manipulate the databases beyond the primary one for said environment?
e.g. if I use specific connection details for a set of models, can I use rake db:create to create said database?
If so, how?
I've actually overwritten the built-in Rails rake tasks in order to manipulate a second database. The neat thing is that it totally works with the original rake tasks as well.
This is what I did... credit to Nikolay Sturm, as I based my rake task off of his blog.
I added new database settings in my database.yml, as detailed in the blog I linked.
You also create a base model that has these database settings:
class BaseModel < ActiveRecord::Base
establish_connection "database_connection_name"
self.abstract_class = true #you want to make this an abstract class because you don't have a table for this
end
You then have any model that connects to this secondary database inherit from your BaseModel.
In your rake task, you set it up as follows (keep in mind that this is for a Postgres connection.)
namespace :db do
desc 'create database'
task create: :environment do
connection_config = BaseModel.connection_config #rename BaseModel to name of your model
ActiveRecord::Base.establish_connection connection_config.merge('database => 'postgres') #you don't need this bit if you're not using Postgres
ActiveRecord::Base.connection.create_database connection_config[:database], connection_config
ActiveRecord::Base.establish_connection connection_config
end
desc 'migrate database'
task migrate: :environment do
ActiveRecord::Base.establish_connection BaseModel.connection_config
ActiveRecord::Migrator.migrate('db/migrate/')
end
desc 'drop database'
task drop: :environment do
connection_config = BaseModel.connection_config
ActiveRecord::Base.establish_connection connection_config.merge('database' => 'postgres')
ActiveRecord::Base.connection.drop_database connection_config[:database]
end
Now, if you do a rake -T in your project, you should be able to see the your descriptions for the rake tasks instead of the default. When you run them, it will run the db:create, db:migrate, and db:drop for both your default and your secondary database. Hope this helps!
Related
I want to run rake task using migration because we want when a user run rails db:migrate then this task will be run through migration.
my rake task is:
namespace :task_for_log do
desc "This task set by default as current date for those logs where log_date is nil"
task set_by_default_date_of_log: :environment do
Log.where("log_date IS NULL").each do |log|
log.update_attributes(log_date: log.created_at.to_date)
end
end
end
please guide what will be migration that run this task, any body here who will be save my life??
Migrations are really just Ruby files following a convention, so if you want to run a rake task inside of them you can just call the Rake class.
class ExampleMigration < ActiveRecord::Migration[5.0]
def change
Rake::Task['task_for_log'].invoke
end
end
However, migration files should be used specifically to handle the database schema. I would rethink how you are approaching the problem for a better solution. For example, you could run a SQL statement that updates your log attributes instead of calling a rake task.
class ExampleMigration < ActiveRecord::Migration[5.0]
def change
execute <<-SQL
UPDATE logs SET log_date = created_at WHERE log_date IS NULL
SQL
end
end
References:
Rails how to run rake task
https://edgeguides.rubyonrails.org/active_record_migrations.html
If you want to run your task after you run db:migrate automatically, you can use enhance.
Rake::Task['db:migrate'].enhance do
# This task runs after every time you run `db:migrate`
Rake::Task['task_for_log:set_by_default_date_of_log'].invoke
end
For a rails application, you can put this anywhere inside lib/tasks folder or put your task inline (inside of the .enhance do block)
You can go as #joseph mention better solution! Or create custom task for it.
rake:
rake cm:set_by_default_date_of_log
task:
#lib/tasks/cm.rake
#custom_migration
namespace :cm do
desc "This task set by default as current date for those logs where log_date is nil"
task set_by_default_date_of_log: ['db:migrate'] do
Log.where("log_date IS NULL").each do |log|
log.update_attributes(log_date: log.created_at.to_date)
end
end
end
Following the instructions in https://stackoverflow.com/a/24496452/102675 I wound up with the following:
namespace :db do
desc 'Drop, create, migrate, seed and populate sample data'
task seed_sample_data: [:drop, :create, :migrate, :seed, :populate_sample_data] do
puts 'Sample Data Populated. Ready to go!'
end
desc 'Populate the database with sample data'
task populate_sample_data: :environment do
puts Inspector.column_names.include?('how_to_fix')
# create my sample data
end
end
As you would expect, I get true if I run bundle exec rake db:populate_sample_data
BUT if I run bundle exec rake db:seed_sample_data I get all the migration output and then false. In other words I can't see the Inspector attribute how_to_fix even though it definitely exists as proved by the other rake run. Where did my attribute go?
My guess is that this is a "caching" problem. Can you try the following?
task populate_sample_data: :environment do
Inspector.reset_column_information
# ...
end
P.S. We used to have a similar problem working with different databases having the exact same schema (only except some columns here and there)
I have a rake task that I use to populate my development database. When it is done I would like it to also reset the test database, but I can't figure out the syntax. I need something like this:
namespace :db do
task populate: :environment do
Rake::Task["db:reset"].execute
Rake::Task["db:reset"].execute RAILS_ENV=test
# Add lots of data to the :environment database
end
end
This lets me run rake db:populate to populate my development database using the latest schema as well as reset the test database.
The task db:test:clone_structure will reset the test database schema to match the development database schema
namespace :db do
task populate: :environment do
Rake::Task["db:reset"].execute
Rake::Task["db:test:clone_structure"].execute
# Add lots of data to the :environment database
end
end
I am doing TDD/BDD in Ruby on Rails 3 with Rspec (2.11.0) and FactoryGirl (4.0.0). I have a factory for a Category model:
FactoryGirl.define "Category" do
factory :category do
name "Foo"
end
end
If I drop, create then migrate the database in the test enviroment I get this error:
rake aborted!
Could not find table 'categories'
This problem occurs because FactoryGirl expects the tables to already exist (for some odd reason). If I remove the spec folder from my rails app and do db:migrate, it works. Also if I mark factory-girl-rails from my Gemfile as :require => false it also works (then I have to comment that require in order to run rspec).
I found some information about this problem here: https://github.com/thoughtbot/factory_girl/issues/88
Is there something wrong that I'm doing? How can I "pass by" the FactoryGirl stage in the db:migration task?
I think you need to have factory girl definition like that in Gemfile:
gem 'factory_girl_rails', :require => false
And then you just require it in your spec_helper.rb like that:
require 'factory_girl_rails'
This is the way I'm always using this gem. You don't need to require it in other places than spec_helper.rb. Your current desired approach is just wrong.
A simple fix to this issue is to delay evaluation of any models in your factories by wrapping them in blocks. So, instead of this:
factory :cake do
name "Delicious Cake"
frosting Frosting.new(:flavor => 'chocolate')
filling Filling.new(:flavor => 'red velvet')
end
Do this (notice the curly braces):
factory :cake do
name "Delicious Cake in a box"
frosting { Frosting.new(:flavor => 'chocolate') }
filling { Filling.new(:flavor => 'red velvet') }
end
If you have a lot of factories this may not be feasible, but it is rather straightforward. See also here.
Information from: http://guides.rubyonrails.org/testing.html
When you do end up destroying your testing database (and it will happen, trust me),
you can rebuild it from scratch according to the specs defined in the development
database. You can do this by running rake db:test:prepare.
The rake db:migrate above runs any pending migrations on the development environment
and updates db/schema.rb. The rake db:test:load recreates the test database from the
current db/schema.rb. On subsequent attempts, it is a good idea to first run db:test:prepare, as it first checks for pending migrations and warns you appropriately.
rake db:test:clone Recreate the test database from the current environment’s database schema
rake db:test:clone_structure Recreate the test database from the development structure
rake db:test:load Recreate the test database from the current schema.rb
rake db:test:prepare Check for pending migrations and load the test schema
rake db:test:purge Empty the test database.
You shouldn't need to do any of that.. I think the issue is that your argument to FactoryGirl.define..
try this.
FactoryGirl.define do
factory :category do
name "Foo"
end
end
That should work fine, and does not screw up my migrations or load.. Today, I had to fix an issue where I was referencing a model constant from my factory directly and had to put it in a block to fix things.
FactoryGirl.define do
factory :category do
# this causes unknown table isseus
# state Category::Active
# this does not.
state { Category::Active }
end
end
I have a rails rake task and I want it to ignore what is in database.yml and use something else. How do I do this?
You can use ActiveRecord::Base.establish_connection to set up a database connection within a Rake task, as described in this SO question.
Or create a separate environment and add the database configuration to database.yml. Then call the rake task with rake mytask RAILS_ENV=myenvironment
You can also have direct access to the database inside a rake task by using this format:
desc "Some task"
task SomeTask: :environment do
Author.all.each do |author|
... some code
end
end