简体   繁体   中英

Get rake task name in initializer

I want to know, if the app instance is started by rake , so I tried out catching rake task name in initializers . For example, if I run rake db:migrate I want get db:migrate or something like this. I tried this;

[7] pry(main)> $0
=> "spring app    | bilet18 | started 3 secs ago | development mode"
[8] pry(main)> ARGV
=> []
[9] pry(main)> Rake.application.top_level_tasks
=> []

but everything is empty.

What can I do that? please help.

UPDATE

if add in Rakefile line like

ENV["RAKE_CURRENT_TASKS"] = Rake.application.top_level_tasks.join(' ')

then in future you'll be able to catch it in. but this solution is not good for me, I need to catch rake task name earlier

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 (eg 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.

Did you include the => :environment in your task? Eg

task :sometask => :environment do
    ...
end

Otherwise the initializers wont run when you run a rake task

There is two ways to achieve what you want,

1 .Not using spring , just run:

spring binstub --remove --all

and then run your rake task.

2 .Using spring:

create a spring.rb file config/spring.rb and use after_fork

Spring.after_fork do
  # run your code here
  # you have access to ARGV
  binding.pry
end

I believe this answers your needs. Task name should include also the namespace.

config/initializers/rake.rb

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  

models/something.rb:

class Something < ActiveRecord::Base
   puts Rake.application.current_task
end

lib/tasks/some_task.rake:

require 'rake'
namespace :ns do
  task :some_task => :environment do
    Something.all.to_a
  end
end

console:

rake ns:some_task
ns:some_task

also works:

bundle exec rake ns:some_task
ns:some_task

You could redefine tasks using Rake::Task#enhance to create a variable before invoking the original task, but that seems a bit messy and you'll have to worry about what to do with tasks like :environment and :default .

I think the easiest way is probably to just extend the Rake::Task#invoke method to add a global variable which you could then access in initializers, or from wherever else you want.

Here's a really quick an dirty example which you could do in the Rakefile :

module RakeTaskName
  def invoke(*args)
    $rake_task_name = name
    super *args
  end
end

Rake::Task.send :prepend, RakeTaskName

Then, in an initializer, you can do:

if defined? $rake_task_name
  puts "Running from rake task: #{$rake_task_name}"
end

Edit

Here's a new rails project with this code & 1 migration. The output looks like this:

Running from rake task: db:migrate
== 20160411150810 CreateFoo: migrating ========================================
-- create_table(:foos)
   -> 0.0010s
== 20160411150810 CreateFoo: migrated (0.0011s) ===============================

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM