Cannot find list of descendants using Rake - ruby-on-rails

Where are Rake tasks such as :environment defined? Is there something lighter than :environment which could give me access to the ActiveRecord subclasses, from a Rake task?
I am trying to get a list of models, in a Rake task. This is what I have:
namespace :mine do
namespace :ar do
desc "my custom"
task :show_sub => [:environment] do
puts "Total:#{ActiveRecord::Base.descendants}"
ActiveRecord::Base.descendants.each do |cls|
puts cls
end
end
end
end
However, all I ever get is [], though I have 3 models defined.
EDIT: I am using Rails 3.1.

The most lightweight way I can imagine to accomplish this is to write the rake task that iterates through all .rb files in the current directory, and sub-directories inside your project (in case you have them stashed somewhere weird), and simply outputs the number of files containing the string < ActiveRecord::Base (spaces being optional, of course).
One regex pattern that matches this is:
^class\s[a-zA-Z0-9_]+\s?<\s?ActiveRecord::Base
...so it only matches lines that include an explicit class declaration, that don't start with a hash # mark (i.e. so it's not a comment)

Related

Command to create a rake task with some folder

rails g task folder1/namespace1 task1
Above command will create the task1.rake inside lib/tasks/task1.rake
But, I need to keep my task1.rake inside lib/tasks/folder1/task1.rake
The rails g task generator does not work this way.
Here are the code for the TaskGenerator class
module Rails
module Generators
class TaskGenerator < NamedBase # :nodoc:
argument :actions, type: :array, default: [], banner: "action action"
def create_task_files
template 'task.rb', File.join('lib/tasks', "#{file_name}.rake")
end
end
end
end
As you see the lib/tasks path is hardcoded and you can not pass in options to alter the path.
I think this might be a great addition to the TaskGenerator class.
The answer to your question is that you have to make the folders manually.

Call methods from a task in Rake files

It is possible to call a method which is in the same rake file as the task? In the code below you can see that I have the method call get_search_url which will be assigned to url.
namespace :populate do
desc "ETC"
task :db => :environment do
Report.where(link: "").each do |word|
url = get_search_url(word.name)
doc = Nokogiri::HTML(open(url))
word.update_columns(link: link)
end
end
def get_search_url(keyword)
return "URL/keyword"
end
end
Yes it is absolutely possible. Just define those methods after the namespace ;)
namespace :populate do
desc "ETC"
task :db => :environment do
Report.where(link: "").each do |word|
url = get_search_url(word.name)
doc = Nokogiri::HTML(open(url))
word.update_columns(link: link)
end
end
end
def get_search_url(keyword)
return "URL/keyword"
end
However if you define the same method in two different rake tasks, they will both be globally loaded when running rake, and one of the method definitions will be redefined / overwritten by the other!
Better practice is to define methods in a separate module and then include that module in your rake file.
See https://kevinjalbert.com/defined_methods-in-rake-tasks-you-re-gonna-have-a-bad-time/ (Solution #3)

Calling a namespaced class with Rails constantize inflector

I have a class that needs to be initialized but it's namespaced like this:
SomeThing::MyClass.new()
But I'm calling it from the args in a rake task, so it comes in as a string:
task :blah, [:my_class_name] => :environment do |t, args|
class_name = args[:my_class_name].camelize.constantize
puts class_name
end
So obviously if I call the rake task like this:
rake blah[my_class]
My task returns:
MyClass # <= Actual ruby object
But how can I get it to run from within a namespace chained before another method, like this:
SomeThing::MyClass.new()
From a string provided as the input?
You can make your life easier by just using the string of the class name and doing
Something.const_get(args[:my_class_name]).new
Here's a simplified version (normal IRB, no Rails):
module Something ; end
class Something::MyClass ; end
my_class_name = "MyClass"
Something.const_get(my_class_name).new
#=> #<Something::MyClass:0x007fa8c4122dd8>

Method namespace clashing when running rake tasks in Rails

Using Rails 2.3.10
If my lib/tasks looks like this
lib/tasks
- a.rake
- b.rake
a.rake looks like this:
namespace :a do
desc "Task A"
task(:a=>:environment)do
msg('I AM TASK A')
end
def msg(msg)
puts "MSG SENT FROM Task A: #{msg}"
end
end
b.rake looks like this
namespace :b do
desc "Task B"
task(:b=>:environment)do
msg('I AM TASK B')
end
def msg(msg)
puts "MSG SENT FROM Task B: #{msg}"
end
end
Then when I run task a
rake a RAILS_ENV=sandbox
The output is
"MSG SENT FROM Task B: I AM TASK A"
So the msg() helper method defined in a.rake does not get called. Rather that defined in b.rake gets called. (What's more, if I have a c.rake - then it's msg helper method gets called when I run task a.
Is this method namespace clashing known behavior?
I would have thought the namespacing would have prevented this.
Thanks
What you observe is that methods in rake files namespaces redefine previously defined methods with the same name. The reason for this is that Rake namespaces are very different than Ruby namespaces (classes or modules), in fact they only serve as a namespace for the names of the tasks defined in them, but nothing else. Thus, task a becomes task a:a if placed in the a namespace but other code outside the tasks shares the same global namespace.
This fact, together with the fact that Rake loads all tasks before running the given task, explains why the method gets redefined.
TL;DR: Solutions / hints for the name clashes
You cannot expect that two methods with the same name (or any other code) placed inside separate namespaces but outside tasks will work properly. Nevertheless, here are a couple of hints to solve such situation:
Place the methods inside the tasks. If both msg methods were defined inside the a:a and b:b tasks, then both rake tasks would run properly and would display the expected messages.
If you need to use the code from the rake's namespace in multiple rake tasks, extract the methods / code to a real Ruby namespace, such as two modules, and include the code in the tasks that need it. Consider this rewrite of the sample rakes:
# lib/tasks/a.rake:
module HelperMethodsA
def msg(msg)
puts "MSG SENT FROM Task A: #{msg}"
end
end
namespace :a do
desc "Task A"
task(:a => :environment) do
include HelperMethodsA
msg('I AM TASK A')
end
end
# lib/tasks/b.rake:
module HelperMethodsB
def msg(msg)
puts "MSG SENT FROM Task B: #{msg}"
end
end
namespace :b do
desc "Task B"
task(:b => :environment) do
include HelperMethodsB
msg('I AM TASK B')
end
end
Because the two modules have different names and because they are included in the respective tasks, both rake tasks will again run as expected.
Now, let's prove the above claims with the help of the source code...
Proof that Rake loads all tasks first and why it does so
This one's easy. In the main Rakefile you'll always find the following line:
Rails.application.load_tasks
This method eventually calls the following code from the Rails engine:
def run_tasks_blocks(*) #:nodoc:
super
paths["lib/tasks"].existent.sort.each { |ext| load(ext) }
end
Thus, it searches the lib/tasks directories for all rake files and loads them one after each other, in a sorted order. That's why the b.rake file will be loaded after a.rake and whatever is inside it will potentially redefine the code from a.rake and all previously loaded rake files.
Rake has to load all rake files simply because the rake namespace names do not have to be the same as rake filenames, so the rake filename cannot be inferred from the task / namespace name.
Proof that rake's namespaces don't constitute a real Ruby-like namespace
Upon loading the rake files, the Rake DSL statements get executed, and also the namespace method. The method takes the block of code defined inside it and executes it (using yield) in the context of the Rake.application object which is a singleton object of the Rake::Application class that is shared among all rake tasks. There is no dynamic module / class created for the namespace, it's just executed in the main object context.
# this reuses the shared Rake.application object and executes the namespace code inside it
def namespace(name=nil, &block)
# ...
Rake.application.in_namespace(name, &block)
end
# the namespace block is yielded in the context of Rake.application shared object
def in_namespace(name)
# ...
#scope = Scope.new(name, #scope)
ns = NameSpace.new(self, #scope)
yield(ns)
# ...
end
See the relevant sources here and here.
Rake tasks DO constitute ruby namespaces
With Rake tasks themselves, the situation is different, though. For each task, a separate object of the Rake::Task class (or of a similar class) is created and the task code is run inside this object's context. The creation of the object is done in the intern method in the task manager:
def intern(task_class, task_name)
#tasks[task_name.to_s] ||= task_class.new(task_name, self)
end
A quote form the Rake's author
Finally, all this is confirmed by this interesting discussion on github that deals with a very similar and related issue, and from which we can quote Jim Weirich, the original author of Rake:
Since namespaces don't introduce real method scopes, the only real possibility for a scope is the DSL module.
...
Perhaps, someday, the Rake namespaces will become full class scoped entities with a place to hang lazy let definitions, but we are not there yet.
Use namespace like the following:
namespace :rake_a do
desc "Task A"
task(:a=>:environment)do
msg('I AM TASK A')
end
def msg(msg)
puts "MSG SENT FROM Task A: #{msg}"
end
end
The rake namespace is only intended for rake tasks.
See the Rake documentations:
The NameSpace class will lookup task names in the the scope defined by a namespace command.
You can create a module along with your rake namespace to cover this problem:
module A do
module_functions
def msg(msg)
puts "MSG SENT FROM Task A: #{msg}"
end
end
namespace :a do
desc "Task A"
task(:a=>:environment)do
A.msg('I AM TASK A')
end
end

Scope of rakes in a rails project?

I have a number of parsers I run with rakes in a project I'm working on. When using a method name that already exists in another rake, and because they both use the same environment, I get a conflict.
Is there a way to limit the scope of rake files within their namespace? I thought that was the whole point of the namespace?
Example:
namespace :test do
task :test_task => :environment do
...
end
def test_method(argument)
...
end
end
namespace :other_test do
task :test_task => :environment do
...
end
def test_method(argument, argument2)
...
end
end
In this case, when running rake test:test_task I'll receive an invalid amount of arguments error. On the other hand, if I define the method within the task itself, I have to keep the method at the top of the rake file in order. This gets kind of confusing and ugly.
Is that just a necessary evil?
Thanks!
I see the namespaces for rake tasks as serving the same purpose as directories in a file system: they're about organization rather than encapsulation. That's why database tasks are in db:, Rails tasks in rails:, etc.
Rake namespaces are not classes so you need to ask yourself what class you're adding test_method to when you define it within a Rake namespace. The answer is Object. So, when you hit your second task, Object already has a test_method method that takes one parameter and Ruby rightly complains.
The best solution is to make your Rake tasks very thin (just like controllers) and put any supporting methods (such as test_method) off in some library file somewhere sensible. A Rake task should usually just do a bit of set up and then call a library method to do the real work (i.e. the same general layout as a controller).
Executive summary: put all the real work and heavy lifting somewhere in your library files and make your Rake tasks thin wrappers for your libraries. This should make your problem go away through proper code organization.
module Anonymous
namespace :test do
# brabra
end
end
self.class.send(:remove_const, :Anonymous)
module Anonymous
namespace :another_test do
# brabra
end
end
self.class.send(:remove_const, :Anonymous)
Module.new do end block needs you self:: before any definition. To avoid this, you need to name the module and remove it.
I've figured out a way to namespace the methods in Rake tasks so same-named methods don't collide.
Wrap each outer namespace in a uniquely-named module.
extend Rake::DSL
Change your methods to class methods (with self.).
I've tested this, and it still allows one rake task to invoke or depend on another rake task that is in a different module.
Example:
module Test
extend Rake::DSL
namespace :test do
task :test_task => :environment do
...
end
def self.test_method(argument)
...
end
end
end
module OtherTest
extend Rake::DSL
namespace :other_test do
task :test_task => :environment do
...
end
def self.test_method(argument, argument2)
...
end
end
end

Resources