Project

isomorphic

0.0
Low commit activity in last 3 years
A long-lived project that still receives updates
Isomorphic is a Ruby library for specifying isomorphisms between Ruby objects.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

>= 0

Runtime

>= 4.2.0, < 7.0
 Project Readme

isomorphic

Isomorphic is a Ruby library for specifying isomorphisms between Ruby objects.

Installation

Add this line to your application's Gemfile:

gem 'isomorphic'

And then execute:

$ bundle

Or install it yourself as:

$ gem install isomorphic

Usage

In this example, we demonstrate how to use Isomorphic to specify an isomorphism between a Ruby on Rails model and a non-trivial module/class definition: BuildingSync, an XML schema for building energy audit data.

We begin with the definition of the Ruby on Rails model:

class Address < ActiveRecord::Base
  validates_presence_of :street_address, :city, :state, :postal_code
end

Our goal is to map instances of the Ruby on Rails model to/from the <auc:Address> XML element in BuildingSync.

The soap4r gem provides the xsd2ruby.rb binary for auto-generating Ruby code from an XML schema document.

An excerpt of the auto-generated Ruby code for BuildingSync is as follows:

module BuildingSync
  # {http://buildingsync.net/schemas/bedes-auc/2019}Address
  #   streetAddressDetail - BuildingSync::Address::StreetAddressDetail
  #   city - SOAP::SOAPString
  #   state - BuildingSync::State
  #   postalCode - SOAP::SOAPString
  #   postalCodePlus4 - SOAP::SOAPString
  #   county - SOAP::SOAPString
  #   country - SOAP::SOAPString
  class Address

    # inner class for member: StreetAddressDetail
    # {http://buildingsync.net/schemas/bedes-auc/2019}StreetAddressDetail
    #   simplified - BuildingSync::Address::StreetAddressDetail::Simplified
    #   complex - BuildingSync::Address::StreetAddressDetail::Complex
    class StreetAddressDetail

      # inner class for member: Simplified
      # {http://buildingsync.net/schemas/bedes-auc/2019}Simplified
      #   streetAddress - SOAP::SOAPString
      #   streetAdditionalInfo - SOAP::SOAPString
      class Simplified
        attr_accessor :streetAddress
        attr_accessor :streetAdditionalInfo

        def initialize(streetAddress = nil, streetAdditionalInfo = nil)
          @streetAddress = streetAddress
          @streetAdditionalInfo = streetAdditionalInfo
        end
      end

      # inner class for member: Complex
      # {http://buildingsync.net/schemas/bedes-auc/2019}Complex
      #   streetNumberPrefix - SOAP::SOAPString
      #   streetNumberNumeric - BuildingSync::Address::StreetAddressDetail::Complex::StreetNumberNumeric
      #   streetNumberSuffix - SOAP::SOAPString
      #   streetDirPrefix - SOAP::SOAPString
      #   streetName - SOAP::SOAPString
      #   streetAdditionalInfo - SOAP::SOAPString
      #   streetSuffix - SOAP::SOAPString
      #   streetSuffixModifier - SOAP::SOAPString
      #   streetDirSuffix - SOAP::SOAPString
      #   subaddressType - SOAP::SOAPString
      #   subaddressIdentifier - SOAP::SOAPString
      class Complex

        # inner class for member: StreetNumberNumeric
        # {http://buildingsync.net/schemas/bedes-auc/2019}StreetNumberNumeric
        #   xmlattr_Source - SOAP::SOAPString
        class StreetNumberNumeric < ::String
          AttrSource = XSD::QName.new("http://buildingsync.net/schemas/bedes-auc/2019", "Source")

          def __xmlattr
            @__xmlattr ||= {}
          end

          def xmlattr_Source
            __xmlattr[AttrSource]
          end

          def xmlattr_Source=(value)
            __xmlattr[AttrSource] = value
          end

          def initialize(*arg)
            super
            @__xmlattr = {}
          end
        end

        attr_accessor :streetNumberPrefix
        attr_accessor :streetNumberNumeric
        attr_accessor :streetNumberSuffix
        attr_accessor :streetDirPrefix
        attr_accessor :streetName
        attr_accessor :streetAdditionalInfo
        attr_accessor :streetSuffix
        attr_accessor :streetSuffixModifier
        attr_accessor :streetDirSuffix
        attr_accessor :subaddressType
        attr_accessor :subaddressIdentifier

        def initialize(streetNumberPrefix = nil, streetNumberNumeric = nil, streetNumberSuffix = nil, streetDirPrefix = nil, streetName = nil, streetAdditionalInfo = nil, streetSuffix = nil, streetSuffixModifier = nil, streetDirSuffix = nil, subaddressType = nil, subaddressIdentifier = nil)
          @streetNumberPrefix = streetNumberPrefix
          @streetNumberNumeric = streetNumberNumeric
          @streetNumberSuffix = streetNumberSuffix
          @streetDirPrefix = streetDirPrefix
          @streetName = streetName
          @streetAdditionalInfo = streetAdditionalInfo
          @streetSuffix = streetSuffix
          @streetSuffixModifier = streetSuffixModifier
          @streetDirSuffix = streetDirSuffix
          @subaddressType = subaddressType
          @subaddressIdentifier = subaddressIdentifier
        end
      end

      attr_accessor :simplified
      attr_accessor :complex

      def initialize(simplified = nil, complex = nil)
        @simplified = simplified
        @complex = complex
      end
    end

    attr_accessor :streetAddressDetail
    attr_accessor :city
    attr_accessor :state
    attr_accessor :postalCode
    attr_accessor :postalCodePlus4
    attr_accessor :county
    attr_accessor :country

    def initialize(streetAddressDetail = nil, city = nil, state = nil, postalCode = nil, postalCodePlus4 = nil, county = nil, country = nil)
      @streetAddressDetail = streetAddressDetail
      @city = city
      @state = state
      @postalCode = postalCode
      @postalCodePlus4 = postalCodePlus4
      @county = county
      @country = country
    end
  end
end

First, we define a factory:

class BuildingSyncFactory < Isomorphic::Factory::AbstractFactory
  include Singleton

  def initialize
    super(BuildingSync)
  end
end

Next, we define an inflector:

class BuildingSyncInflector < Isomorphic::Factory::AbstractInflector
  include Singleton

  def initialize
    super(BuildingSync)
  end
end

Finally, we declare the isomorphism using the domain-specific language:

class Address < ActiveRecord::Base
  validates_presence_of :street_address, :city, :state, :postal_code

  include Isomorphic::Model

  isomorphism_for(BuildingSyncFactory.instance, BuildingSyncInflector.instance, BuildingSync::Address) do
    member :streetAddressDetail, BuildingSync::Address::StreetAddressDetail do
      member :simplified, BuildingSync::Address::StreetAddressDetail::Simplified do
        attribute_for reflect_on_attribute(:street_address), :streetAddress
      end
    end
    attribute_for reflect_on_attribute(:city), :city
    attribute_for reflect_on_attribute(:state), :state
    attribute_for reflect_on_attribute(:postal_code), :postalCode
  end
end

Now, we can map instances of the Ruby on Rails model to/from the <auc:Address> XML element in BuildingSync:

orig_record = Address.new(street_address: '123 Fake Street', city: 'York', state: 'PA', postal_code: '17402')

building_sync_address = orig_record.to_building_sync_address

new_record = Address.from_building_sync_address(building_sync_address)

Development

After checking out the repo, run bin/setup to install dependencies. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

License

The 2-Clause BSD License

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/pnnl/isomorphic.