4

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

Mike Belyakov
  • 1,355
  • 1
  • 13
  • 24

5 Answers5

3

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.

Matouš Borák
  • 15,606
  • 1
  • 42
  • 53
1

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) ===============================
idlefingers
  • 31,659
  • 5
  • 82
  • 68
  • 1
    Thank you, but for `rake db:migrate` initializers starts before Rakefile, so, i'll get an empty line. Your idea is very similar with that line of code, which I wrote in update part of the question. And both snippets run too late. So it will work with own rake task, except pre-defined. – Mike Belyakov Apr 11 '16 at 14:06
  • @MikeBelyakov Are you sure you're following the example correctly? I generated a new rails app with the code exactly as I have above here, and when you run `rake db:migrate`, you get the puts line followed by the rake task output. See here - https://github.com/idlefingers/rake-task-name-example – idlefingers Apr 11 '16 at 15:11
0

Did you include the => :environment in your task? E.g.

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

Otherwise the initializers wont run when you run a rake task

Andy
  • 5,287
  • 2
  • 41
  • 45
0

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
Abdoo Dev
  • 1,216
  • 11
  • 16
  • Thank you, but this idea gives the same result,as i added in update section of the question. As test you can try it with `rake db:migrate` and at this moment. Also, this method will work only with `rake db:migrate`, and not work with `bundle exec rake db:migrate` – Mike Belyakov Apr 09 '16 at 15:24
0

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
Community
  • 1
  • 1
Ronna
  • 1,150
  • 14
  • 24
  • Thank you. But this answer clone the idea of my post update. Also, I need to fetch rake task name inside rails app, not inside another rake task. And I can not edit basic rake task- it is built in in rails, `db:migrate` as example. In this case question is still open. – Mike Belyakov Apr 11 '16 at 10:57
  • I tested it within the app and it works for me. I will edit to explain exactly what I did. – Ronna Apr 11 '16 at 12:25
  • It is really good method for custom tasks, and I see that it will work. But it odes not works with built in rake tasks, I try it out with `db:migrate` few minutes ago. May be it will work on other env, I do no know. I use rails 4.2.6, and try rake 10.5 and 11.1.2 – Mike Belyakov Apr 11 '16 at 13:00
  • It's true that it doesn't work for rake tasks that do not load the environment as explained by @ved, but it works for db:migrate as well. I just tested it by adding a call to the model `Something` inside a migration. Maybe you should add a code snippet of what it is exactly that you're doing. – Ronna Apr 11 '16 at 13:06