How to Know the Current Rake Task

Is there a way to know the current rake task?

I'm thinking of making an app behave different when runned by rake.

Is it already enough to check caller, if it is called from rake, or do you need also which task?


I hope, it is ok, when you can modify the rakefile. I have a version which introduce Rake.application.current_task.

# Rakefile
require 'rake'
module Rake
class Application
attr_accessor :current_task
end
class Task
alias :old_execute :execute
def execute(args=nil)
Rake.application.current_task = @name
old_execute(args)
end
end #class Task
end #module Rake

task :start => :install do; end
task :install => :install2 do
MyApp.new.some_method()
end
task :install2 do; end

# myapp.rb
class MyApp
def some_method(opts={})
## current_task? -> Rake.application.current_task
puts "#{self.class}##{__method__} called from task #{Rake.application.current_task}"
end
end

Two remarks on it:

  • you may add the rake-modifications in a file and require it in your rakefile.
  • the tasks start and install are test tasks to test, if there are more then one task.
  • I made only small tests on side effects. I could imagine there are problems in a real productive situation.

How to get name of current rake task in my Rails model?

If you run your task via rake task or bundle exec rake task you can check it in your initializer simply by:

if $0.end_with?('rake')
# rake stuff
else
# non-rake stuff
end

You can use $PROGRAM_NAME instead of $0 if you like.

How to know that a rake task has been run in rails?

Using rake task for data migration is an extremely risky idea. Couple of reasons not to do this:

  1. Even if you manage to find out whether your rake task has finished or not, your migration will still be marked as completed and you won't be able to replay it. Only way around is raising an exception in your migration.

    No, you won't be able to rollback that migration neither. If rake task finishes after the migration has run, rollback will try to add already existing column.

  2. Setting up your database from scratch by new devs will become painful as hell, as they will need to know which rake tasks are to be run when. Not to mentioned that rake db:migrate executes all migrations.

  3. You're polluting your rake task list with non-reusable tasks

It seems that what you're doing is just a regular data migration, so all the stuff done by your rake task should be in fact part of your migration. That will even allow you to make a reversible data migration (in majority of cases).

Note however that data migrations are not that simple as regular scheme-only migrations. Because your migration should be completely independent on your code (as they are to work in the future, even when migrated model is completely removed from your codebase), so it is a common practice to redefine the models you are about to use in your migrations (only the bits required fro the migration). This is not that simple as it sounds, unfortunately, and honestly I am still looking for a perfect solution to that. The best I've seen so far is simple (I'm assuming that paid_by used to be string and you changed it paid_by_id, which references the user):

class YOURMIGRATIONNAME < ActiveRecord::Migration
class Transaction < ActiveRecord::Base
belongs_to :paid_by, class_name: "User"
end

class User < ActiveRecord::Base
end

def up
add_column :transaction, :paid_by_id, :integer

Transaction.transaction do # for speed
Transaction.find_each do |t|
t.paid_by_id = User.find_by(username: t[:paid_by])
t.save! # Always banged save in migration!
end
end

remove_column :paid_by
end

def down
add_column :transaction, :paid_by, :string

Transactions.transaction do
Transaction.find_each do |t|
t[:paid_by] = t.paid_by && t.paid_by.username
t.save!
end
end

remove_column :transactions, :paid_by_id

end

The only downfall of using the code above is that it won't work well if any of those models is using STI (I've made that mistake once, took a while to find out what's wrong). The work around is to define it outside of the migration class, but then those classes are available across all migrations and can be affected with your actual model code (especially in production when all the models are preloaded). In short, data migration with STI is something I am still looking into at the moment.

Get rake task name in initializer

None of the other answers presented here will work unless you stop using Spring, because Spring changes the way rake tasks are called significantly.

When using Spring, the command being run is handed over to the Spring server process using a UNIX socket and unfortunately Spring server reads this socket to get the command and its arguments after initializing the rails environment. Thus, during rails initialization, there seems to be no way of getting the command and its arguments (e.g. the rake task name) when using Spring, as Spring itself does not know yet! Even the after_fork hook that Spring provides won't help, because it is being also run after rails initialization.

A proof can be seen in the Spring source code. It is the serve method in which Spring gets the ARGV of the command being run from the socket, forks itself and runs the command. The relevant parts of the method are these:

def serve(client)
# ... getting standard input / output streams from the client socket

# this is where rails initialization occurs
preload unless preloaded?

# this is where Spring gets the command name and it's ARGV and environment
args, env = JSON.load(client.read(client.gets.to_i)).values_at("args", "env")
command = Spring.command(args.shift)

# ...

# fork and run the command
pid = fork {
# ...
# run the command
ARGV.replace(args)
$0 = command.exec_name
# ...

# run the after_fork hook
invoke_after_fork_callbacks

command.call
}

# ...
end

The rails initializers are run in the preload method which is run before the command name is read from the socket. The $0 and ARGV variables are also set after initialization, in the fork block.

So, unless you monkey-patched Spring significantly (replaced the serve method with your own, but you'd need to handle working with the socket yourself), you need to stop calling your rake tasks inside the Spring environment. If the rake command is a binstub in the RAILS_ROOT/bin/ directory, you need to remove the binstub with spring binstup --remove rake.

Only then, I believe, you can use one of the solutions in the other answers.

Check if rake task exists from within Rakefile

what about doing something like this? Invoke the task if it exists, as opposed to making it an explicit dependency?

 task :archive => [:clean, :vendor_deps] do 
Rake.application["assets:precompile"].invoke if Rake::Task.task_defined?('assets:precompile')
....
end

or even easier. Since specifying the task again allows you to add to it, something like this appears to work as well.

task :archive => [:clean, :vendor_deps] do
...
end
task :archive => "assets:precompile" if Rake::Task.task_defined?("assets:precompile")

which will conditionally add the dependency on assets:precompile if it is defined.

How to use current_user method in rake task rails

I found a solution where i can create a background job for the required fields in another table and this solve my problem as i have user_id in the table.



Related Topics



Leave a reply



Submit