Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Versioning a Ruby Object

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?

like image 853
John Straughn Avatar asked Jul 05 '11 14:07

John Straughn


3 Answers

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
like image 147
Jeremy Avatar answered Nov 12 '22 02:11

Jeremy


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
like image 28
SvenK Avatar answered Nov 12 '22 03:11

SvenK


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
like image 43
Ian Avatar answered Nov 12 '22 03:11

Ian