Bathysphere

Gem Version Build Status Code Climate Dependency Status Inline docs

Fetch arbitrarily deep properties from YAML files without loosing your breath.

Bathysphere takes benefit of a self-describing YAML-based data format to normalize options fetching, so the same option can be stored at different depths in distinct configuration files.

NOTICE: Contributions to make Bathysphere amazing are highly encouraged! And at any moment, feedback is welcome! : )

Installation

Add the gem to your Gemfile:

# Gemfile

gem 'bathysphere', '~> 1.0' # see semver.org

Usage

Basics

Define your configuration files. The key is the self-documenting part of the configuration file, and it defines how deep the values must be fetched:

# config/products/fruit.yml

---
display_name:
  key: 'size,color'
  values:
    small:
      green: 'Grape'
      orange: 'Kumquat'
      purple: 'Blueberry'
    medium:
      green: 'Pear'
      orange: 'Orange'
      purple: 'Plum'
    large:
      green: 'Watermelon'
      orange: 'Melon'
      purple: 'Eggplant'
# config/product/vehicle.yml

---
display_name:
  key: 'size'
  values:
    large: 'Train'
    medium: 'Bus'
    small: 'Bike'

Create a Bathysphere::Parser for each configuration file (note that you don't need to know how deep the values are defined):

# app/models/product.rb

require 'bathysphere'

class Product

  def self.configuration
    @configuration ||= Bathysphere::Parser.new("config/products/#{name}.yml")
  end

  def configuration
    self.class.configuration
  end

  def display_name
    raise NotImplementedError
  end
end

The same Bathysphere::Parser#fetch method can be used to fetch all the display_name values, using exactly the required arguments:

# app/models/fruit.rb

class Fruit < Product

  def initialize(size, color)
    @size = size
    @color = color
  end

  def display_name
    configuration.fetch(:display_name, @size, @color)
  end
end
# app/models/vehicle.rb

class Vehicle < Product

  def initialize(size)
    @size = size
  end

  def display_name
    configuration.fetch(:display_name, @size)
  end
end

A nice and easy way to implement duck types for different kinds of products!

# anywhere

products = [
  Fruit.new(:large, :purple),
  Vehicle.new(:small),
  Fruit.new(:small, :green),
  Fruit.new(:small, :orange),
  Vehicle.new(:large),
]

products.map(&display_name)
# => ["Eggplant", "Bike", "Grape", "Kumquat", "Train"]

Further automation

Sometimes, defining the reader methods for the configurable properites is not practical. (Maybe your products are generated dynamically, or you want to keep the duck type obvious by keeping these readers in the Product class.)

Optionally, the additional arguments to the Parser#fetch method can be retrieved automatically from any object provided to the Parser#using method:

# app/models/product.rb

require 'bathysphere'

class Product

  def self.configuration
    @configuration ||= Bathysphere::Parser.new("config/products/#{name}.yml")
  end

  def configuration
    self.class.configuration
  end

  def display_name
    # Note that only :display_name was provided as an argument to Parser#fetch,
    # the additional arguments will be retrieved by calling the corresponding
    # reader methods on whatever object is provided to Parser#using, which in
    # this case is the product instance.
    configuration.using(self).fetch(:display_name)
  end
end
# app/models/fruit.rb

class Fruit < Product

  # These readers will be automatically called by Bathysphere::Parser#fetch
  # to retrieve the Fruit#display_name - note that the parser is using(self)
  attr_reader :color, :size

  def initialize(size, color)
    @size = size
    @color = color
  end
end
# app/models/vehicle.rb

class Vehicle < Product

  # This reader will be automatically called by Bathysphere::Parser#fetch
  # to retrieve the Vehicle#display_name - note that the parser is using(self)
  attr_reader :size

  def initialize(size)
    @size = size
  end
end

Contributions

Contributions are welcome! Please feel free to open issues or pull requests to get help or discuss ideas or implementations. Note that opening pull requests with work in progress is not only welcome, but encouraged! Talk early, talk often, and we will all learn in the way :)

Finally, please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms.

Credits

Bathysphere is maintained and funded by Redbubble.

License

    Bathysphere
    Copyright (C) 2016, 2017 Redbubble

    Permission is hereby granted, free of charge, to any person obtaining
    a copy of this software and associated documentation files (the
    "Software"), to deal in the Software without restriction, including
    without limitation the rights to use, copy, modify, merge, publish,
    distribute, sublicense, and/or sell copies of the Software, and to
    permit persons to whom the Software is furnished to do so, subject to
    the following conditions:

    The above copyright notice and this permission notice shall be included
    in all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.