Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Masterplan is a library for comparing Ruby data structures against predefined templates - like XML Schema without the XML.

Fetching latest commit…

Octocat-spinner-32-eaf2f5

Cannot retrieve the latest commit at this time

Octocat-spinner-32 lib
Octocat-spinner-32 spec
Octocat-spinner-32 .document
Octocat-spinner-32 .gitignore
Octocat-spinner-32 .rspec
Octocat-spinner-32 Gemfile
Octocat-spinner-32 Gemfile.lock
Octocat-spinner-32 LICENSE.txt
Octocat-spinner-32 README.rdoc
Octocat-spinner-32 Rakefile
Octocat-spinner-32 VERSION
Octocat-spinner-32 schemer.gemspec
README.rdoc

Schemer

Schemer is a library for comparing Ruby data structures against predefined templates.

At Travel IQ, this is used to define a canonical definition of the structure and format of the requests and responses of our APIs. In short, you get something that is similar to a XML Scheme - a template against which your data can be compared, to ensure its correctness programmatically. Only without the XML part.

Examples make this easier to explain. Say you have a webservice or class that produces a Ruby data structure like this:

{
  :airports => [
    {
      :name => "Tegel Airport",
      :code => "TXL",
      :latitude => 2.35454,
      :longitude => 54.67867
    },
    {
      :name => "Schönefeld Airport",
      :code => "SXF",
      :latitude => 5.35454,
      :longitude => 34.67867
    }
  ]
}

and so on and so forth. In your tests of this service, you want to make sure that this structure follows certain rules:

  • It's a Hash with one key, :airports

  • The value is an Array, and it can be empty, but not null

  • Each entry is a hash

  • Each hash has the keys :name, :code, :latitude, :longitude

  • No value of these keys is ever null

  • Name and code are strings

  • The strings aren't empty

  • The code is three characters long and consists of uppercase letters

  • Latitude and Longitude are floats

In a more XML-centric world, you would define an XML (or Relax-NG etc.) Scheme that defines all these rules and then use that to validate your output. But we also want to deliver JSON…so we'll define the rules as a Schemer::Document, and validate the data while it's still Ruby, and say that as long as the source data is correct, the representation in JSON or XML will also be correct:

include Schemer::DefineRules

doc = Schemer::Document.new(
  :airports => [
    {
      :name => "Tegel Airport",
      :code => rule("TXL", :matches => /[A-Z]{3}/),
      :latitude => 2.35454,
      :longitude => 54.67867
    },
    {
      :name => "Schönefeld Airport",
      :code => "SXF",
      :latitude => 5.35454,
      :longitude => 34.67867
    }
  ]
)

It doesn't look much different from the example, but there are a lot of rules built-in. You can now use the doc object to check your data against the template:

Schemer.compare(:scheme => doc, :to => [{:example => :data}])

And it will throw a Schemer::FailedError exception, and print out debugging data:

>> Schemer.compare(:scheme => doc, :to => {:example => :data})
Schemer::FailedError: keys don't match in 'root':
expected:	airports
received:	example

Expected:
{"airports"=>
  [{:latitude=>2.35454,
    :longitude=>54.67867,
    :code=>
     #<Schemer::Rule:0x6d0bd70
      @example_value="TXL",
      @options=
       {"compare_each"=>false,
        "allow_nil"=>false,
        "included_in"=>false,
        "matches"=>/[A-Z]{3}/}>,
    :name=>"Tegel Airport"},
   {:latitude=>5.35454,
    :longitude=>34.67867,
    :code=>"SXF",
    :name=>"Schönefeld Airport"}]}

but was:
{"example"=>:data}

Another example:

>> Schemer.compare(:scheme => doc, :to => {:airports => [{:name => "Bla", :latitude => 1.1, :longitude => 2.3, :code => "XXx"}]})
Schemer::FailedError: value at 'root'=>'airports'=>'0'=>'code' "XXx" (String) does not match /[A-Z]{3}/ !

Expected:
{"name"=>"Tegel Airport",
 "latitude"=>2.35454,
 "code"=>
  #<Schemer::Rule:0x6d0bd70
   @example_value="TXL",
   @options=
    {"compare_each"=>false,
     "allow_nil"=>false,
     "included_in"=>false,
     "matches"=>/[A-Z]{3}/}>,
 "longitude"=>54.67867}

but was:
{"name"=>"Bla", "latitude"=>1.1, "code"=>"XXx", "longitude"=>2.3}

The implicit rules are:

  • Each object in the data needs to be of the same class as in the template

  • hash keys must match up

  • The first element of an Array in the template is used as the template for all elements in the data. That's why we didn't have to restate the custom rule about the code in the above example, as only the “Tegel Airport” hash is used for all checks.

You can add extra rules with the rule method - see Schemer::DefineRules#rule for details.

There is also an added assertion for unit tests or specs:

assert_schemer(doc, [{:example => :data})

A problem with webservices is that you need to keep the documentation up to date - something that is easily forgotten. If you have a schemer document, you can use it not only as the template, but also as an example in, say, online documentation:

<pre>
  <%= JSON.dump(doc.to_hash) %>
</pre>

The to_hash method removes the Schemer::Rule objects for clean output.

Installation

Currently, you can only install from Github. Add this to your Gemfile:

gem 'schemer', :git => 'git://github.com/traveliq/schemer.git'

If you don't use bundler, you're on your own, sorry.

Authors

Martin Tepper (monogreen.de), Holger Pillmann (holger.pillmann@gmail.com), Dr. Florian Odronitz (odo@mac.com)

Contact

For questions, contact the authors or developer@traveliq.net

Contributing to schemer

  • Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet

  • Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it

  • Fork the project

  • Start a feature/bugfix branch

  • Commit and push until you are happy with your contribution

  • Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.

  • Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.

Copyright

Copyright © 2011 www.travel-iq.com. See LICENSE.txt for further details.

Something went wrong with that request. Please try again.