I create a dynamic class using the Class.new method. But sometimes I call the method with a parameter to create an inherited class - sometimes without.
Option 1:
newclass = Class.new do
...
end
Option 2:
newClass = Class.new(p) do
...
end
The body of the new class is identical. But I cannot call Class.new(p) with undefined p. So I have to create an if statement and then either call Class.new with parameter or without which means I have the duplicate the code for creating the class which is not ideal since every change in my code I have to make twice. Any way how I can get around this?
You can just abstract the Class.new call and leave the block in one place. Something like this:
def create_me_a_class(superklass = Object, &block)
Class.new(superklass, &block)
end
newclass = create_me_a_class(p) do
def my_method
# whatever
end
end
I'm fairly new to ruby and wanted to know if it's possible to export a method/method call.
Basically, I have this:
class B
def initialize
#bptimer = BP_Timer.new("MyName", method(:sayhello))
end
def sayhello()
msgbox("hello")
end
end
class BP_Timer
def initialize(name = nil, method = nil)
#name = name
#time = 0
#repeats = 0
#start_from = 0
#method = method
#active = false
end
def start()
#active = true
$timers.push(self)
end
end
(I removed all the rather uninteresting parts of the code)
Basically, I now have a "BP_Timer" object with the name and method that will later be pushed into a $timers array which I want to save to a file and later on load from when I restart the program. Is it possible to deconstruct the object in such a way that I can store and restore it?
This is not how we usually store/restore the state. The common approach would be to store a configuration and perform the whole initialization again.
That said, you are to store an array of initialization parameters, like ["MyName", :sayhello] in your case to _YAML,JSON`, or any other text format of your choice:
config.yml
bp_timers:
- "MyName": :sayhello
And then on program start you do:
config = YAML.load_file("config.yml")
config["bp_timers"].each do |k, v|
BP_Timer.new(*[k, method(v)]).start
end
Answering your initial question: yes, this is possible to some extent with Marshal class.
I want my method add_directory to be able to work with one or two arguments. As shown in the two different versions of the method. I know that Ruby doesn't allow method overloading and as a person coming from C++ I haven't gotten the hang of it. How do I redesign my method so that I achieve the results I want? Thanks in advance.
Module RFS
class Directory
attr_accessor :content
def initialize
#content = {}
end
def add_file (name,file)
#content[name]=file
end
def add_directory (name,subdirectory)
#content[name] = subdirectory
end
def add_directory (name)
#content[name] = RFS::Directory.new
end
end
end
There are several possible solutions in your case. The simplest one, probably the most correct, is to declare subdirectory as an optional parameter.
def add_directory(name, subdirectory = nil)
if subdirectory
#content[name] = subdirectory
else
#content[name] = RFS::Directory.new
end
end
In Ruby you can simulate the overloading using the * (splat) operator and inspecting the arguments in the method. For instance
def add_directory(*names)
# here *names is an array of 0 or more items
# you can inspect the number of items and their type
# and branch the method call accordingly
end
But this solution is unnecessarily complicated in your case.
oh man, Simone got to it while I was answering
I was going to suggest
def add_directory(name, subdirectory=nil)
#content[name] = subdirectory.present? ? subdirectory : RFS::Directory.new
end
In response to Simone's splat method.
def add_directory(*args)
#content[args[0]] = args.length > 1 ? args[1] : RFS::Directory.new
end
I don't personally like this because you have a required name param and then a optional subdirectory param. You wouldn't know if they put name first or second, or if they put name at all. it would be better to require name as the first param and catch the rest of the params in the splat
def add_directory(name, *args)
#content[name] = args.present? ? args[0] : RFS::Directory.new
end
which turns out to be similar to the first method which just sets subdirectories to a default of nil
I'm new to Ruby and I'm trying to change some of my code to have OO design using our current framework. When converting my utility file to classes I encountered the following problem:
There are two functions *create_output_file* and *write_to* are framework methods (don't belong to any classes) that I cannot touch (They specifies where to write to the framework).
And there is a class A I wrote which needs to use the *write_to* to log the time and return status of the foo().
<!-- language: lang-rb -->
def create_output_file(params)
#output_file = params["SS_output_file"]
#hand = File.open(#output_file, "a")
write_to "New Run - Shell Cmd\n\n"
return true
end
def write_to(message, newline = true)
return if message.nil?
sep = newline ? "\n" : ""
#hand.print(message + sep)
print(message + sep)
end
def this_is_dum
print("This is to show class A have access to global methods.\n")
end
class A
def foo()
# Do something that needs the status of instance A
this_is_dum()
write_to("This is the current status!")
end
end
# Main routine
params = {}
params["SS_output_file"] = "./debugging_log"
create_output_file(params) #sets the #hand file handle
A.new.foo
These are the outputs I'm getting:
New Run - Shell Cmd:
This is to show class A have access to global methods.
D:/data/weshi/workspace/RubyBRPM2/scripts/Random.rb:12:in `write_to':
private method `print' called for nil:NilClass (NoMethodError)
from D:/data/weshi/workspace/RubyBRPM2/scripts/Random.rb:18:in
`foo' from
D:/data/weshi/workspace/RubyBRPM2/scripts/Random.rb:26:in
`'
After digging a bit I found that #hand was not accessed during the foo() scope.
I'm not sure how I'll be able to access the #hand variable inside class A and what it means without assigning to a class. But I do need the write_to function to work in accordance to the whole framework. Any suggestions or instructions are welcomed and appreciated.
You can give #hand to your A instance in the constructor like that:
def create_output_file(params)
#output_file = params["SS_output_file"]
#hand = File.open(#output_file, "a")
write_to "New Run - Shell Cmd\n\n"
return true
end
def write_to(message, newline = true)
return if message.nil?
sep = newline ? "\n" : ""
#hand.print(message + sep)
print(message + sep)
end
def this_is_dum
print("This is to show class A have access to global methods.\n")
end
class A
def initialize(hand)
#hand = hand
end
def foo()
# Do something that needs the status of instance A
this_is_dum()
write_to("This is the current status!")
end
end
# Main routine
params = {}
params["SS_output_file"] = "./debugging_log"
create_output_file(params) #sets the #hand file handle
A.new(#hand).foo
The output is:
New Run - Shell Cmd
This is to show class A have access to global methods.
This is the current status!
Otherwise I found that which may be a little bit more complicated.
I need to load a YAML file (I'm experimenting with SettingsLogic) and I'd like the instance to load the YAML with the same name as it. Briefly:
class MySettings < SettingsLogic
source "whatever_the_instance_is_called.yml"
# Do some other stuff here
end
basic_config = MySettings.new # loads & parses basic_config.yml
advanced_cfg = MySettings.new # loads & parses advanced_cfg.yml
...and so on...
The reason for this I don't yet know what configuration files I'll have to load, and typing:
my_config = MySettings.new("my_config.yml")
or
my_config = MySettings.new(:MyConfig)
just seems to be repeating myself.
I took a look around both Google and Stackoverflow, and the closest I came to an answer is either "Get Instance Name" or a discussion about how meaningless an instance name is! (I'm probably getting the query wrong, however.)
I have tried instance#class, and instance#name; I also tried instance#_id2ref(self).
What am I missing?!
Thanks in advance!
O.K., so with local variable assignment, there are snags, such as that assignment might occur slightly later than local variable symbol addition to the local variable list. But here is my module ConstMagicErsatz that I used to implement something similar to out-of-the box Ruby constant magic:
a = Class.new
a.name #=> nil - anonymous
ABC = a # constant magic at work
a.name #=> "ABC"
The advantage here is that you don't have to write ABC = Class.new( name: "ABC" ), name gets assigned 'magically'. This also works with Struct class:
Koko = Struct.new
Koko.name #=> "Koko"
but with no other classes. So here goes my ConstMagicErsatz that allows you to do
class MySettings < SettingsLogic
include ConstMagicErsatz
end
ABC = MySettings.new
ABC.name #=> "ABC"
As well as
a = MySettings.new name: "ABC"
a.name #=> "ABC"
Here it goes:
module ConstMagicErsatz
def self.included receiver
receiver.class_variable_set :##instances, Hash.new
receiver.class_variable_set :##nameless_instances, Array.new
receiver.extend ConstMagicClassMethods
end
# The receiver class will obtain #name pseudo getter method.
def name
self.class.const_magic
name_string = self.class.instances[ self ].to_s
name_string.nil? ? nil : name_string.demodulize
end
# The receiver class will obtain #name setter method
def name= ɴ
self.class.const_magic
self.class.instances[ self ] = ɴ.to_s
end
module ConstMagicClassMethods
# #new method will consume either:
# 1. any parameter named :name or :ɴ from among the named parameters,
# or,
# 2. the first parameter from among the ordered parameters,
# and invoke #new of the receiver class with the remaining arguments.
def new( *args, &block )
oo = args.extract_options!
# consume :name named argument if it was supplied
ɴς = if oo[:name] then oo.delete( :name ).to_s
elsif oo[:ɴ] then oo.delete( :ɴ ).to_s
else nil end
# but do not consume the first ordered argument
# and call #new method of the receiver class with the remaining args:
instance = super *args, oo, &block
# having obtained the instance, attach the name to it
instances.merge!( instance => ɴς )
return instance
end
# The method will search the namespace for constants to which the objects
# of the receiver class, that are so far nameless, are assigned, and name
# them by the first such constant found. The method returns the number of
# remaining nameless instances.
def const_magic
self.nameless_instances =
class_variable_get( :##instances ).select{ |key, val| val.null? }.keys
return 0 if nameless_instances.size == 0
catch :no_nameless_instances do search_namespace_and_subspaces Object end
return nameless_instances.size
end # def const_magic
# ##instances getter and setter for the target class
def instances; const_magic; class_variable_get :##instances end
def instances= val; class_variable_set :##instances, val end
# ##nameless_instances getter for the target class
def nameless_instances; class_variable_get :##nameless_instances end
def nameless_instances= val; class_variable_set :##nameless_instances, val end
private
# Checks all the constants in some module's namespace, recursivy
def search_namespace_and_subspaces( ɱodule, occupied = [] )
occupied << ɱodule.object_id # mark the module "occupied"
# Get all the constants of ɱodule namespace (in reverse - more effic.)
const_symbols = ɱodule.constants( false ).reverse
# check contents of these constant for wanted objects
const_symbols.each do |sym|
# puts "#{ɱodule}::#{sym}" # DEBUG
# get the constant contents
obj = ɱodule.const_get( sym ) rescue nil
# is it a wanted object?
if nameless_instances.map( &:object_id ).include? obj.object_id then
class_variable_get( :##instances )[ obj ] = ɱodule.name + "::#{sym}"
nameless_instances.delete obj
# and stop working in case there are no more unnamed instances
throw :no_nameless_instances if nameless_instances.empty?
end
end
# and recursively descend into the subspaces
const_symbols.each do |sym|
obj = ɱodule.const_get sym rescue nil # get the const value
search_namespace_and_subspaces( obj, occupied ) unless
occupied.include? obj.object_id if obj.kind_of? Module
end
end
end # module ConstMagicClassMethods
end # module ConstMagicErsatz
The above code implements automatic searching of whole Ruby namespace with the aim of finding which constant refers to the given instance, whenever #name method is called.
The only constraint using constants gives you, is that you have to capitalize it. Of course, what you want would be modifying the metaclass of the object after it is already born and assigned to a constant. Since, again, there is no hook, you have to finde the occasion to do this, such as when the new object is first used for its purpose. So, having
ABC = MySettings.new
and then, when the first use of your MySettings instance occurs, before doing anything else, to patch its metaclass:
class MySettings
def do_something_useful
# before doing it
instance_name = self.name
singleton_class.class_exec { source "#{instance_name}.yml" }
end
# do other useful things
end
Shouldn't you be able to do either
File.open(File.join(File.expand_path(File.dir_name(__FILE__)), foo.class), "r")
or
require foo.class
The first one need not be that complicated necessarily. But if I'm understanding you correctly, you can just use foo.class directly in a require or file load statement.
Adjust as necessary for YAML loading, but #class returns a plain old string.
Well if you have tons of variables to instantiate, I'd personally just create a Hash to hold them, it's cleaner this way. Now to instantiate all of this, you could do a loop other all your yaml files :
my_settings = {}
[:basic_config, :advanced_cfg, :some_yaml, :some_yaml2].each do |yaml_to_parse|
my_settings[yaml_to_parse] = MySettings.new(yaml_to_parse)
end
Make sure your initialize method in MySettings deals with the symbol you give it!
Then get your variables like this :
my_settings[:advanced_cfg]
Unfortunately, Ruby has no hooks for variable assignment, but this can be worked around. The strategy outline is as follows: First, you will need to get your MySettings.new method to eval code in the caller's binding. Then, you will find the list of local variable symbols in the caller's binding by calling local_variables method there. Afterwards, you will iterate over them to find which one refers to the instance returned by super call in your custom MySettings.new method. And you will pass its symbol to source method call.