Ribbon

Ruby Object Notation

Inspired by JSON and OpenStruct.

Installation

Latest version:

gem install ribbon

From source:

git clone git://github.com/matheusmoreira/ribbon.git

Introduction

A Ribbon is a simple but powerful associative data structure designed to be easy and natural to use. It allows the dynamic definition of arbitrary attributes, which can easily be nested.

> r = Ribbon.new
 => {}
> r.a.b.c = :d
 => :d
> r
 => {a: {b: {c: :d}}}

If a property hasn't been set, an empty Ribbon will be used as its value. This allows you to easily and seamlessly nest any number of Ribbons. If the property has been set, its value will be returned instead.

> r.a.b.c
 => :d

You can also set the property if you give an argument to the method.

> r.a.b.c :e
 => :e
> r
 => {a: {b: {c: :e}}}

If you give it a block, the value of the option will be yielded to it.

> Ribbon.new do |config|
    config.music do |music|
      music.file do |file|
        file.extensions %w(flac mp3 ogg wma)
      end
    end
  end
 => {music: {file: {extensions: ["flac", "mp3", "ogg", "wma"]}}}

If the block takes no arguments (arity of zero), it will be evaluated in the context of the value instance. The above example could be rewritten as:

> Ribbon.new do
    music do
      file do
        extensions %w(flac mp3 ogg wma)
      end
    end
  end
 => {music: {file: {extensions: ["flac", "mp3", "ogg", "wma"]}}}

If you wish to check if a property has a value, you can simply append a ? to its name. If the property isn't there, nil will be returned and no Ribbon will be created and stored in its place.

> r.z?
 => nil
> r
 => {}

You may also provide a return value or a block:

> r.z? :no_value
 => :no_value
> r.z? { :value_from_block }
 => :value_from_block
> r.z? { raise 'Value not found' }
 => RuntimeError: Value not found

If you append a ! to the name of the property and give it an argument, the value of the property will be set to it and the receiver will be returned, allowing you to chain multiple assignments in a single line.

> r.a!(:z).s!(:x).d!(:c)
 => {a: :z, s: :x, d: :c}

You can also access the properties by key using the [] and []= operators. They work just like the regular method calls, which means you can chain them.

> r[:these_properties][:do_not][:exist]
> r[:they][:will_be] = :created

Ribbon Wrappers

Since Ribbons inherit from BasicObject, they don't include many general-purpose methods. In order to solve that problem, Ribbon::Wrapper is provided. You can treat wrapped ribbons as if it were ordinary hashes.

> w = Ribbon::Wrapper.new
> w[:x]
 => nil
> w.fetch :x, 10
 => 10

All undefined methods will be forwarded to the Ribbon's internal hash. However, if the hash doesn't respond to the method, it will be forwarded to the Ribbon itself. In other words, you can use wrapped ribbons as if they weren't wrapped, too.

> w.x?
 => nil
> w.x.y.z = 10
 => 10

One big difference to be aware of is that dynamic property creation and access via square brackets isn't available with wrapped ribbons, because hashes respond to [].

> w[:undefined][:property]
 => NoMethodError: undefined method `[]' for nil:NilClass

Also noteworthy is the fact that wrapping a ribbon will not modify it; nested ribbons will not be wrapped. However, the are the methods wrap_all! and unwrap_all!, which will recursively wrap and unwrap every ribbon, respectively, are available.

In addition to that, many other useful methods are implemented, such as to_hash, which recursively converts the ribbon and all nested ribbons to pure hashes, and to_yaml, which serializes the ribbon in YAML format.

Finally, you may access the wrapped ribbon's internal hash using the hash attribute, and access the wrapped ribbon itself using the ribbon attribute.


Originally part of Acclaim.