Calling a namespaced class with Rails constantize inflector - ruby-on-rails

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>

Related

How to get at instance variable in rake task for testing in rspec 3.4?

So I have a rake task:
task :some_task do
# some stuff
#myvar = 2
SomeModel.my_method
end
I have the rspec test setup such that I can tell if SomeModel calls my method, but how do I check if instance variable #myvar is set to 2? I tried moving it outside the task block but to no avail. I'm using RSpec 3.4 with Rails 4.2.x and Ruby 2.2.x.
In SomeModel add an attr_accessor for the #myvar instance variable.
Eg:
class SomeModel
attr_accessor :myvar
def my_method
#myvar = 2
end
end
Now you can access it
task :some_task do
# some stuff
# #myvar = 2
SomeModel.myvar = 3
SomeModel.my_method
assert_match SomeModel.myvar, 2
end

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)

app/utils classes not found for rake task

I have a method I have added on Date that looks like this:
class Date
def days_different(date)
Float(self-date)
end
end
This works great in the rest of my application, but I also have a rake task in lib/tasks/database.rake that uses this where this is not being loaded despite having the => :environment dependency on the task (greatly simplified test version shown below).
task :test_days_different => :environment do
date = 2.days.ago
puts date.days_different(DateTime.now)
end
Strangely, rake:test loads the helper function when it loads its environment so I'm completely baffled as to why this isn't loading in rake db
Any ideas?
You did not mention where you put the extended Date class but I think you have to require 'date' in the rake file.
This code snippet is not working without the require statement:
require 'date'
class Date
def days_different(date)
Float(self-date)
end
end
date = Date.new(2001,2,1)
puts date.days_different(DateTime.now)

Cannot find list of descendants using Rake

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)

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

Resources