I have a series of Ruby objects that model underlying XML (like an OXM). Unfortunately, the XML is being changed and the corresponding version is being bumped. I need to update my Ruby objects to be able to handle both versions. I'd like something cleaner than a ton of if/else clauses in my methods, as this is likely to happen again. Is there an idiomatic Ruby way to handle this? I was thinking of using the base class as a proxy for various "versioned" classes, i.e.
class XMLModel
class V1
# V1 specific implementation
end
class V2;
# V2 specific implementation
end
def initialize
# create a new V? and set up delegation to that specific version of the object
end
def from_xml(xml_string)
# use the XML string to determine correct version and return a
# specific version of the object
end
end
The nice thing about the above method is that each version is distinct within the code, and allowing me to add/drop versions with little backwards compatibility testing. The bad thing is that I'll likely end up with a lot of code duplication. Also, in this case, the XMLModel.new
returns a new XMLModel
, while the XMLModel.from_xml
factory method returns a new XMLModel::V1
.
Ideas?
I see a few options for you.
You can proxy method calls on XMLModel
with method_missing
.
class XMLModel
def load_xml(xml)
version = determine_version(xml)
case version
when :v1
@model = XMLModelV1.new
when :v2
@model = XMLModelV2.new
end
end
def method_missing(sym, *args, &block)
@model.send(sym, *args, &block)
end
end
Another option would be to dynamically copy the methods from a specific version to the instance of XMLModel
. However, I would discourage doing this unless completely necessary.
The third option is to create a module for each version that has the methods specific to that version. Then, instead of having a proxy object you will just include the module for the particular version.
module XMLModelV1
#methods specific to v1
end
module XMLModelV2
#methods specific to v2
end
class XMLModel
#methods common to both versions
def initialize(version)
load_module(version)
end
def load_xml(xml)
load_module(determine_version(xml))
end
private
def load_module(version)
case version
when :v1
include XMLMOdelV1
when :v2
include XMLModelV2
end
end
end
Why not build subclass which inherits from XMLModel, then the decision between the classes is just at one point in the code.
class XMLModel_V1 < XMLModel
def from_xml(xml_string)
# do V1 specific things
end
end
class XMLModel_V2 < XMLModel
def from_xml(xml_string)
# do V2 specific things
end
end
# Sample code wich shows the usage of the classes
if(V1Needed)
m = XMLModel_V1
else
m = XMLModel_V2
end
This is not a full answer, but just a better formatted version of my comment describing how you can override ::new.
class XMLModel
def new *args
if self == XMLModel
klass = use_v1? ? XMLModelV1 : XMLModelV2
instance = klass.allocate
instance.initialize *args
instance
else
super *args
end
end
end
# and of course:
class XMLModelV1 < XMLModel; end
class XMLModelV2 < XMLModel; end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With