1

I'm working on an XML export for a Ruby project and I'm looking for an elegant way to implement it. The goal of this XML file is to export all data inside a project container and there is several models (about 20) sharing some common properties (for example a name and a description).

Currently, the XML export looks like an awful thing like that:

def export_project(p)
  @xml project {
     @xml.name p.name
     @xml.description p.description

     @xml.itemAs {
       p.item_as.each {|item_a|export_itemA(item_a)
     }

     @xml.itemBs {
       p.item_Bs.each {|item_B|export_itemB(item_b)
     }

     @xml.itemCs {
       p.item_cs.each {|item_c|export_itemC(item_c)
     }
    }
  end

  def export_itemA(a)
    @xml.itemA {
      @xml.name a.name
    }
  end

  def export_itemB(b)
    @xml.itemB {
      @xml.description b.description
    }
  end


  def export_itemC(c)
    @xml.itemC {
      @xml.name c.name
      @xml.description c.description 
    }
  end

Which is pretty ugly (well, it's bearrable with 4 types, but the reality is 480 lines of mess ...)

What I'd like would be something like that (considered there is a magic mapping between a model and an exporter):

module Named
  def export
    @xml.name @context.name
  end
end

module Described
  def export
    @xml.description @context.description
  end
end

class ProjectExporter < ModelExporter
  include Named
  include Described

  def export
    @xml.project {
      super

     @xml.itemAs {
       export_items(p.item_as)
     }

     @xml.itemBs {
       export_items(p.item_Bs)
     }

     @xml.itemCs {
       export_items(p.item_cs)
     }
   }
end

class ItemAExporter < ModelExporter
  include Named

  def export
    @xml.itemA {
      super
    }
  end
end

class ItemBExporter < ModelExporter
  include Described

  def export
    @xml.itemB {
      super
    }
  end
end

class ItemCExporter < ModelExporter
  include Named
  include Described

  def export
    @xml.itemC {
      super
    }
  end
end

The problem with this method is that "super" will only call the export method of one of the module, not all of them.

I'm pretty suer the module and super approach is not the correct one, but I'm unable to find something more suitable. Any idea ?

Cheers and thanks, Vincent

Vincent
  • 620
  • 7
  • 19
  • 1
    See this answer: http://stackoverflow.com/questions/4470108/when-monkey-patching-a-method-can-you-call-the-overridden-method-from-the-new-i – marc Nov 28 '14 at 22:55
  • 1
    Aren't those definitions of subclases, so you need `class` rather than `def`? Also `Named#export` will just get overwritten after it's included. `super` will look to `ModelExporter` for a method `export`. Is there one? – Cary Swoveland Nov 29 '14 at 01:13
  • If, say, `ProjectExplorer` is a class, `include Name` will bring in the instance method `export`, just as though that method had been created in `ProjectExplorer` using `def export...end`. Then `include Describe` brings in another instance method with the same name, which takes the place of ("overwrites") the first `export` you "mixed" in. Then you create another method by that name within the class, which again overwrites the second `exports`. Now when you invoke `super`, Ruby will look to `ProjectExporer`'s superclass `ModelExplorer` for a method by the same name. – Cary Swoveland Nov 29 '14 at 01:26
  • 1
    If you're open to broad solutions, you may want to change from declarative XML generation to object-oriented XML generation. Here's how I did something similar in a project by creating an Exportable module: [(Gist)](https://gist.github.com/SixArm/6e6915f0d6f356084e71). Here's an example similar to your code: [(Gist)](https://gist.github.com/SixArm/ccbd5429af3a1538e5e9). You may also want to consider the Builder gem, RABL gem, and Rails ActiveRecord Serialization using #to_xml. – joelparkerhenderson Nov 29 '14 at 06:41
  • thanks for the inputs. I fixed the class/def typo. I think I'll go with Joel's solution that looks pretty nice. – Vincent Dec 01 '14 at 10:25

0 Answers0