Traitorous Gem Version

Build Status Code Climate Inline docs

This is a simple trait based system that emphasizes reading in and out data structures, using an plugin Converter system. Each trait defined has a name and a Converter obj that responds to :export, and :import.

This process came out of a need to have a flexible config system that could be read from files easily (yaml, json, f*$k xml), populate a nested set of objects, and able to export the while thing ready to be encoded and saved back to disk.

The converters can be used to help (de)serialize, set default values, do validations and translations. The converter's job is to be able to import a value, instantiating into classes and assigning values.

I took a lot of inspiration from virtus.

Installation

Add this line to your application's Gemfile:

gem 'traitorous'

And then execute:

$ bundle

Or install it yourself as:

$ gem install traitorous

Usage


    # see spec/
    require 'traitorous'

    class Ruin
      include Traitorous
      include Traitorous::Equality
      trait :name
      trait :danger
    end

    r = Ruin.new(name: 'Skull Mountain', danger: 'The Devil of')
    #
    puts r.name
    # Skull Mountain

    puts r.danger
    # The Devil of

    puts r.export
    # {"name"=>"Skull Mountain", "danger"=>"The Devil of"}

    puts Ruin.new(r.export) == r
    # true

    class Area
      include Traitorous
      include Traitorous::Equality
      trait :name
      trait :size, Converter::DefaultValueStatic.new('sub-continent')
      trait :ruins, Converter::UniformArray.new(Ruin)
    end

    area = Area.new(
      name: 'Western Marches',
      ruins: [{name: 'Skull Mountain', danger: 'The Devil of'},
              {name: 'Dire Swamp', danger: 'The Devil of'}
              ]
     )
    puts area.size
    # 'sub-continent'
    puts area.ruins.length
    # 2
    puts area.export
    #  {:name=>"Western Slope", :size=> "sub-continent", :ruins=>[{:name=>"Dire Swamp", :danger=>"The Creature of"}, {:name=>"Skull Mountain", :danger=>"The Devil of"}]}
    puts Area.new(area.export) == area
    # true

Converters

The purpose of the converters are to facilitate the importation of simple JSON or YAML data and import that data into an arbitrarily nested tree of objects. And then to take those object and be able to export that data in a simple form ready to save.

This system should be flexible enough to account for an large variety of data structures that can be read in and out of storage easily and in 1 tree.

Traitorous::Converter::Identity

This converter is meant as a pass through converter that doesn't alter the incoming value on either do_import or do_export.

Traitorous::Converter::DefaultValueStatic

This converter is similar to the Traitorous::Converter::Identity except that when doing do_import it will return a default value if the opts are nil or false.

Traitorous::Converter::Model

This converter takes a model_klass argument and on do_import, will instantiate a new object of that class passing in the opts as params. do_export calls .export on the object and returns the result.

Traitorous::Converter::UniformArray

This converter takes a uniform_klass argument. It expects an array as input for .do_import and will instantiate an uniform_klass object for each element of the array and return the resulting array.

Traitorous::Converter::MethodKeyedUniformHash

This converter takes key_method and uniform_class arguments. It expects an array as input for .do_import and will instantiate an uniform_klass object for each element of the array, then call key_method on that object and add them to a hash as a key instance pair.

More Converters

more intelligent conversions? Expand the model, array and hash converters to accept an override to instantiating with ::new to allow for more flexibility in usage. This especially would be important if you wanted to import a list of object that represents different klasses that are given with a sub_type or sub_class attributes that are part of the do_import data.

Maybe also set traits to accept both import and export settings? Not sure if this is necessary yet.

Roadmap

  1. Add better documentation
  2. better testing of deep constructions
  3. Additional Converters a. DefaultValueDynamic that stores code to run upon input or output b. VariableArray that uses a sub-type in the opts to location the correct class to instantiate instead of always using a uniform class
  4. Validations?
  5. translations?
  6. HashWithIndifferentAccess ## Development

I use Guard to automate testing. It won't affect anything execpt the disk space needed to store the gems. If you do want to use it, from a shell in the home directory of the gem and type guard.

After checking out the repo, run bin/setup to install dependencies. Then, 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 to create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

  1. Fork it ( https://github.com/[my-github-username]/traitorous/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request