ruby: Using a variable for module/class name when instantiating [duplicate] - 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

What does IRB use to determine how to represent a returned instance?

Given a Ruby class:
class Foo
def initialize(options={})
#sensitive = options.delete :sensitive
end
end
If I create an instance of that class in IRB, I get to see instance vars and memory address.
irb(main):002:0> Foo.new(sensitive: 'foo')
=> #<Foo:0x007fe766134a98 #sensitive="foo">
If I create an instance of AWS::S3, I don't:
irb(main):003:0> require 'aws-sdk'
=> true
irb(main):004:0> AWS::S3.new(access_key_id: 'aki', secret_access_key: 'sak')
=> <AWS::S3>
Note that AWS::S3 is not a singleton (at least not in the sense of explicitly including the Singleton module).
Is there anything I can do to tell IRB not to output instance vars and/or memory address?
(I've already tried .to_s but I get a string containing the memory address for instances of both classes without any instance vars.)
If you start IRB irb --noecho, it will suppress all IRB inspections. But I think this is not your question.
IRB use #inpect method. Read the line from the Documentation :
Returns a string containing a human-readable representation of obj. By default, show the class name and the list of the instance variables and their values (by calling inspect on each of them). User defined classes should override this method to make better representation of obj. When overriding this method, it should return a string whose encoding is compatible with the default external encoding.
Example :
class Foo
def initialize
#x = 10
end
# customized inspection
def inspect
"0x%7x" % self.object_id.to_s
end
end
foo = Foo.new
foo # => 0x118e27c
Note : I used String#% method inside my customized #inspect method.
The standard method being used to render human-readable debugging output (not just in IRb but in general, e.g. on Rails error pages etc.) is #inspect. Depending on which extensions you loaded, your command line options or whether you are using Pry instead of IRb, it may also look for a #pretty_inspect first.

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

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.

Get a class by name in 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

Resources