Get class by name in Ruby - ruby

I have two classes
class ClassOne
def do_something
[...]
end
end
class ClassTwo
def do_something
[...]
end
end
I am getting a class name (either ClassOne or ClassTwo) from the database and I want to call do_something in that class
so I have
class_name = "ClassOne"
and I want to call ClassOne.do_something or ClassTwo.do_something if class_name is equals to "ClassTwo".
I can't do it using a simple if condition, I have many classes and am checking if the class exists before calling..
Is there a way to do it?

For vanilla ruby:
Kernel.const_get('ClassOne').do_something
For Rails:
'ClassOne'.constantize.do_something

Although you can convert any arbitrary string to a class using constantize from ActiveSupport if available, this could cause exceptions if users can submit the string in question. It might be safer to use a case:
case (with_class)
when 'ClassOne', 'ClassTwo'
with_class.constantize.do_something
else
raise "Um, what are you doing?"
end
The same thing could be achieved with a Hash or Array defining valid classes and testing with either [] or include? accordingly.

eval("#{classname}.do_something")
Note: you have to change your code to def self.do_something, otherwise these are instance methods. It looks like this is your intention.

Related

Can I call a custom method on an object with this notation?

This is an example of what I am after:
def already_taken?
# Magic goes here...
end
"Charlotte".already_taken?
Would it be possible to construct a method in a way where I can call it directly on a String object, without having to modify the String class itself?
You could patch the String class with a custom module:
module MyStringPatch
def already_taken?
'yes'
end
end
String.include MyStringPatch
"Charlotte".already_taken?
If you want to add methods to any class (String in this case), without monkey-patching it, you should consider using Refinements.
module StringRefinements
refine String do
def already_taken?
puts "yes!"
end
end
end
# in another file...
using StringRefinements
"Charlotte".already_taken?
The already_taken? method will only be available in a scope that calls using StringRefinements and nowhere else.

Ruby beginner needs a hand

I am a beginner in ruby.
I've tried to run this code and it shows run time error.
What's wrong with this code?
class Calc
attr_accessor :val1, :val2
def initialize (val1,val2)
#val1=val1
#val2=val2
end
end
a=Calc.new(2,3)
a.add_two_numbers(3)
def add_two_numbers(v3)
return #val1+#val2+v3
end
The method add_two_numbers is not defined on the class Calc, however you are using it as if it is. This is the problem.
I would presume you got a NoMethodError.
Update: As pointed out in the comments, in actuallity, the method is defined on the Object class by default, which then gets auto inherited into all classes, but as private. This actually means that you will be getting the error saying that a private method is being called. The fix remains the same, since the overarching problem is a confusion in how to define classes and their methods.
The fix would be to define the method on the class, by putting it in the class body.
class Calc
attr_accessor :val1, :val2
def initialize (val1,val2)
#val1=val1
#val2=val2
end
def add_two_numbers(v3)
return #val1+#val2+v3
end
end
So you are defining a method outside of a class (which is want we don't want)
def add_two_numbers(v3)
return #val1+#val2+v3
end
You always want to make sure that you keep your classes and you logic as two separate entities in terms of organization. By that I mean:
Your classes in one file (calc.rb):
**class Calc
attr_accessor :val1, :val2
def initialize (val1,val2)
#val1=val1
#val2=val2
end
def add_two_numbers(v3)
return #val1+#val2+v3
end
end**
And your logic to access calc.rb in another file. Use require relative to access the logic inside the class file:
require_relative"/calc.rb"
a=Calc.new(2,3)
a.add_two_numbers(3)
Tip: When I was learning ruby the best way to keep them in two separate files for better organization.That way you know you don't have a method somewhere outside of the class. This would avoid your "no method error"

How to Convert an ActiveRecord::Relation by default

I am working on a project that requires very specific methods to be called on an ActiveRecord::Relation object. These methods cannot extend ActiveRecord::Relation because the Class has it's own initialize method to determine if the object should be collected. I have tried a dozen ways to handle this but because of method chaining in AR I have been unable to accomplish this. Currently I have monkey patched ActiveRecord::Relation with a method that converts it like so:
module ActiveRecord
class Relation
def to_claim_set
exec_queries unless loaded?
ClaimSet.new(#records)
end
end
end
Firstly I am sure this is an improper way to handle it. Secondly this causes me to have to call #to_claim_set constantly throughout the application.
I am hoping someone can assist on making this the default return after all method chaining is complete.
What I am hoping for is something like
Claim.policy_number('913006')
#=> ClaimSetObjectHere
But I need it to support chaining like AR does so that things like
Claim.policy_number('913006').by_program('Base')
#=> ClaimSetObjectHere
I also tried to patch the #where method inside Claim which works great unless I use a scope or I chain methods in which case it complains that ClaimSet does not define default_scoped?.
Any insight would be greatly appreciated. As for "Why would you want to do this" like I said I am constantly calling this method throughout the application and I need the methods defined in ClaimSet for this to function properly.
Note: This is being used outside of rails
Okay so what I ended up doing was imposing a wrapper for ActiveRecord::Relation like so:(removed specific business logic for brevity)
class ClaimSet
def initialize(object)
process_target(object)
# ...
end
# ...
def respond_to_missing?(method_name,include_private=false)
#target.respond_to?(method_name)
end
def method_missing(method_name, *args, &block)
if #target.respond_to?(method_name)
ClaimSet.new(#target.send(method_name,*args,&block))
else
super
end
end
private
def process_target(object)
#target = object if object.is_a?(ActiveRecord::Relation)
#target = object.target if object.is_a?(ClaimSet)
end
end
Then in the Claim class.
class Claim < ActiveRecord::Base
class << self
def where(*args)
ClaimSet.new(super(*args))
end
def localized_scope(name,proc)
scope_proc = lambda do |*args|
ClaimSet.new(proc.call(*args))
end
singleton_class.send(:define_method,name,scope_proc)
end
end
end
Then I define all my scopes as localized_scope e.g.
localized_scope :policy_number, ->(policy_number){where(policy_number: policy_number)}
Now it always returns a ClaimSet in place of an ActiveRecord::Relation for #where and #localized_scope and supports method chaining through #method_missing. It also removes the monkey patch on ActiveRecord::Relation.
If you have any other suggestions please let me know as I would be glad to entertain other ideas but this works for the time being.

How to reopen a class using just its instance variable in Ruby?

I want to create a function that messes around with the classes passed to it. What would be the most idiomatic way to reopen those classes to add functionality? Here's what I mean:
def class_messer(target_object)
#would like to reopen class here with something like:
class target_object.class
#add methods
end
end
Obviously that syntax doesn't work. I could get the target_object's class and eval some strings, but that feels gross. Is there a more idiomatic way to do this?
I think you're looking for class_eval. If you want to reopen a class and you do not have the constant as is, but a reference, you can call class_eval on it and pass a block (or even a string) of code to be evaluated in that classes context.
def class_messer(target_object)
# assuming that target_object is an instance of desired class
target_object.class.class_eval do
#add methods
end
end
target_object.class.class_exec do
# add methods
end
Maybe it's not correct to change class, for example, if you had an instance of Array class and changed its class, then this change could impact on other instances of Array class. So instead use singleton class of instance and the definition of method will be:
target_object.send(:define_method, :new_method) do
#...
end
or
class << target_object
def new_method
#...
end
end
You can also do this:
class << target_object.class
end

Test to validate ruby subclasses implement strategy method

I'm implementing a simple strategy pattern (for the first time in ruby) and I want to write a test to make sure that every subclass implements the crucial strategy method. So, I have something like this:
class SearchTools::MusicSearcher
def find_artists
raise 'Abstract method called'
end
end
class SearchTools::LastFMSearcher < MusicSearcher
def find_artists(search_phrase)
# get artists from lastfm's restful api
end
end
class SearchTools::DatabaseSearcher < MusicSearcher
def find_artists(search_phrase)
# look in database for artists
end
end
class SearchTools::Search
def initialize(searcher)
#searcher = searcher
end
def find_artists(search_phrase)
#searcher.find_artists(search_phrase)
end
end
I'm currently using rspec, factory_girl and shoulda-matchers for my testing. Anyone know how I achieve this?
Cheers!
P.S. I'm used to specifying a literal 'interface' with C#, so that's why I'm looking to see what I can use in ruby to enforce a common interface for each strategy...
I would expect it to be something like,
it "should respond to find_artists method" do
o = SearchTools::LastFMSearcher.new
o.respond_to?(:find_artists).should be_true
end

Resources