Log SQL queries during rake tasks - ruby-on-rails

Similar to 'rails server' that prints every SQL query executed I would like to do the same for rake tasks.
What is the best way to achieve that?

Proper answer is to put this at the beginning of rake task:
ActiveRecord::Base.logger = Logger.new STDOUT

Depending on your environment, Rake will log sql queries just like any Rails process will & in the same logfile. So on your dev box, check your log/development.log file - it will contain your Rake task's queries. If you want queries logged in production, set the log level in your Rake task to DEBUG, and make sure the rake task depends on :environment.
desc "Task with SQL logging"
task :test_log => :environment do
Rails.logger.level = Logger::DEBUG
Your code here...
end

rake db:migrate --trace
The --trace will show details

echo '' > log/development.log
rake db:migrate:redo VERSION=20141017153933
cat log/development.log

I tried the above and couldn't get it to work. Syntax was fine, no errors, but no sql came to stdout (or the log). I'm using rails 3.2. I'm also running in a production environment.
To see the sql queries generated by my rake tasks, I used the technique found at http://eewang.github.io/blog/2013/07/29/how-to-use-rake-tasks-to-generate-migration-sql/
In particular, I just inserted this block in my task before the find() statements that generated the SQL queries I was intersted in:
ActiveRecord::Base.connection.class.class_eval do
# alias the adapter's execute for later use
alias :old_execute :execute
# define our own execute
def execute(sql, name = nil)
print "===== #{sql}\n"
old_execute sql, name
end
end
Then I could see the SQL on stdout. This is not my code - Eugene Wang came up with this technique.

You can create the following task (lib/tasks/db.rake):
task :show_sql do
ActiveRecord::Base.logger = Logger.new STDOUT
end
And use it like so:
$ bin/rake show_sql db:migrate

Related

Run Rake task programmatically with specified environment

I'm setting up a second database with my Ruby on Rails (3) application, so I want to create a rake task to create the second development database. I'm trying to overwrite the rake db:create task such that it does all the database creation that I need. However, it seems I can't find a suitable way to perform this task. I've tried a few approaches - establishing a connection to the database from the URL:
# remove db:create from the list of rake tasks in order to override it
db_create = Rake.application.instance_variable_get('#tasks').delete('db:create')
namespace :db do
task :create do
if Rails.env == "development"
# database.yml contains an entry for secondary_development, this works, as confirmed from rails console
ActiveRecord::Base.establish_connection "postgresql://localhost/secondary_development"
Rake::Task["db:create"].invoke # this does nothing
end
# invoke original db_create task - this works
db_create.invoke
end
end
Another approach was to do:
# remove db:create from the list of rake tasks in order to override it
db_create = Rake.application.instance_variable_get('#tasks').delete('db:create')
namespace :db do
task :create do
if Rails.env == "development"
Rails.env = "secondary_development"
Rake::Task["db:create"].invoke
end
# invoke original db_create task - this doesn't work like this
db_create.invoke
end
end
This time only the secondary_development db:create works and the database is created as desired, but the development database is no longer created using this approach.
From one answer I found elsewhere, I thought that reenabling the task would be necessary, but that didn't change anything here and appears not to be the issue.
Finally, an approach that has worked is:
# remove db:create from the list of rake tasks in order to override it
db_create = Rake.application.instance_variable_get('#tasks').delete('db:create')
namespace :db do
task :create do
if Rails.env == "development"
system("rake db:create RAILS_ENV=secondary_development")
end
db_create.invoke
end
end
The only issue here is that because the rake task is being run via system, the Rails application has to load before being executed, so I'm essentially loading the application twice fully just to run the task - this will be 3 times when I add a test database into the mix.
So, the actual question(s):
Is it possible to run Rake::Task["..."] programmatically with a specified environment?
Why doesn't ActiveRecord::Base.establish_connection work in this way when creating the database? I had success when running this from Rails console.
I managed to find a solution to this. I believe the reason is that .invoke will not always invoke the task, but it will first determine whether it is necessary. Given that rake db:create is run on several occasions within the same task, .invoke deems the subsequent invocations as unnecessary and therefore does not run them. For the desired behaviour, .execute should be used instead.
# remove db:create from the list of rake tasks in order to override it
db_create = Rake.application.instance_variable_get('#tasks').delete('db:create')
namespace :db do
task :create do
if Rails.env == "development"
Rails.env = "secondary_development"
Rake::Task["db:create"].execute # execute rather than invoke
end
# Reset the Rails env to 'development', otherwise it remains as 'secondary_development', which is not what we want (or move this above the if)
Rails.env = "development"
db_create.execute
end
end

Ruby on rails specify environment in rake task

I want to create a custom db/seeds file and pass it to a specific environment
desc "Select for bonus"
task :bonus => :environment do
puts "Bonus for: #{pick(User).name}"
end
Is is possible in the task section of my rake task to specify say only the test environment without having to run RAILS_ENV=test rake in my command line?
If all you are doing is seeding the database with this specific task, you just need to establish connection with the right database as part of the task.
desc "Select for bonus"
task :bonus => :environment do
puts "Bonus for: #{pick(User).name}"
ActiveRecord::Base.establish_connection('test')
....
end
ActiveRecord::Base.establish_connection('test') above connects to the test database before running the rest of the steps on that database.
If you are doing lot more complicated things as part of the task, that is possible too. See How do I force RAILS_ENV in a rake task? for some tips.

How to run schema:load on the initial capistrano 3 deploy of my rails app

I would like to run db:schema:load in place of db:migrate on the initial deploy of my rails app.
This used to be fairly trivial, as seen in this stack overflow question, but in Capistrano 3, they have deprecated the deploy:cold task. The initial deploy isn't any different than all subsequent deploys.
Any suggestions? Thanks!
I, too, am new to Capistrano, and trying to use it for the first time to deploy a Rails app to production servers I configured with Puppet.
I finally had to dig into the Capistrano source (and capistrano/bundler, and capistrano/rails, and even sshkit and net-ssh to debug auth problems) to determine exactly how everything works before I felt confidant deciding for myself what changes I wanted to make. I just finished making those changes, and I'm pleased with the results:
# lib/capistrano/tasks/cold.rake
namespace :deploy do
desc "deploy app for the first time (expects pre-created but empty DB)"
task :cold do
before 'deploy:migrate', 'deploy:initdb'
invoke 'deploy'
end
desc "initialize a brand-new database (db:schema:load, db:seed)"
task :initdb do
on primary :web do |host|
within release_path do
if test(:psql, 'portal_production -c "SELECT table_name FROM information_schema.tables WHERE table_schema=\'public\' AND table_type=\'BASE TABLE\';"|grep schema_migrations')
puts '*** THE PRODUCTION DATABASE IS ALREADY INITIALIZED, YOU IDIOT! ***'
else
execute :rake, 'db:schema:load'
execute :rake, 'db:seed'
end
end
end
end
end
The deploy:cold task merely hooks my custom deploy:inidb task to run before deploy:migrate. That way the schema and seeds get loaded, and the deploy:migrate step that follows does nothing (safely) because there are no new migrations to run. As a safety, I test to see if the schema_migrations table already exists before loading the schema in case you run deploy:cold again.
Note: I choose to create the DB using Puppet so I can avoid having to grant the CREATEDB privilege to my production postgresql user, but if you want Capistrano to do it, just add "execute :rake, 'db:create'" before the db:schema:load, or replace all three lines with 'db:setup'.
You'll have to define deploy:cold as basically a duplicate of the normal deploy task but with deploy:db_load_schema instead of deploy:migrations. For example:
desc 'Deploy app for first time'
task :cold do
invoke 'deploy:starting'
invoke 'deploy:started'
invoke 'deploy:updating'
invoke 'bundler:install'
invoke 'deploy:db_load_schema' # This replaces deploy:migrations
invoke 'deploy:compile_assets'
invoke 'deploy:normalize_assets'
invoke 'deploy:publishing'
invoke 'deploy:published'
invoke 'deploy:finishing'
invoke 'deploy:finished'
end
desc 'Setup database'
task :db_load_schema do
on roles(:db) do
within release_path do
with rails_env: (fetch(:rails_env) || fetch(:stage)) do
execute :rake, 'db:schema:load'
end
end
end
end
It might even be better to run the deploy:db_schema_load task independently, as the tasks included in the default deploy might change over time.
I actually using db:setup for fresh deploys because it seeds the database after creating tables:
desc 'Setup database'
task :db_setup do
...
execute :rake, 'db:setup'
...
end

puts vs logger in rails rake tasks

In a rake task if I use puts command then I see the output on console. However I will not see that message in log file when app is deployed on production.
However if I say Rails.logger.info then in development mode I see nothing on console. I need to go to log file and tail that.
I would ideally like to use Rails.logger.info and in development mode inside the rake task, the output from logger should also be sent to console.
Is there a way to achieve that?
Put this in application.rb, or in a rake task initialize code
if defined?(Rails) && (Rails.env == 'development')
Rails.logger = Logger.new(STDOUT)
end
This is Rails 3 code. Note that this will override logging to development.log. If you want both STDOUT and development.log you'll need a wrapper function.
If you'd like this behaviour only in the Rails console, place the same block of code in your ~/.irbrc.
You could create a new rake task to get this to work.
desc "switch logger to stdout"
task :to_stdout => [:environment] do
Rails.logger = Logger.new(STDOUT)
end
This way when you execute your rake task you can add to_stdout first to get stdout log messages or don't include it to have messages sent to the default log file
rake to_stdout some_task
Rake tasks are run by a user, on a command-line. Anything they need to know right away ("processed 5 rows") should be output on the terminal with puts.
Anything that needs to be kept for posterity ("sent warning email to jsmith#example.com") should be sent to the Rails.logger.
Code
For Rails 4 and newer, you can use Logger broadcast.
If you want to get both STDOUT and file logging for rake tasks in development mode, you can add this code into config/environments/development.rb :
if File.basename($0) == 'rake'
# http://stackoverflow.com/questions/2246141/puts-vs-logger-in-rails-rake-tasks
log_file = Rails.root.join("log", "#{Rails.env}.log")
Rails.logger = ActiveSupport::Logger.new(log_file)
Rails.logger.extend(ActiveSupport::Logger.broadcast(ActiveSupport::Logger.new(STDOUT)))
end
Test
Here's a small Rake task to test the above code :
# lib/tasks/stdout_and_log.rake
namespace :stdout_and_log do
desc "Test if Rails.logger outputs to STDOUT and log file"
task :test => :environment do
puts "HELLO FROM PUTS"
Rails.logger.info "HELLO FROM LOGGER"
end
end
Running rake stdout_and_log:test outputs
HELLO FROM PUTS
HELLO FROM LOGGER
while
HELLO FROM LOGGER
has been added to log/development.log.
Running rake stdout_and_log:test RAILS_ENV=production outputs
HELLO FROM PUTS
while
HELLO FROM LOGGER
has been added to log/production.log.
I'd say that using Rails.logger.info is the way to go.
You won't be able to see it in the server console because it won't run via the server. Just open up a new console and tail -f the log file, it'll do the trick.
Many users are aware of the UNIX®
command 'tail', which can be used to
display the last few lines of a large
file. This can be useful for viewing
log files, etc.
Even more useful in some situations,
is the '-f' parameter to the 'tail'
command. This causes tail to 'follow'
the output of the file. Initially, the
response will be the same as for
'tail' on its own - the last few lines
of the file will be displayed.
However, the command does not return
to the prompt, and instead, continues
to 'follow' the file. When additional
lines are added to the file, they will
be displayed on the terminal. This is
very useful for watching log files, or
any other file which may be appended
over time. Type 'man tail' for more
details on this and other tail
options.
(via)
In Rails 2.X to redirect the logger to STDOUT in models:
ActiveRecord::Base.logger = Logger.new(STDOUT)
To redirect logger in controllers:
ActionController::Base.logger = Logger.new(STDOUT)
Execute a background job with '&' and open script/console or whatever..
That way you can run multiple commands in the same window.
tail -f log/development.log &
script/console
Loading development environment (Rails 2.3.5)
>> Product.all
2011-03-10 11:56:00 18062 DEBUG Product Load (6.0ms) SELECT * FROM "products"
[<Product.1>,<Product.2>]
note Can get sloppy quickly when there is a lot of logging output.
How about creating an application helper which detects which environment is running and does the right thing?
def output_debug(info)
if RAILS_ENV == "development"
puts info
else
logger.info info
end
end
Then call output_debug instead of puts or logger.info

How do I find the source file for a rake task?

I know you can view all possible rake tasks by typing
rake -T
But I need to know what exactly a task does. From the output, how can I find a source file that actually has the task? For example, I'm trying to find the source for the db:schema:dump task.
I know this is an old question, but in any case:
rake -W
This was introduced in rake 0.9.0.
http://rake.rubyforge.org/doc/release_notes/rake-0_9_0_rdoc.html
Support for the –where (-W) flag for showing where a task is defined.
Despite what others have said, you can programmatically get the source location of rake tasks in a rails application. To do this, just run something like the following in your code or from a console:
# load all the tasks associated with the rails app
Rails.application.load_tasks
# get the source locations of actions called by a task
task_name = 'db:schema:load' # fully scoped task name
Rake.application[task_name].actions.map(&:source_location)
This will return the source locations of any code that gets executed for this task. You can also use #prerequisites instead of #source_location to get a list of prerequisite task names (e.g. 'environment', etc).
You can also list all tasks loaded using:
Rake.application.tasks
UPDATE: See Magne's good answer below. For versions of rake >= 0.9.0 you can use rake -W to show the source location of your rake tasks.
There is no programmatic way to do this unfortunately. Rake tasks can be loaded either from rails itself, lib/tasks, or from any plugin with a tasks directory.
This should nab most everything not within Rails itself:
find . -name "*.rake" | xargs grep "whatever"
As for db:schema:dump, here's the source:
desc "Create a db/schema.rb file that can be portably used against any DB supported by AR"
task :dump => :environment do
require 'active_record/schema_dumper'
File.open(ENV['SCHEMA'] || "#{RAILS_ROOT}/db/schema.rb", "w") do |file|
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
end
end
It can be found on line 242 of lib/tasks/database.rake in the rails 2.2.2 gem. If you've got a different version of Rails, just search for "namespace :schema".
You probably actually want the source of the ActiveRecord::SchemaDumper, but I think you should have no trouble figuring out where that is. :-)
For most rake tasks in Rails, look in the Rails gem directory, in lib/tasks.
If you've vendored Rails into your app directory structure then look in vendor/rails/railties/lib/tasks instead
Either way, db:schema:dump is in databases.rake.

Resources