Skip to content
simcha edited this page Sep 13, 2010 · 26 revisions

Catalogue

To define maps one need to add them to the catalogue, to do it call Mappum.catalogue_add function:


Mappum.catalogue_add do
  1. … maps go here …
    end

Mappum.catalogue_add has one optional argument – catalogue name (defaults to ROOT) do one can call it like this:

Mappum.catalogue_add "SOME_NAME" do
	
  • … maps go here …
    end
  • Map

    RootMap

    To define mapping between two documents be it XML or Ruby objects you need to call map function. Like this:

    
    map Person, Client do |person_alias, client_alias|
      #... submaps go here ...
    end
    

    One can use symbols instead of class names for example :Person instead of Person.
    Be careful! It is not possible now to use Person <=> CRM::Client syntax here, we know that it would be nice but have no idea how to fix it.

    Map names and finding maps

    By default maps are given names as follows:

    1. For bidirectional maps: Type1_from_to_Type2
    2. For unidirectional maps: Type1_to_Type2
      One can use name_map or name_map_prefix method to change names of maps:
      
      map Person, Client do |person_alias, client_alias|
        #not a recommended name 
        name_map '<', 'person_from_client' 
        #a recommended name 
        name_map '<=>', 'Nolists_Person_to_from_Client' 
        #prefix works only for default names
        name_map_prefix 'Nolists_'
      end
      

    Maps are also looked up by class name of from and to mappings if this mappings are unique by catalogue.

    Simple map

    To map (or bind) two elements of the structure you write:

    
    person_alias.element1 <=> client_alias.element2
    

    This will transform element1 from Person to element2 on Client when transforming Person to Client. And transform element2 from Client to element1 on Person when transforming Client to Person. We say transform instead of copy but it will be copied when types of elements are the same. If types are not same and no sub-map is defined map from catalogue for given types will be used.

    Type declaration

    When type will not be deduced from object definition (in runtime) one need to declare type of element. This is done like this:

    
    map p.address(ERP::Address) <=> c.address(CRM::Address)
    

    When the map for this classes is declared in same catalogue it will be processed automatically. Maps for some typical conversions are defined in autoconv_catalogue and used by default, currently there are such a conversions defined:
    Conversion Mappum version Remarks
    Date <=> String >=0.2 The format used is YYYY-MM-DD
    Time <=> String >=0.2 Format rfc2822 is used in Time >> String conversion
    Float <=> String >=0.2
    Fixnum <=> String >=0.2

    To change the default conversion define your own map for given types in chosen catalogue. For example to change default time format to iso8601 do:

    
    Mappum.catalogue_add
      map Time, String do |a,b|
        map a.self << b.self do |b1|
          return Time.parse(b1)
        end
        map a.self.iso8601 >> b.self
      end
    end
    

    and use it like this:
    
    Mappum.catalogue_add
      map Xo, Yo do |x,y|
        x.my_time(Time) <=> y.your_time(String)
      end
    end
    

    If you like to change the way object are converted for one mapping only you should do:
    
    Mappum.catalogue_add
      map Xo, Yo do |x,y|
        `leave default`
        x.my_time(Time) << y.your_time(String)
        `go utc and iso`
        x.my_time.getutc.iso8601 >> y.your_time
      end
    end
    

    Sub-maps

    Maps of substructures e.g. complex elements can be declared in line as sub-maps. Sub map is a do .. end block given to map function:

    
    map p.address(ERP::Address) <=> c.address(CRM::Address) do |erp_address, crm_address|
      map erp_address.street <=> crm_address.street
      #etc.
    end
    

    Unidirectional map

    Maps can point one direction only:

    
    map p.text >> c.body
    #or
    map p.somefield << c.element
    

    This map will work as normal bidirectional map when object will be transformed but:

    • There is no sub-maps on unidirectional maps
    • You can declare function calls on unidirectional maps

    Simple function calls

    Function calls work only on unidirectional maps. To call a function on mapped object simply add call after the element:

    
    map p.text.upcase >> c.body
    #or
    map p.somefield << c.element[0..5]
    

    Complex function calls

    Function calls work only on unidirectional maps. To execute multiline Ruby code on element pass block of code to map function:

    
    map p.text >> c.body do |text|
      return text.upcase
    end
    #or
    map p.somefield << c.element do |elem|
      return elem[0..5]
    end
    

    Constant mapping

    Constant values can be mapped to fields in both directions. One need to specify the constant value
    in the unidirectional map.

    
        map p.type << "NaN"
        map "Last" >> c.order_by
    

    Raw function

    To provide the function with no arguments (named raw in here) to be evaluated in the moment of
    mapping “func” keyword is used.

    
        map p.date_updated << func do
          Date.today
        end
        map func >> c.updated do 
          Time.now
        end
    

    Dictionary

    Dictionaries support bidirectional mapping. When mapping left to right element value matching key in the dictionary will be mapped to value for that key. And when mapping right to left element value matching value from dictionary will be mapped to the matching key.

    
    map p.name <=> c.sn, :dict => {"PTO" => "Point to Object",  "B2B" => "Business to Business"}
    

    Accessing Context (From version 0.3)

    One can pass a context (an arbitrary object) to transform function in options parameter as {:context => ctx}. Then in the map use context as any other element:

    
    map context.session["username"] <=> c.user
    map context.ip <=> c.server
    

    Accessing Hash (From version 0.3)

    When mapping Hash to non hash. One can use [key]. An example is:

    
    map p.props["username"] <=> c.user
    

    Functions on Arrays

    When mapping Arrays to non arrays. One can use find{|elem| block}. An example of use with sub-maps on typical
    properties list:

    
    map p.group <=> c.properties.find{|prop| prop.name == "group"} do |group, prop|
      map group.self <=> prop.value
      map "group" >> prop.name
    end
    

    When mapping from p → c on non nil group element property with name “group” and value of p.group field will be added to properties list. When mapping from c → p property with name “group” will be found and its value will be mapped to p.group.

    When mapping array to array one can use select function select{|elem| conditon} to filter from lists.

    Mapping to existing to Array

    By default when mapping to same array second time new element will be appended to this array. One can change this by using
    to_array_take method.
    First argument is a label:
    :> – left to right map
    :< – right to left map
    :<=> – both maps
    Second argument is the_way and can be:
    :new – will create new element
    :first – will map to the first element
    :all – will map to all elements

    
    map c.products[] <=> d.products[] do |wn, app|
      ...
    end
    map c.default_product <=> d.products[] do |wn, app|
      to_array_take :>, :all
      ...
    end
    

    Conditions

    If the map is to be executed only on certain conditions one can use map_when method for bidirectional maps and :when parameter for unidirectional.

    
        map f.mem <=> b.tem do
           map_when(:>) {|mem| mem > 10}
        end
        map f.mem <=> b.gem do
           map_when(:>) {|mem| mem <= 10}
        end
        map f.pol >> b.gol, :when => lambda {|pol| pol != 0}
    

    XML

    XML attributes

    Attributes are accessible via xmlattr_ prefix.

    
    map p.xmlattr_name <=> c.name