Class from string - ruby

Let's say I have a class named Klass, and a class called Klass2. Depending on the user's input, I'd like to decide whether I'll call "hello_world" on Klass, or Klass2:
class Klass
def self.hello_world
"Hello World from Klass1!"
end
end
class Klass2
def self.hello_world
"Hello World from Klass2!"
end
end
input = gets.strip
class_to_use = input
puts class_to_use.send :hello_world
The user inputs "Klass2" and the script should say:
Hello World from Klass2!
Obviously this code doesn't work, since I'm calling #hello_world on String, but I'd like to call #hello_world on Klass2.
How do I "convert" the string into a referrence to Klass2 (or whatever the user might input), or how could I else would I achieve this behavior?

puts Object.const_get(class_to_use).hello_world

puts eval(class_to_use).hello_world

If you have ActiveSupport loaded (e.g. in a Rails app) you can also use #constantize:
class_to_use.constantize.hello_world

Related

How to make callable attribute in Ruby

I do not know how to google it, but i know what i want.
i want to make something like this
class SchedulingManager
attr_accessor :on_start
def call
on_start
end
end
scheduling = SchedulingManager.new
scheduling.on_start do
puts "hello"
end
so i want my on_start here to be initiate by do block style. and save it so i can call in method call and print hello (or do whatever code in the block).
i do not know what the name of it, i also do not know how to google it.
Kindly need your help guys, thanks
I would do it like this and store the block in a variable.
class SchedulingManager
def on_start(&block)
#block = block
end
def call
#block&.call
end
end
scheduling = SchedulingManager.new
scheduling.on_start do
puts "hello"
end
scheduling.call
#=> hello

Why can't a class method have the same name as a non-class method?

I'm learning ruby, and noticed that I cannot create a class method called puts:
class Printer
def initialize(text="")
#text = text
end
def puts
puts #text
end
end
The error is:
`puts': wrong number of arguments (given 1, expected 0)
My expectation was that I could use the code like this:
p = Printer.new("hello")
p.puts
It's not just because puts is a built-in method, though. For instance, this code also gives a syntax error:
def my_puts(text)
puts text
end
class Printer
def initialize(text="")
#text = text
end
def my_puts
my_puts #name
end
end
tldr; within the scope of the instance, the puts resolves to self.puts (which then resolves to the locally defined method, and not Kernel#puts). This method overriding is a form of shadowing.
Ruby has an 'implicit self' which is the basis for this behavior and is also how the bare puts is resolved - it comes from Kernel, which is mixed into every object.
The Kernel module is included by class Object, so its methods [like Kernel#puts] are available in every Ruby object. These methods are called without a receiver and thus can be called in functional form [such as puts, except when they are overridden].
To call the original same-named method here, the super keyword can be used. However, this doesn't work in the case where X#another_method calls X#puts with arguments when it expects to be calling Kernel#puts. To address that case, see Calling method in parent class from subclass methods in Ruby (either use an alias or instance_method on the appropriate type).
class X
def puts
super "hello!"
end
end
X.new.puts
P.S. The second example should trivially fail, as my_puts clearly does not take any parameters, without any confusion of there being another "puts". Also, it's not a syntax error as it occurs at run-time after any language parsing.
To add to the previous answer (https://stackoverflow.com/a/62268877/13708583), one way to solve this is to create an alias of the original puts which you use in your new puts method.
class Printer
alias_method :original_puts, :puts
attr_reader :text
def initialize(text="")
#text = text
end
def puts
original_puts text
end
end
Printer.new("Hello World").puts
You might be confused from other (static) programming languages in which you can overwrite a method by creating different signatures.
For instance, this will only create one puts method in Ruby (in Java you would have two puts methods (disclaimer: not a Java expert).
def puts(value)
end
def puts
end
If you want to have another method with the same name but accepting different parameters, you need to use optional method parameters like this:
def value(value = "default value")
end

Ruby class printing name in another class

class NameData
def initialize
#name="Cleetus"
end
class Greetings
def hello()
puts "Hello #{#name}! How wonderful to see you today."
end
end
end
greet=Greetings.new
p greet.hello
Im a little bit of a beginner, but i'm trying to get my name to be used in the Greetings class so the string will print with my name inside. Any ideas?
It's common to put multiple classes inside the same module (sometimes called "namespacing") in Ruby, but it's unusual to have a class inside another class. Even if you do it's still a separate class and does not have access to instance variables in the "outer" class.
What you need to do is to make your #name value accessible outside your NameData object. The usual way to do this in Ruby is with an attribute reader:
class NameData
attr_reader :name
def initialize
#name = "Cleetus"
end
end
name_data = NameData.new
puts name_data.name
# => Cleetus
Once you've done that you need to tell your Greetings object about the NameData object, and how to use it. One way to do that is to pass the NameData object as an argument to hello:
class Greetings
def hello(name_data)
puts "Hello #{name_data.name}! How wonderful to see you today."
end
end
greet = Greetings.new
greet.hello(name_data)
# => Hello Cleetus! How wonderful to see you today.
Another way is to pass it to the Greetings constructor and save it in an instance variable:
class Greetings
def initialize(name_data)
#name_data = name_data
end
def hello
puts "Hello #{#name_data.name}! How wonderful to see you today."
end
end
greet = Greetings.new(name_data)
greet.hello
# => Hello Cleetus! How wonderful to see you today.
The #name variable is an instance variable and it can not be seen by methods within a different class. It can only be seen by methods within the same class. Try this instead:
class Greetings
def initialize
#name="Cleetus"
end
def hello()
puts "Hello #{#name}! How wonderful to see you today."
end
end
greet = Greetings.new.hello

Ruby - overwrite def method

How can I overwrite the def method? But it's strange, cause I don't know from where the def method is defined. It's not Module, not Object, not BasicObject (of Ruby 1.9). And def.class don't say nothing ;)
I would like to use something like:
sub_def hello
puts "Hello!"
super
end
def hello
puts "cruel world."
end
# ...and maybe it could print:
# => "Hello!"
# => "cruel world."
Many thanks, for any ideas.
Who told you def is a method? It's not. It's a keyword, like class, if, end, etc. So you cannot overwrite it, unless you want to write your own ruby interpreter.
You could use alias_method.
alias_method :orig_hello, :hello
def hello
puts "Hello!"
orig_hello
end
You can use blocks to do some similar things like this:
def hello
puts "Hello"
yield if block_given?
end
hello do
puts "cruel world"
end
As others have said, def isn't a method, it's a keyword. You can't "override" it. You can, however, define a method called "def" via Ruby metaprogramming magic:
define_method :def do
puts "this is a bad idea"
end
This still won't override the def keyword, but you can call your new method with method(:def).call.
So, there you (sort of) have it.
Note: I have no idea why you'd ever want to define a method called def. Don't do it.

Ruby design pattern: How to make an extensible factory class?

Ok, suppose I have Ruby program to read version control log files and do something with the data. (I don't, but the situation is analogous, and I have fun with these analogies). Let's suppose right now I want to support Bazaar and Git. Let's suppose the program will be executed with some kind of argument indicating which version control software is being used.
Given this, I want to make a LogFileReaderFactory which given the name of a version control program will return an appropriate log file reader (subclassed from a generic) to read the log file and spit out a canonical internal representation. So, of course, I can make BazaarLogFileReader and GitLogFileReader and hard-code them into the program, but I want it to be set up in such a way that adding support for a new version control program is as simple as plopping a new class file in the directory with the Bazaar and Git readers.
So, right now you can call "do-something-with-the-log --software git" and "do-something-with-the-log --software bazaar" because there are log readers for those. What I want is for it to be possible to simply add a SVNLogFileReader class and file to the same directory and automatically be able to call "do-something-with-the-log --software svn" without ANY changes to the rest of the program. (The files can of course be named with a specific pattern and globbed in the require call.)
I know this can be done in Ruby... I just don't how I should do it... or if I should do it at all.
You don't need a LogFileReaderFactory; just teach your LogFileReader class how to instantiate its subclasses:
class LogFileReader
def self.create type
case type
when :git
GitLogFileReader.new
when :bzr
BzrLogFileReader.new
else
raise "Bad log file type: #{type}"
end
end
end
class GitLogFileReader < LogFileReader
def display
puts "I'm a git log file reader!"
end
end
class BzrLogFileReader < LogFileReader
def display
puts "A bzr log file reader..."
end
end
As you can see, the superclass can act as its own factory. Now, how about automatic registration? Well, why don't we just keep a hash of our registered subclasses, and register each one when we define them:
class LogFileReader
##subclasses = { }
def self.create type
c = ##subclasses[type]
if c
c.new
else
raise "Bad log file type: #{type}"
end
end
def self.register_reader name
##subclasses[name] = self
end
end
class GitLogFileReader < LogFileReader
def display
puts "I'm a git log file reader!"
end
register_reader :git
end
class BzrLogFileReader < LogFileReader
def display
puts "A bzr log file reader..."
end
register_reader :bzr
end
LogFileReader.create(:git).display
LogFileReader.create(:bzr).display
class SvnLogFileReader < LogFileReader
def display
puts "Subersion reader, at your service."
end
register_reader :svn
end
LogFileReader.create(:svn).display
And there you have it. Just split that up into a few files, and require them appropriately.
You should read Peter Norvig's Design Patterns in Dynamic Languages if you're interested in this sort of thing. He demonstrates how many design patterns are actually working around restrictions or inadequacies in your programming language; and with a sufficiently powerful and flexible language, you don't really need a design pattern, you just implement what you want to do. He uses Dylan and Common Lisp for examples, but many of his points are relevant to Ruby as well.
You might also want to take a look at Why's Poignant Guide to Ruby, particularly chapters 5 and 6, though only if you can deal with surrealist technical writing.
edit: Riffing of off Jörg's answer now; I do like reducing repetition, and so not repeating the name of the version control system in both the class and the registration. Adding the following to my second example will allow you to write much simpler class definitions while still being pretty simple and easy to understand.
def log_file_reader name, superclass=LogFileReader, &block
Class.new(superclass, &block).register_reader(name)
end
log_file_reader :git do
def display
puts "I'm a git log file reader!"
end
end
log_file_reader :bzr do
def display
puts "A bzr log file reader..."
end
end
Of course, in production code, you may want to actually name those classes, by generating a constant definition based on the name passed in, for better error messages.
def log_file_reader name, superclass=LogFileReader, &block
c = Class.new(superclass, &block)
c.register_reader(name)
Object.const_set("#{name.to_s.capitalize}LogFileReader", c)
end
This is really just riffing off Brian Campbell's solution. If you like this, please upvote his answer, too: he did all the work.
#!/usr/bin/env ruby
class Object; def eigenclass; class << self; self end end end
module LogFileReader
class LogFileReaderNotFoundError < NameError; end
class << self
def create type
(self[type] ||= const_get("#{type.to_s.capitalize}LogFileReader")).new
rescue NameError => e
raise LogFileReaderNotFoundError, "Bad log file type: #{type}" if e.class == NameError && e.message =~ /[^: ]LogFileReader/
raise
end
def []=(type, klass)
#readers ||= {type => klass}
def []=(type, klass)
#readers[type] = klass
end
klass
end
def [](type)
#readers ||= {}
def [](type)
#readers[type]
end
nil
end
def included klass
self[klass.name[/[[:upper:]][[:lower:]]*/].downcase.to_sym] = klass if klass.is_a? Class
end
end
end
def LogFileReader type
Here, we create a global method (more like a procedure, actually) called LogFileReader, which is the same name as our module LogFileReader. This is legal in Ruby. The ambiguity is resolved like this: the module will always be preferred, except when it's obviously a method call, i.e. you either put parentheses at the end (Foo()) or pass an argument (Foo :bar).
This is a trick that is used in a few places in the stdlib, and also in Camping and other frameworks. Because things like include or extend aren't actually keywords, but ordinary methods that take ordinary parameters, you don't have to pass them an actual Module as an argument, you can also pass anything that evaluates to a Module. In fact, this even works for inheritance, it is perfectly legal to write class Foo < some_method_that_returns_a_class(:some, :params).
With this trick, you can make it look like you are inheriting from a generic class, even though Ruby doesn't have generics. It's used for example in the delegation library, where you do something like class MyFoo < SimpleDelegator(Foo), and what happens, is that the SimpleDelegator method dynamically creates and returns an anonymous subclass of the SimpleDelegator class, which delegates all method calls to an instance of the Foo class.
We use a similar trick here: we are going to dynamically create a Module, which, when it is mixed into a class, will automatically register that class with the LogFileReader registry.
LogFileReader.const_set type.to_s.capitalize, Module.new {
There's a lot going on in just this line. Let's start from the right: Module.new creates a new anonymous module. The block passed to it, becomes the body of the module – it's basically the same as using the module keyword.
Now, on to const_set. It's a method for setting a constant. So, it's the same as saying FOO = :bar, except that we can pass in the name of the constant as a parameter, instead of having to know it in advance. Since we are calling the method on the LogFileReader module, the constant will be defined inside that namespace, IOW it will be named LogFileReader::Something.
So, what is the name of the constant? Well, it's the type argument passed into the method, capitalized. So, when I pass in :cvs, the resulting constant will be LogFileParser::Cvs.
And what do we set the constant to? To our newly created anonymous module, which is now no longer anonymous!
All of this is really just a longwinded way of saying module LogFileReader::Cvs, except that we didn't know the "Cvs" part in advance, and thus couldn't have written it that way.
eigenclass.send :define_method, :included do |klass|
This is the body of our module. Here, we use define_method to dynamically define a method called included. And we don't actually define the method on the module itself, but on the module's eigenclass (via a small helper method that we defined above), which means that the method will not become an instance method, but rather a "static" method (in Java/.NET terms).
included is actually a special hook method, that gets called by the Ruby runtime, everytime a module gets included into a class, and the class gets passed in as an argument. So, our newly created module now has a hook method that will inform it whenever it gets included somewhere.
LogFileReader[type] = klass
And this is what our hook method does: it registers the class that gets passed into the hook method into the LogFileReader registry. And the key that it registers it under, is the type argument from the LogFileReader method way above, which, thanks to the magic of closures, is actually accessible inside the included method.
end
include LogFileReader
And last but not least, we include the LogFileReader module in the anonymous module. [Note: I forgot this line in the original example.]
}
end
class GitLogFileReader
def display
puts "I'm a git log file reader!"
end
end
class BzrFrobnicator
include LogFileReader
def display
puts "A bzr log file reader..."
end
end
LogFileReader.create(:git).display
LogFileReader.create(:bzr).display
class NameThatDoesntFitThePattern
include LogFileReader(:darcs)
def display
puts "Darcs reader, lazily evaluating your pure functions."
end
end
LogFileReader.create(:darcs).display
puts 'Here you can see, how the LogFileReader::Darcs module ended up in the inheritance chain:'
p LogFileReader.create(:darcs).class.ancestors
puts 'Here you can see, how all the lookups ended up getting cached in the registry:'
p LogFileReader.send :instance_variable_get, :#readers
puts 'And this is what happens, when you try instantiating a non-existent reader:'
LogFileReader.create(:gobbledigook)
This new expanded version allows three different ways of defining LogFileReaders:
All classes whose name matches the pattern <Name>LogFileReader will automatically be found and registered as a LogFileReader for :name (see: GitLogFileReader),
All classes that mix in the LogFileReader module and whose name matches the pattern <Name>Whatever will be registered for the :name handler (see: BzrFrobnicator) and
All classes that mix in the LogFileReader(:name) module, will be registered for the :name handler, regardless of their name (see: NameThatDoesntFitThePattern).
Please note that this is just a very contrived demonstration. It is, for example, definitely not thread-safe. It might also leak memory. Use with caution!
One more minor suggestion for Brian Cambell's answer -
In you can actually auto-register the subclasses with an inherited callback. I.e.
class LogFileReader
cattr_accessor :subclasses; self.subclasses = {}
def self.inherited(klass)
# turns SvnLogFileReader in to :svn
key = klass.to_s.gsub(Regexp.new(Regexp.new(self.to_s)),'').underscore.to_sym
# self in this context is always LogFileReader
self.subclasses[key] = klass
end
def self.create(type)
return self.subclasses[type.to_sym].new if self.subclasses[type.to_sym]
raise "No such type #{type}"
end
end
Now we have
class SvnLogFileReader < LogFileReader
def display
# do stuff here
end
end
With no need to register it
This should work too, without the need for registering class names
class LogFileReader
def self.create(name)
classified_name = name.to_s.split('_').collect!{ |w| w.capitalize }.join
Object.const_get(classified_name).new
end
end
class GitLogFileReader < LogFileReader
def display
puts "I'm a git log file reader!"
end
end
and now
LogFileReader.create(:git_log_file_reader).display
This is how I would make an extensible factory class.
module Factory
class Error < RuntimeError
end
class Base
##registry = {}
class << self
def inherited(klass)
type = klass.name.downcase.to_sym
##registry[type] = klass
end
def create(type, *args, **kwargs)
klass = ##registry[type]
return klass.new(*args, **kwargs) if klass
raise Factory::Error.new "#{type} is unknown"
end
end
end
end
class Animal < Factory::Base
attr_accessor :name
def initialize(name)
#name = name
end
def walk?
raise NotImplementedError
end
end
class Cat < Animal
def walk?; true; end
end
class Fish < Animal
def walk?; false; end
end
class Salmon < Fish
end
duck = Animal.create(:cat, "Garfield")
salmon = Animal.create(:salmon, "Alfredo")
pixou = Animal.create(:duck, "Pixou") # duck is unknown (Factory::Error)

Resources