Traitorous 
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',
size: 'Region',
ruins: [{name: 'Skull Mountain', danger: 'The Devil of'},
{name: 'Dire Swamp', danger: 'The Devil of'}
]
)
puts area.ruins.length
# 2
puts area.export
# {:name=>"Western Slope", :size=>"region", :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.
Roadmap
- Add better documentation
- better testing of deep constructions
- 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 c. Validations? d. translations? e. class specific (Date, CreditCard)
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
- Fork it ( https://github.com/[my-github-username]/traitorous/fork )
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create a new Pull Request