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

3
  • I don't know why this question is downvoted. It looks like a genuine question to me. Commented Apr 9, 2016 at 8:17
  • 1
    the downvotes may be from the old post, before it was undeleted and rewritten into an completely different question. Commented Apr 9, 2016 at 8:35
  • You are absolutely right Commented Apr 9, 2016 at 8:36

5 Answers 5

3
+50

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you! After cutting out spring it gotta work. You gave full and clear explanation. Great!
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) =============================== 

2 Comments

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.
@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 - github.com/idlefingers/rake-task-name-example
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

1 Comment

I need primary it for rake db:migrate. Initializers have been run and i can debug it with pry.
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 

1 Comment

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
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 

4 Comments

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.
I tested it within the app and it works for me. I will edit to explain exactly what I did.
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
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.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.