Get a class by name in Ruby? - ruby

Having a string with the module and name of a class, like:
"Admin::MetaDatasController"
how do I get the actual class?
The following code works if there's no module:
Kernel.const_get("MetaDatasController")
but it breaks with the module:
ruby-1.8.7-p174 > Kernel.const_get("Admin::MetaDatasController")
NameError: wrong constant name Admin::MetaDatasController
from (irb):34:in `const_get'
from (irb):34
ruby-1.8.7-p174 >

If you want something simple that handles just your special case you can write
Object.const_get("Admin").const_get("MetaDatasController")
But if you want something more general, split the string on :: and resolve the names one after the other:
def class_from_string(str)
str.split('::').inject(Object) do |mod, class_name|
mod.const_get(class_name)
end
end
the_class = class_from_string("Admin::MetaDatasController")
On the first iteration Object is asked for the constant Admin and returns the Admin module or class, then on the second iteration that module or class is asked for the constant MetaDatasController, and returns that class. Since there are no more components that class is returned from the method (if there had been more components it would have iterated until it found the last).

ActiveSupport provides a method called constantize, which will do this. If you are on Rails, which I assume you are based on the name of your constant, then you already have ActiveSupport loaded.
require 'active_support/core_ext/string'
class Admin
class MetaDatasController
end
end
"Admin::MetaDatasController".constantize # => Admin::MetaDatasController
To see how the method is implemented, check out https://github.com/rails/rails/blob/85c2141fe3d7edb636a0b5e1d203f05c70db39dc/activesupport/lib/active_support/inflector/methods.rb#L230-L253

In Ruby 2.x, you can just do this:
Object.const_get('Admin::MetaDatasController')
=> Admin::MetaDatasController

i could be way off-base, but wouldn't eval return the class?
eval("Admin::MetaDatasController")
so eval("Admin::MetaDatasController").new would be the same as Admin::MetaDatasController.new

Related

ruby: Using a variable for module/class name when instantiating [duplicate]

Having a string with the module and name of a class, like:
"Admin::MetaDatasController"
how do I get the actual class?
The following code works if there's no module:
Kernel.const_get("MetaDatasController")
but it breaks with the module:
ruby-1.8.7-p174 > Kernel.const_get("Admin::MetaDatasController")
NameError: wrong constant name Admin::MetaDatasController
from (irb):34:in `const_get'
from (irb):34
ruby-1.8.7-p174 >
If you want something simple that handles just your special case you can write
Object.const_get("Admin").const_get("MetaDatasController")
But if you want something more general, split the string on :: and resolve the names one after the other:
def class_from_string(str)
str.split('::').inject(Object) do |mod, class_name|
mod.const_get(class_name)
end
end
the_class = class_from_string("Admin::MetaDatasController")
On the first iteration Object is asked for the constant Admin and returns the Admin module or class, then on the second iteration that module or class is asked for the constant MetaDatasController, and returns that class. Since there are no more components that class is returned from the method (if there had been more components it would have iterated until it found the last).
ActiveSupport provides a method called constantize, which will do this. If you are on Rails, which I assume you are based on the name of your constant, then you already have ActiveSupport loaded.
require 'active_support/core_ext/string'
class Admin
class MetaDatasController
end
end
"Admin::MetaDatasController".constantize # => Admin::MetaDatasController
To see how the method is implemented, check out https://github.com/rails/rails/blob/85c2141fe3d7edb636a0b5e1d203f05c70db39dc/activesupport/lib/active_support/inflector/methods.rb#L230-L253
In Ruby 2.x, you can just do this:
Object.const_get('Admin::MetaDatasController')
=> Admin::MetaDatasController
i could be way off-base, but wouldn't eval return the class?
eval("Admin::MetaDatasController")
so eval("Admin::MetaDatasController").new would be the same as Admin::MetaDatasController.new

undefined method error for :string

I have this code...
foo = opt_source.downcase.camelize
"#{foo}".new.start_check
this should call a class and the method start_check, but I am getting an undefined method error
undefined method `start_check' for "Abcd":String (Abcd is the class that foo represents)
Any suggestions on how what I am doing wrong?
You need to convert that string into a constant. Historically this was done with eval but this leads to security issues in your code -- never eval user-supplied strings.
The correct way to do this (in Rails) is via String#constantize:
foo = opt_source.downcase.camelize
foo.constantize.new.start_check
For ruby, use Kernel#const_get:
foo = opt_source.downcase.camelize
Kernel.const_get(foo).new.start_check
Don't forget to check for errors before calling your methods.
Just eval(foo).new.start_check
Create a class name from a plural table name like Rails does for table names to models.
http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-classify
camelize is not fit to convert plural string into active record class if you are calling method from active record
For example:
u = "batteries"
u.camelize #=> "Batteries"
u.classify #=> ""Battery"
Try
opt_source.classify.constantize.new.send(:start_check)

field vs method ruby on rails

I have this class:
class User
include Mongoid::Document
field :revenues, :type => Integer, :default => nil
attr_accessible :revenues
#now method
def revenues
return 1
end
end
Why in console I get 1 instead nil?
1.9.3-p125 :002 > u.revenues
=> 1
Which has priority, the method or the field? How can I created a method with the same features that a field?
The field macro is defined in Mongoid::Document. It is neither a syntatic feature from Ruby nor from Rails.
What's happening with your code is the following:
The field function creates for you some methods, one of them is called revenues.
When you create another method called revenues, you are in effect overwriting the previously defined method, therefore making it useless.
Short answer: I don't understand a zip about Mongoid, but chances are that your field still exists even after you defined oce again a method named revenues. The only drawback is that you cannot access it by calling myUser.revenues anymore.
Try to make a test: access your field with the notation some_user[:revenues] and see what happen :)
Best regards

subclassing from a Struct vs. using attr_accessible

In Ruby 1.8.6, I could write class PerformableMethod < Struct.new(:object, :method, :args)
Now in Ruby 1.9.3, that throws an error: superclass mismatch for class PerformableMethod
It works if I change the code to:
class PerformableMethod
attr_accessor :object, :method_name, :args
But why doesn't the struct work?
The class name is optional also in 1.9 and 2.0. The problem is this:
> Struct.new(:asdf, :qwer) == Struct.new(:asdf, :qwer)
=> false
Even if you provide a class name for your Struct:
> Struct.new("Zxcv", :asdf, :qwer) == Struct.new("Zxcv", :asdf, :qwer)
(irb):22: warning: redefining constant Struct::Zxcv
=> false
This means that if you have this in a file that you load or require:
class MyClass < Struct.new(:qwer, :asdf)
def some_method
puts "blah"
end
end
... then if you load it again -- maybe because you changed something and you want to try it without restarting irb, or maybe you are running Rails in dev mode and it reloads classes on each request -- then you get the exception:
TypeError: superclass mismatch for class MyClass
... because every time your class definition runs, it is declaring a brand new Struct as the superclass of MyClass. Providing a class name to Struct.new() does not help, as seen in the second code block; That just adds a warning about redefining a constant, and then opening the class fails anyway.
The only way to avoid the exception is to stash your Struct in a constant somewhere you control and make sure not to change that constant when the file is reloaded.
While typing out this question, my colleague sitting right next to me figured it out.
Struct now takes the class name as its first parameter.
So in Ruby 1.9.3 the following works:
class << Struct.new('PerformableMethod', :object, :method, :args)

Ruby: Include a dynamic module name

I have a situation in my Rails application where I need to include arbitrary modules depending on the current runtime state. The module provides custom application code that is only needed when certain conditions are true. Basically, I'm pulling the name of a company from the current context and using that as the filename for the module and its definition:
p = self.user.company.subdomain + ".rb"
if File.exists?(Rails.root + "lib/" + p)
include self.class.const_get(self.user.company.subdomain.capitalize.to_sym)
self.custom_add_url
end
My test module looks like this:
module Companyx
def custom_add_url
puts "Calling custom_add_url"
end
end
Now in the console, this actually works fine. I can pull a user and include the module like so:
[1] pry(main)> c = Card.find_by_personal_url("username")
[2] pry(main)> include c.class.const_get(c.user.company.subdomain.capitalize)=> Object
[3] pry(main)> c.custom_add_url
Calling custom_add_url
If I try to run the include line from my model, I get
NoMethodError: undefined method `include' for #<Card:0x007f91f9094fb0>
Can anyone suggest why the include statement would work on the console, but not in my model code?
I'm doing a similar thing. I found this answer useful:
How to convert a string to a constant in Ruby?
Turns out I was looking for the constantize method. This is the line I'm using in my code:
include "ModuleName::#{var.attr}".constantize
Edit:
So ack, I ran into various problems with actually using that line myself. Partially because I was trying to call it inside a method in a class. But since I'm only calling one method in the class (which calls/runs everything else) the final working version I have now is
"ModuleName::#{var.attr}".constantize.new.methodname
Obviously methodname is an instance method, so you could get rid of the new if yours is a class method.
Include is a method on a class.
If you want to call it inside a model, you need to execute the code in the context of its singleton class.
p = self.user.company.subdomain + ".rb"
if File.exists?(Rails.root + "lib/" + p)
myself = self
class_eval do
include self.const_get(myself.user.company.subdomain.capitalize.to_sym)
end
self.custom_add_url
EDIT:
class << self doesn't accept a block; class_eval does, hence it preserves the state of local variables. I've modified my solution to use it.

Resources