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.
Related
In Ruby, say I have a class called Song. I would like to know how correcty to write the initialize so that it supports 2 different types of. For example:
song_full = Song.new(fromSomeCloudStorage)
song_preview = Song.new(fromLocalStorage)
Then say I have a Song class, where I always want to assign the #time_stamp, but then depending on whether there is cloud_storage or not, assign #cloud_store_spec
def initialize(cloud_storage = nil, time_stamp = nil, local_storage = nil)
#time_stamp = time_stamp || (Time.now.to_f * 1000).to_i.to_s
#cloud_store_spec = cloud_storage
end
I'm thinking of using nil as I have done, however would the code know which is cloud_storage and which is local_storage. Is it actually possible?
Any help appreciated?
First off, use keyword arguments to allow you to pass the relevant data to the initializer (up to you if you want to include time_stamp as a keyword or regular arg):
def initialize(cloud_storage: nil, local_storage: nil, time_stamp: nil)
#time_stamp = time_stamp || (Time.now.to_f * 1000).to_i.to_s
#cloud_store_spec = cloud_storage
end
This will make things a lot clearer when calling the class, allowing:
song_full = Song.new(cloud_storage: fromSomeCloudStorage)
song_preview = Song.new(local_storage: fromLocalStorage)
In terms of how your code will know whether the code is from cloud or local, if you mean the class's instances, you can just check for the presence of #cloud_store_spec, something like:
def cloud_storage?
#cloud_store_spec.present?
end
Then, from anywhere else in your code, you can call:
song_full = Song.new(cloud_storage: fromSomeCloudStorage)
song_full.cloud_storage? # => true
song_preview = Song.new(local_storage: fromLocalStorage)
song_preview.cloud_storage? # => false
Hope that helps and I'm reading you right :) Let me know how you get on or if you've any questions.
Update for Ruby 1.9.3
As keyword arguments were introduced in Ruby 2.0, for 1.9.3 you can use an options hash:
def initialize(options = {}) # again, timestamp can be a separate arg if you'd prefer
#time_stamp = options[:time_stamp] || (Time.now.to_f * 1000).to_i.to_s
#cloud_store_spec = options[:cloud_storage]
end
The rest of the code will remain the same.
If CloudStorage or LocalStorage can be considered as classes:
class LocalStorage
def initialize; p 'LocalStorage class initialized'; end
end
class CloudStorage
def initialize; p 'CloudStorage class initialized'; end
def play; p "...playing song"; end
end
Maybe you could consider to hard code a Hash, (let apart time_stamp):
class Song
attr_reader :store_spec
def initialize(kind = nil)
storage_kind = {cloud: CloudStorage, preview: LocalStorage}
#store_spec = storage_kind[kind].new
end
def storage_kind
#store_spec.class
end
def play
#store_spec.play
end
end
So you can call for example:
song = Song.new(:cloud) #=> "CloudStorage class initialized"
song.storage_kind #=> CloudStorage
song.play #=> "...playing song"
I'm making a game with a Board class Cell class. The Board class needs to be initialized with a unique instance variable for each Cell. I can hardcode it so that it works, but it seems inelegant and doesn't allow the size of the board to be chosen by the user at runtime. Here's what I have:
class Board
def initialize
#cell_1 = Cell.new(1)
#cell_2 = Cell.new(2)
#cell_3 = Cell.new(3)
#cell_4 = Cell.new(4)
#cell_5 = Cell.new(5)
#cell_6 = Cell.new(6)
#cell_7 = Cell.new(7)
#cell_8 = Cell.new(8)
#cell_9 = Cell.new(0)
#cells = [#cell_1, #cell_2, #cell_3,
#cell_4, #cell_5, #cell_6,
#cell_7, #cell_8, #cell_9]
end
end
I think I could use a loop to create a hash with unique key names pointing to unique Cell objects, but I don't know how I could make unique instance variables with a loop.
If you don't need to create each instance variables (#cell_1, #cell_2, ...), you can use Enumerable#map:
#cells = [*1..8, 0].map { |i| Cell.new(i) }
If you really need to refer every instance variable by name you can do something like this.
class Board
def initialize
#cells = (1..9).to_a.map { |i| Cell.new(i) }
end
def method_missing(method, *args, &block)
if method =~ /^cell_[1-9][0-9]*$/
index = method[/\d+/].to_i
#cells[index-1]
else
super
end
end
end
In this way you can call:
board = Board.new
board.cell_1 #=> first cell
Of course I'd use the solution proposed by #falsetru.
I have a class that lazily loads data from a database into an instance variable, they are events in an array in numeric order. The class has several methods that analyse this array, here is an example of how I use it.
class Foo
def initialize
#a = [1,2,3,4,5] # data from database
end
def analyse
#a.reduce(:+)
end
end
d = Foo.new
result = d.analyse
I wanted to be able to apply these methods to the data after a very basic filter (eg: <= 3) and I imagined being able to call it like so:
d.at(3).analyse
and that the at method only affected the instance variable for the chained analyse call. i.e.
d = Foo.new # data loaded into instance var [1,2,3,4,5]
d.analyse # 15
d.at(3).analyse # 6
d.analyse # 15
I'm not sure how I can do this without re-creating a completely new object within the at call and this feels inefficient. I have a work around which would change how I call the at method - not the end of the world but I wondered if what I want is feasible whilst remaining efficient.
I'm not sure how I can do this without re-creating a completely new object within the at call and this feels inefficient.
I don't think creating a new object is too expensive. You could return a new Foo from at, initialized with a subset of the original data. You'd have another Foo instance and another Array instance, but the array would contain the very same objects:
class Foo
def initialize(a = nil)
#a = a || [1,2,3,4,5] # use a or fetch data from database
end
def analyse
#a.reduce(:+)
end
def at(max)
Foo.new(#a.take_while { |x| x <= max })
end
end
Example:
d = Foo.new
d.analyse #=> 15
d.at(3).analyse #=> 6
d.analyse #=> 15
You can prepare another instance variable which is set by at, overrides #a when defined, and is reset by analyse.
class Foo
def initialize
#a = [1,2,3,4,5]
end
def at i
#b = #a.select{|e| e <= i}
self
end
def array
if instance_variable_defined?(:#b)
#b.tap{remove_instance_variable(:#b)}
else
#a
end
end
def analyse
array.reduce(:+)
end
end
This question has been asked before: Read and write YAML files without destroying anchors and aliases?
I was wondering how to solve that problem with many anchors and aliases?
thanks
The problem here is that anchors and aliases in Yaml are a serialization detail, and so aren’t part of the data after it’s been parsed, so the original anchor name isn’t known when writing the data back out to Yaml. In order to keep the anchor names when round tripping you need to store them somewhere when parsing so that they are available later when serializing. In Ruby any object can have instance variables associated with it, so an easy way to achieve this would be to store the anchor name in an instance variable of the objet in question.
Continuing from the example in the earlier question, for hashes we can change our redifined revive_hash method so that if the hash is an anchor then as well as recording the anchor name in the #st variable so later alises can be recognised, we add the it as an instance variable on the hash.
class ToRubyNoMerge < Psych::Visitors::ToRuby
def revive_hash hash, o
if o.anchor
#st[o.anchor] = hash
hash.instance_variable_set "#_yaml_anchor_name", o.anchor
end
o.children.each_slice(2) { |k,v|
key = accept(k)
hash[key] = accept(v)
}
hash
end
end
Note that this only affects yaml mappings that are anchors. If you want to have other types to keep their anchor name you’ll need to look at psych/visitors/to_ruby.rb and make sure the name is added in all cases. Most types can be included by overriding register but there are a couple of others; search for #st.
Now that the hash has the desired anchor name associated with it, you need to make Psych use it instead of the object id when serializing it. This can be done by subclassing YAMLTree. When YAMLTree processes an object, it first checks to see if that object has been seen already, and emits an alias for it if it has. For any new objects, it records that it has seen the object in case it needs to create an alias later. The object_id is used as the key in this, so you need to override those two methods to check for the instance variable, and use that instead if it exists:
class MyYAMLTree < Psych::Visitors::YAMLTree
# check to see if this object has been seen before
def accept target
if anchor_name = target.instance_variable_get('#_yaml_anchor_name')
if #st.key? anchor_name
oid = anchor_name
node = #st[oid]
anchor = oid.to_s
node.anchor = anchor
return #emitter.alias anchor
end
end
# accept is a pretty big method, call super to avoid copying
# it all here. super will handle the cases when it's an object
# that's been seen but doesn't have '#_yaml_anchor_name' set
super
end
# record object for future, using '#_yaml_anchor_name' rather
# than object_id if it exists
def register target, yaml_obj
anchor_name = target.instance_variable_get('#_yaml_anchor_name') || target.object_id
#st[anchor_name] = yaml_obj
yaml_obj
end
end
Now you can use it like this (unlike the previous question, you don’t need to create a custom emitter in this case):
builder = MyYAMLTree.new
builder << data
tree = builder.tree
puts tree.yaml # returns a string
# alternativelty write direct to file:
File.open('a_file.yml', 'r+') do |f|
tree.yaml f
end
here's a slightly modified version for up to newer versions of the psych gem. before it gave me the following error:
NoMethodError - undefined method `[]=' for #<Psych::Visitors::YAMLTree::Registrar:0x007fa0db6ba4d0>
the register method moved into a subclass of YAMLTree, so this works now with respect to everything what matt says in his answer:
class ToRubyNoMerge < Psych::Visitors::ToRuby
def revive_hash hash, o
if o.anchor
#st[o.anchor] = hash
hash.instance_variable_set "#_yaml_anchor_name", o.anchor
end
o.children.each_slice(2) { |k,v|
key = accept(k)
hash[key] = accept(v)
}
hash
end
end
class MyYAMLTree < Psych::Visitors::YAMLTree
class Registrar
# record object for future, using '#_yaml_anchor_name' rather
# than object_id if it exists
def register target, node
anchor_name = target.instance_variable_get('#_yaml_anchor_name') || target.object_id
#obj_to_node[anchor_name] = node
end
end
# check to see if this object has been seen before
def accept target
if anchor_name = target.instance_variable_get('#_yaml_anchor_name')
if #st.key? anchor_name
oid = anchor_name
node = #st[oid]
anchor = oid.to_s
node.anchor = anchor
return #emitter.alias anchor
end
end
# accept is a pretty big method, call super to avoid copying
# it all here. super will handle the cases when it's an object
# that's been seen but doesn't have '#_yaml_anchor_name' set
super
end
end
I had to further modify the code that #markus posted to work with Psych v2.0.17.
Here's what I ended up with. I hope it helps someone else save quite a bit of time. :-)
class ToRubyNoMerge < Psych::Visitors::ToRuby
def revive_hash hash, o
if o.anchor
#st[o.anchor] = hash
hash.instance_variable_set "#_yaml_anchor_name", o.anchor
end
o.children.each_slice(2) do |k,v|
key = accept(k)
hash[key] = accept(v)
end
hash
end
end
class Psych::Visitors::YAMLTree::Registrar
# record object for future, using '#_yaml_anchor_name' rather
# than object_id if it exists
def register target, node
#targets << target
#obj_to_node[_anchor_name(target)] = node
end
def key? target
#obj_to_node.key? _anchor_name(target)
rescue NoMethodError
false
end
def node_for target
#obj_to_node[_anchor_name(target)]
end
private
def _anchor_name(target)
target.instance_variable_get('#_yaml_anchor_name') || target.object_id
end
end
class MyYAMLTree < Psych::Visitors::YAMLTree
# check to see if this object has been seen before
def accept target
if anchor_name = target.instance_variable_get('#_yaml_anchor_name')
if #st.key? target
node = #st.node_for target
node.anchor = anchor_name
return #emitter.alias anchor_name
end
end
# accept is a pretty big method, call super to avoid copying
# it all here. super will handle the cases when it's an object
# that's been seen but doesn't have '#_yaml_anchor_name' set
super
end
def visit_String o
if o == '<<'
style = Psych::Nodes::Scalar::PLAIN
tag = 'tag:yaml.org,2002:str'
plain = true
quote = false
return #emitter.scalar o, nil, tag, plain, quote, style
end
# visit_String is a pretty big method, call super to avoid copying it all
# here. super will handle the cases when it's a string other than '<<'
super
end
end
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.