Marshaling and undefined attributes/classes - ruby

I am using ruby marshaling to send data between two clients. Each client has a set of class definitions which they will use to help load the marshaled data. The definitions are stored in an external ruby file which they can load anytime they want (but usually when they start up)
A simple use case would be
Client A marshal dumps the data and sends it to client B
Client B marshal loads the data and then writes it out to a file
However, sometimes one client is sending data that contains objects that isn't defined in the other client's definitions, in which case the other client should update its definitions accordingly.
It might be a new instance variable that should be added to the definition of class xyz or it might be a new class completely.
Marshal#Load currently just throws an exception when it runs into an undefined variable (eg: undefined class/method abc).
Is there a way for me to take this exception and update the definitions accordingly so that the client can happily read the data and write it out?
All classes will contain data that Marshal already knows how to encode/decode, such as strings, arrays, hashes, numbers, etc. There won't be any data that requires custom dump/load methods.

My solution would be to automatically create the class (and constant hierarchy, i.e. Foo::Bar::Baz) and make the class autorespond to attribute access attempts.
class AutoObject
def method_missing(*args,&b)
if args.size == 1
name = args[0]
if instance_variable_defined? "##{name}"
self.class.send :attr_accessor, name
send(*args)
else
super
end
elsif args.size == 2 && args[0].to_s[/=$/]
name = args[0].to_s[0...-1]
if instance_variable_defined? "##{name}"
self.class.send :attr_accessor, name
send(*args)
else
super
end
end
end
end
def Marshal.auto_load(data)
Marshal.load(data)
rescue ArgumentError => e
classname = e.message[%r(^undefined class/module (.+)$), 1]
raise e unless classname
classname.split("::").inject(Object) do |outer, inner|
if !outer.const_defined? inner
outer.const_set inner, Class.new(AutoObject)
else
outer.const_get inner
end
end
retry
end
This could easily be extended to log all classes created, and even to determine what instance variables they might have. Which could then aid you in updating the files, perhaps programatically.

Related

I need a class to call a class containing it as a property for a number of scenarios - how should this be implemented?

I'm fairly new to Ruby, coming from Objective-C and Swift.
I've hit upon a problem where I have an object containing another object as a property. The second object has a beginStream function, by which it streams data from a server, and when it gets new data then it yields, thus the first object can respond. It looks like this:
class StreamManager
def initialize
#streams = Array.new
end
def setup_user_stream(user_id)
stream = Stream.new(user_id)
#streams << stream
stream.begin_stream do |message|
puts "A message was received: #{message}"
end
end
end
class Stream
def initialize(user_id)
#user_id = user_id
end
def begin_stream
Thread.new do
# Begins stream
#client = Stream::Client.new(user_id)
#client.on_error do
# need to let StreamManager know about this
end
#client.on_message do |message|
yield message if block_given?
end
end
end
end
Now I have this on_error call that I'm getting from my stream client, and I need to let my StreamManager know about it. How would I go about doing this?
In Objective-C/Swift, I'd have a protocol called StreamDelegate, which the stream would have as a weak property, and then the StreamManager would set the Stream's delegate to be itself, and respond to the functions provided in the protocol. So then the Stream would call the delegate function #delegate?.streamDidReceiveError, and the stream manager would be set as the delegate, and have that function implemented, and it'd be called.
I've simplified this example - the Stream is an abstraction over the Stream::Client, which is from another library and also gives out a bunch of other messages. But now I'm writing this, I'm thinking perhaps their way of having those different blocks I'm yielding is the way to go. In which case, I'd need to understand how to implement that myself? Or perhaps that would be a poor way to design my class - I don't know?
There's a few different approaches here:
1) You could still use the delegate pattern you're used to in Cocoa. The only difference is you wouldn't have a formal protocol/interface for it. Your Stream would take a delegate/callback handler object which is any object that implements certain methods. You can make those methods optional by checking the object responds to them before calling them. Your StreamManager could implement this interface and pass itself in to the stream as a dependency.
2) You could define callbacks on your Stream class for errors and messages rather than passing a single block to the begin_stream method.
3) Keep your existing API by instead of yielding the message, encapsulate the message or error in a Result object and yield that instead. I think this might be my preferred option.
Sorry for the lack of code examples but I'm writing this on my iPhone.
The parent StreamManager can pass itself as a variable to the child Stream.
def initialize(stream_manager, user_id)
#stream_manager = stream_manager
#user_id = user_id
end
And the initializing it, in setup_user_stream:
stream = Stream.new(self, user_id)
If you want slightly more verbose code, you can use named keywords:
def initialize(:stream_manager, :user_id)
then:
Stream.new(stream_manager: self, user_id: user_id)

Custom to_json for nested complex objects in Ruby

I'm new to Ruby and having a little trouble json. I have inherited my classes with custom made JSONable class, as explained HERE in this answer. I have customized it according to my need, but I couldn't figure out how to make it work with custom nested (complex) objects, according to my requirement. I have following scenario.
First Class:
class Option < JSONable
def IncludeAll=(includeAll) #bool
#includeAll = includeAll
end
def IncludeAddress=(includeAddress) #bool
#includeAddress= includeAddress
end
......
Second Class:
class Search < JSONable
def CustomerId=(customerId)
#customerId = customerId
end
def identifier=(identifier)
#identifier = identifier
end
def Options=(options) #This is expected to be of Class Option, declared above
#options = options
end
Third Class:
class Request < JSONable
def DateTimeStamp=(dateTimeStamp)
#dateTimeStamp = dateTimeStamp
end
def SDKVersion=(sDKVersion)
#sDKVersion = sDKVersion
end
def RequestMessage=(requestMessage) #This is of type Search, declared above
#requestMessage = requestMessage
end
I call it as:
search = Search.new
searchOpts = Options.new
request = Request.new
search.identifier = identifier
searchOpts.IncludeAll = false
searchOpts.IncludeAddress = true
search.Options = searchOpts #setting nested level2 property here
//THE MOST OUTER CLASS OBJECT
request.SDKVersion = "xyz"
request.RequestMessage = search #setting nested level1
My ultimate goal is to send this request object to an API, after converting it to JSON. so i call to_json on request object as:
request.to_json
But here, suggested solution in that post (JSONable) fails in this case, as it can't convert the nested complex objects request.search and request.search.Options to Json.
(gives error: in 'to_json': wrong number of arguments (1 for 0) (ArgumentError)')
What I tried:
class JSONable
def to_json
hash = {}
self.instance_variables.each do |var|
#hash[var] = self.instance_variable_get var #tried to apply following check
if((self.instance_variable_get var).instance_of? Options ||((varVal).instance_of? Search))
varVal = self.instance_variable_get var
hash[var] = varVal.to_json #convert inner object to json
else
hash[var] = self.instance_variable_get var
end
end
hash.to_json
end
.....
This converts the nested model without any problem, but it messes up the 3rd level json. The result is as following:
{"DateTimeStamp":"121212","SDKVersion":"1.5","Culture":"en","RequestMessage":"{\"identifier\":\"851848913\",\"Options\":\"{\\\"IncludeAll\\\":true,\\\"IncludeAssociatedEntities\\\":true,\\\"IncludeAddress\\\":true,\\\"IncludePaymentInstructions\\\":true}\"}"}
And API doesn't respond. It seems as it messes up the boolean variables, which should be something like:
"SearchOption":"{\"IncludeAll\":true,\"IncludeAssociatedEntities\":true,\...
but it gives:
"SearchOption\":\"{\\\"IncludeAll\\\":true,\\\"IncludeAssociatedEntities\\\":true,\\\"Includ...
So the API logic can't cast it to corresponding bool objects anymore. JSON validator also fails to validate this result, i checked online
Questions:
How can I avoid this, and produce valid JSON in this case?
How can I apply generic check to in my JSONable class to check if the object is of some custom class / complex object.
(currently i have checked only for specific classes as:)
if((self.instance_variable_get var).instance_of? Options ||((varVal).instance_of? Search))
Other Info:
It works fine for all complex objects, having no nested objects
API is developed in .NET
I'm not using Rails, its a Ruby console app (I'm new to Ruby)
The answer you referred is dated “Dec 2010.” JSON library is included in ruby stdlib for years already and it perfectly converts Hash instances to json. That said, you just need to construct hashes out of your objects and then call JSON.dump on the resulting hash. I have no idea what JSONable is and you definitely do not need it. Introduce some base class, let’s call it Base:
class Base
def to_h
instance_variables.map do |iv|
value = instance_variable_get(:"##{iv}")
[
iv.to_s[1..-1], # name without leading `#`
case value
when Base then value.to_h # Base instance? convert deeply
when Array # Array? convert elements
value.map do |e|
e.respond_to?(:to_h) ? e.to_h : e
end
else value # seems to be non-convertable, put as is
end
]
end.to_h
end
end
Now just derive your classes from Base to make them respond to to_h, define all your instance variables as you did, and call:
require 'json'
JSON.dump request.to_h # request.to_h.to_json should work as well
The above should produce the nested JSON, hashes are happily converted to json by this library automagically.

How to instantiate an instance of a Ruby class when the class changes dynamically?

So I am trying to create a text game in ruby, and I have attempted to create a fight method that can deal with creating any object. I have a Monsters class in another file, and child classes such as Rogue and Vampire. I have managed to make this work by using a case statement that instantiates an object named m that is either Rogue or Vampire, and putting practically all the methods in the Monsters class so that they share the same method name, but is there a more efficient way of dealing with unknown objects?
My code:
def fight(monsterToFight)
case monsterToFight
when "Rogue"
m = ::Rogue.new
when "Vampire"
m = ::Vampire.new
else
puts "error 503"
end
... #more code
link to full repo: https://github.com/chaseWilliams/textGame
You can use const_get.
class_name = "Rogue"
rogue_class = Object.const_get(class_name) # => Rogue
my_rogue = rogue_class.new # => #<Rogue ...>
In your example, this would look like:
def fight(name_of_monster_to_fight)
monster_class = Object.const_get(name_of_monster_to_fight)
m = monster_class.new
# ... more code
rescue NameError # This is the error thrown when the class doesn't exist
puts "error 503"
end

Calling a Volt Framework Task method from another Task

I have a Volt Framework Task that checks and stores information on a directory, e.g.
class DirectoryHelperTask < Volt::Task
def list_contents()
contents = []
Dir.glob("/path/to/files").each do |f|
contents << f
end
return contents
end
end
I would like to call this from a different task, e.g.
class DirectoryRearrangerTask < Volt::Task
dir_contents = DirectoryHelperTask.list_contents()
end
The code above (DirectoryRearranger) throws an error, as does a promise call
DirectoryHelperTask.list_contents().then do |r|
dir_conents = r
end.fail do |e|
puts "Error: #{e}"
end
Could not find a way to call a task from another task in the Volt Framework documentation.
Thanks a lot!
From what I gather, tasks are meant to be run on the server side and then called on the client side, hence the use of the promise object. The promise object comes from OpalRb, so trying to call it from MRI won't work. If you have a "task" that will only be used on the server side, then it doesn't really fit with Volt's concept of a task.
Your first approach to the problem actually does work, except that DirectoryRearrangerTask can't inherit from Volt::Task.
directory_helper_task.rb
require_relative "directory_rearranger_task"
class DirectoryHelperTask < Volt::Task
def list_contents
contents = []
Dir.glob("*").each do |file|
contents << file
end
DirectoryRearrangerTask.rearrange(contents)
contents
end
end
directory_rearranger_task.rb
class DirectoryRearrangerTask
def self.rearrange(contents)
contents.reverse!
end
end
Here is a GitHub repo with my solution to this problem.
You can call tasks from the client or server, but keep in mind that you call instance methods on the class. (So they get treated like singletons) And all methods return a Promise. I think your issue here is that your doing dir_contents = DirectoryHelperTask.list_contents() inside of the class. While you could do this in ruby, I'm not sure its what you want.
Also, where you do dir_contents = r, unless dir_contents was defined before the block, its going to get defined just in the block.

Alternative initialize for a Class to avoid processing already known information

I have a class, Autodrop, that contains several methods , a.o. 'metadata', that call an external API (dropbox). They are slow.
However, I already often have that metadata around when initializing the AutodropImage, so I should make the methods smarter.
What I have in mind is this:
class Autodrop
include Dropbox
attr_reader :path
def initialize(path)
#path = path
end
def self.from_entry(drop_entry)
#drop_entry = drop_entry
self.initialize(#drop_entry.path)
end
def metadata
if #drop_entry = nil
return heavy_lifting_and_network_traffic
else
return #drop_entry.metadata
end
end
#...
end
Now, I would expect to call
entry = BarEntry.new()
foo = Autodrop.from_entry(entry)
foo.metadata
In order to avoid that heavy lifting and network traffic call.
But this does not work. And somehow, in all my newbieness, I am sure I am goind at this all wrong.
Is there a term I should look for and read about first? How would you go for this?
Note, that the examples are simplified: in my code, I inherit AutodropImage < Autodrop for example, which is called from withing AutodropGallery < Autodrop. The latter already knows all metadata for the AutodropImage, so I mostly want to avoid AutodropImage going over the heavy lifting again.
You are creating an instance variable #drop_entry in your class method from_entry and obviously it wont be available to your object that you are creating in this method. One workaround is to pass it as a parameter when you are initializing the class. It should work if you do the following modifications:
In your from_entry class method change
self.initialize(#drop_entry)
to
new(#drop_entry)
Modify initialize method to:
def initialize(drop_entry)
#drop_entry = drop_entry
#path = #drop_entry.path
end
Or if your class is tied up to pass only the path parameter, ie. you dont want to change the other existing code then you can use an optional parameter drop entry like so
def initialize(path, drop_entry=nil)
You would need to cache the metadata in a class variable.
Edit: Or in a class level instance variable.
Maybe this read will help: http://railstips.org/blog/archives/2006/11/18/class-and-instance-variables-in-ruby/

Resources