In my Rails 3.1 app (with Ruby 1.9), I have a Deployer1 class that is in a worker subdirectory below the model directory
I am trying to load/instantiate this class dynamically with this code:
clazz = item.deployer_class # deployer_class is the class name in a string
deployer_class = Object.const_get clazz
deployer = deployer_class.new
If I dont use namespaces, eg something global like this:
class Deployer1
end
Then it works fine (deployer_class="Deployer1") - it can load the class and create the object.
If I try and put it into a module to namespace it a bit, like this:
module Worker
class Deployer1
end
end
It doesnt work (deployer_class="Worker::Deployer1") - gives an error about missing constant, which I believe means it cannot find the class.
I can access the class generally in my Rails code in a static way (Worker::Deployer1.new) - so Rails is configured correctly to load this, perhaps I am loading it the wrong way...
EDIT:
So, as per Vlad's answer, the solution I went for is:
deployer_class.constantize.new
Thanks
Chris
try using constantize instead:
module Wtf
class Damm
end
end
#=> nil
'Wtf::Damm'.constantize
#=> Wtf::Damm
Object.const_get 'Wtf::Damm'
#=> Wtf::Damm
Object does not know a constant named Worker::Deployer1, which is why Object.const_get 'Worker::Deployer1' doesn't work. Object only knows a constant Worker. What does work is Worker.const_get 'Deployer1'.
Vlad Khomisch's answer works, because if you look at the implementation of constantize, this is exactly what it does: it splits the string on '::' and recursively const_get's.
Related
Greetings to everyone.
This question is the continuation of a previous one :
Is it possible to extend a class by using a string as a module ? - Ruby 2.7.1
So here it is. I am currently doing some tests with Ruby 2.7.1 on my FreeBSD 12.1 workstation. My objective is to find a way to load all the script within a directory. These scripts are modules with predictable names. For instance, if I got a script named mymodule.rb, it will contain a module named : Mymodule and a method : mymodule. So I can make a list of all scripts within a directory by using an Array. I can use that list to load/require all my script files easily. And with the help of some .sub, .chop or .capitalize, I can can extract what I need from each index of my array. But the result of this operation is always a String. The problem is that I cannot execute a method with a String. Previously I was having problem with extending my main class with module name from a String, but answers were given and solved this little issue. Here is my main class :
load "mymodule.rb"
class Myclass
def mymethod
var1 = "Mymodule"
extend self.class.const_get(var1)
var2 = "mymodule"
#I need something here to call the method from the module.
#puts #varmod
end
end
a = Myclass.new
a.mymethod
and here is my module :
module Mymodule
def mymodule
#varmod = "TEST"
end
end
So, I would like to know if there is a way to execute the method within Mymodule the same fashion we did with "extend self.class.const_get(var1)".
Thanks in advance for your responses !
In order to send a message with a name that is not statically known at design time, you can use the Object#public_send method:
public_send(var2)
It is not necessary to use Object#send in this case, since your methods are not private.
I think it is the send method your are looking for. The following should work:
send(var2)
I was thinking wouldn't it be cool to have a print method defined in the Ruby Object class? Consider the following:
class Object
def print
puts self.to_s
end
end
23.times &:print
Is there any issue in having something like this? Seems like a good feature to have. It also appears easy to read.
There's already Object#inspect defined. Plus, there's already Kernel#print defined as private method in Object class and every class that inherits from it.
This method already exists in the Ruby standard library. However, it has a different name: display.
23.times &:display
# 012345678910111213141516171819202122
As you can see, it does not write a newline after the object's string representation; it is ill-suited for object inspection.
The main issue with adding methods to Object is that they become universal and may clash with similarly named methods in other libraries or in your project.
There are already multiple simple ways to output data or convert to string form in Ruby core, so the risk of a clash (on a very useful method name) likely outweighs any benefits from nicer syntax even in your own code.
If you have a smaller set of classes in your own project, where you feel this would be a useful feature to have, then this is an ideal use case for mix-ins.
Define a module:
module CanPrintSelf
def print
puts self.to_s
end
end
And include it in any class you want to have the feature:
class MyClass
include CanPrintSelf
end
my_object = MyClass.new
my_object.print
So you can have this feature if you like it, and you don't need to modify Object.
I'm referencing a custom class from within a model named Plac
Model is defined in models/plac.rb like this:
class Model < ActiveRecord::Base
def notify_owner
notifier = BatchNotify.getInstance
end
end
BatchNotify is defined in lib/modules/batch_notify.rb like so:
class BatchNotify
def self.getInstance
env = Rails.env
if(env == "test")
return TestBatchNotify.new
else
BatchNotify.new
end
end
end
I have also added the modules directory to autoload_path:
config.autoload_paths += %W(#{config.root}/lib/modules)
The weird thing is that when notify_owner() works great from the rails console.
However, when I start the web server with rails server and try to trigger notify_owner by using the app in the browser, I get the following error:
uninitialized constant Plac::BatchNotify
First, why is the behavior different in console vs web server?
Second, why does it still not recognize the Batch notify constant?
By the way, I've also tried defining BatchNotify within a module and referencing it as Module::BatchNotify with no luck...
There are a couple of ways to get the BatchNotify class loaded properly.
Add a config/initializers/00_requires.rb file with the following code:
require "#{Rails.root}/lib/modules/batch_notify.rb"
Or, require models/placebo.rb in the Model class:
require "#{Rails.root}/lib/modules/batch_notify.rb"
A couple of other comments on this code:
Rails already uses the term models, so Model is not a good class name in Rails.
The file naming convention is that the file name should correspond with the model name. So, the models/placebo.rb should be renamed to models/model.rb to follow convention.
BatchNotify is a class so I don't think you should put it a directory name modules.
I want to build an index for different objects in my Rails project and would like to add a 'count_occurences' method that I can call on String objects.
I saw I could do something like
class String
def self.count_occurences
do_something_here
end
end
What's the exact way to define this method, and where to put the code in my Rails project?
Thanks
You can define a new class in your application at lib/ext/string.rb and put this content in it:
class String
def to_magic
"magic"
end
end
To load this class, you will need to require it in your config/application.rb file or in an initializer. If you had many of these extensions, an initializer is better! The way to load it is simple:
require 'ext/string'
The to_magic method will then be available on instances of the String class inside your application / console, i.e.:
>> "not magic".to_magic
=> "magic"
No plugins necessary.
I know this is an old thread, but it doesn't seem as if the accepted solution works in Rails 4+ (at least not for me). Putting the extension rb file in to config/initializers worked.
Alternatively, you can add /lib to the Rails autoloader (in config/application.rb, in the Application class:
config.autoload_paths += %W(#{config.root}/lib)
require 'ext/string'
See this:
http://brettu.com/rails-ruby-tips-203-load-lib-files-in-rails-4/
When you want to extend some core class then you usually want to create a plugin (it is handy when need this code in another application). Here you can find a guide how to create a plugin http://guides.rubyonrails.org/plugins.html and point #3 show you how to extend String class: http://guides.rubyonrails.org/plugins.html#extending-core-classes
I have some rb files, all with the same structure:
class RandomName < FooBar
end
The randomname is a random class name which changes in each rb file but all inherits from Foobar.
how i can load all randomclass from there rb files?
I think there are 2 parts to the solution:
How to dynamically instantiate a class
a. Using String#constantize from ActiveSupport
klass = "SomeNamespace::SomeClassName".constantize
klass.new
b. Use Module#const_get (which doesn't handle namespaces)
klass = const_get(:SomeClassName)
klass.new
How to detect a class name
A convention followed widely in ruby is to name the file after the class that it contains, so random_name.rb would contain the RandomName class. If you follow this convention, then you could do something like:
Dir["/path/to/directory/*.rb"].each do |file|
require file
file_name = File.basename(file.path, '.rb')
# using ActiveSupport for camelcase and constantize
file_name.camelcase.constantize.new
end
I think you should explain what you are trying to accomplish. The approach you are taking seems unconventional and there may be a much more effective way of reaching your goal without doing all this loading of files and dynamic instantiation of classes with random names.
Remember, just because ruby lets you do something, it doesn't mean it's a good idea to actually do it!
you can define a method called inherited in the FooBar class. look here
class FooBar
def self.inherited(subclass)
puts "New subclass: #{subclass}"
end
end
Every time a subclass is created, you will get it in the callback. Then you can do whatever you want with all those subclasses.
I have a similar requirement, passing a class name in as a string. One trick with require is that it doesn't have to be at the start, so I prefer to only load the class I need.
I used eval because it doesn't have any Rails dependencies (I'm writing pure Ruby code here).
The following relies on convention (that the Class is in a file of the same name), but if you do know the class and file, this approach has the advantage of not requiring every file in a directory and only dynamically loading the one you need at the time you need it.
klass = "classname"
begin
# Load the file containing the class from same directory I'm executing in
require_relative klass # Or pass a local directory like "lib/#{klass}"
# Use eval to convert that string to a Constant (also capitalize it first)
k = eval(klass.capitalize).new
rescue
# Do something if the convention fails and class cannot be instantiated.
end
k.foo # Go ahead and start doing things with your new class.