forked from hashie/hashie
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add an extension for persisting Hashes to files
Building on @maxlinc's start, this implements a Persistable extension that you can mix into any Hash to gain access to file persistence. Currently, there are two adapters: JSON and YAML. The persistence framework is easily expandable via adapters that can be registered and hotloaded into a Persistable module. The module defaults to a JSON adapter with a `#persist` method when mixed into a derived Hash class, but can be customized as follows: ```ruby class YamlPersistedHash < Hash include Hashie::Extensions::Persistence.new( adapter: :yaml, persist_method: :save ) end test = YamlPersistedHash['test' => 'value'] #=> {'test' => 'value'} test.save('filename.yaml') ``` To create a new adapter, you only need to register a class, module, or object that responds to an `#adapter` method. The `#adapter` method needs to respond with an object with a `#write(target, data)` method that knows how to write the data for persistence. For two examples, see the `Hashie::Extensions::Persistable::Json` and `Hashie::Extensions::Persistable::Yaml` modules. Closes hashie#262 by finishing and expanding the implementation.
- Loading branch information
1 parent
b458e72
commit 6f34588
Showing
7 changed files
with
446 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
require 'hashie/extensions/persistable/builder' | ||
require 'module_builder/buildable' | ||
|
||
module Hashie | ||
module Extensions | ||
# Give persistance capabilities to a Hash via different file adapters. | ||
# | ||
# @api public | ||
module Persistable | ||
include ModuleBuilder::Buildable | ||
|
||
# Raised when there is an error with the configuration for a Persistable | ||
# Hash. This is a generic error and shouldn't be raised directly, but | ||
# should be subclassed appropriately. | ||
PersistableError = Class.new(StandardError) | ||
|
||
# Raised when there is no adapter set for the type of data to write. | ||
UnknownAdapter = Class.new(PersistableError) | ||
|
||
# Raised when there is no location set for where to persist the Hash. | ||
UnknownLocation = Class.new(PersistableError) | ||
|
||
# An internal adapter identifier-to-adapter map used for setup. | ||
# | ||
# @return [Hash<Symbol=>Module>] | ||
# | ||
# @api private | ||
def self.adapters | ||
@adapters ||= {} | ||
end | ||
|
||
# Load a source into a new copy of the Hash class. | ||
# | ||
# @param [#read, #to_io, #to_str] source The source to load from. | ||
# @param [Hash] options | ||
# @option [#load] adapter An adapter that responds to #load. | ||
# | ||
# @return [Hash] | ||
# | ||
# @api public | ||
def self.load(source, options = {}) | ||
adapter = options.fetch(:adapter) { fail(UnknownAdapter, 'You did not specify an adapter for this Persistable Hash.'.freeze) } | ||
|
||
adapter.load(source) | ||
end | ||
|
||
# Persist a Hash to a file in a specified format. | ||
# | ||
# @param [Hash] hash The hash to persist. | ||
# @param [Hash] options | ||
# @option options [#write] adapter An adapter that responds to #write. | ||
# @option options [#write] target The target to persist to. | ||
# | ||
# @return [#read] | ||
# | ||
# @api public | ||
def self.persist(hash, options = {}) | ||
adapter = options.fetch(:adapter) { fail(UnknownAdapter, 'You did not specify an adapter for this Persistable Hash.'.freeze) } | ||
target = options.fetch(:target) { fail(UnknownLocation, 'You did not specify where you want to persist this Persistable Hash.'.freeze) } | ||
|
||
adapter.write(target, hash) | ||
end | ||
|
||
# Register adapter namespace under a specified identifier. | ||
# | ||
# @param [Symbol] identifier | ||
# @param [Module] adapter | ||
# | ||
# @return [self] | ||
# | ||
# @api public | ||
def self.register_adapter(identifier, adapter) | ||
adapters[identifier] = adapter | ||
self | ||
end | ||
|
||
# Add the #persist method to a Hash. | ||
# | ||
# @api public | ||
module Persistence | ||
# Persist the Hash to the given store. | ||
# | ||
# @note When the Hash has previously been persisted, this can be | ||
# called without a parameter to persist to the last location. | ||
# | ||
# @param [String, #write] store The path name or actual target to write to. | ||
# | ||
# @return [#read] | ||
# | ||
# @raise [ArgumentError] if the Hash was not previously persisted and | ||
# file is nil | ||
def persist(store = nil) | ||
self.persistable_store = store unless store.nil? | ||
|
||
if persistable_store.nil? | ||
fail(UnknownLocation, 'You did not specify where you want to persist this Persistable Hash.'.freeze) | ||
else | ||
Persistable.persist(self, adapter: adapter, target: persistable_store) | ||
persistable_store | ||
end | ||
end | ||
|
||
private | ||
|
||
attr_reader :persistable_store | ||
|
||
# @api private | ||
def persistable_store=(store) | ||
if store.is_a? String | ||
@persistable_store = Pathname.new(store) | ||
else | ||
@persistable_store = store | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end | ||
|
||
require 'hashie/extensions/persistable/json' | ||
require 'hashie/extensions/persistable/yaml' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
require 'module_builder/builder' | ||
|
||
module Hashie | ||
module Extensions | ||
module Persistable | ||
# Class to build a Persistable module with its own configuration. | ||
# | ||
# This allows for invidiual Persistable modules to be included in | ||
# classes and not impact the global Persistable configuration. | ||
# | ||
# @api private | ||
class Builder < ModuleBuilder::Builder | ||
def defaults | ||
{ adapter: :json, persist_method: :persist } | ||
end | ||
|
||
def hooks | ||
[:add_adapter, :override_persist_method] | ||
end | ||
|
||
def inclusions | ||
[Hashie::Extensions::Persistable::Persistence] | ||
end | ||
|
||
private | ||
|
||
def add_adapter | ||
@module.__send__(:include, Hashie::Extensions::Persistable.adapters[@adapter]) | ||
end | ||
|
||
def override_persist_method | ||
return if @persist_method == :persist | ||
|
||
@module.__send__(:alias_method, @persist_method, :persist) | ||
@module.__send__(:undef_method, :persist) | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
require 'json' | ||
|
||
module Hashie | ||
module Extensions | ||
module Persistable | ||
# Persistable adapter that serializes a Hash to a JSON file. | ||
# | ||
# @example | ||
# class PersistableHash < Hash | ||
# include Hashie::Extensions::Persistable.new(adapter: :json) | ||
# end | ||
# | ||
# data = PersistableHash[test: 'value'] | ||
# data.persist('data.json') | ||
module Json | ||
# Accessor method for an instance of the JSON Adapter. | ||
# | ||
# @api private | ||
def adapter | ||
Adapter.new | ||
end | ||
|
||
# Class that handles serializing a Hash to a JSON file. | ||
# | ||
# @api public | ||
class Adapter | ||
# Load a hash from a JSON source. | ||
# | ||
# @param [#read, #to_io, #to_str] source The source to load from. | ||
# | ||
# @return [Hash] | ||
# | ||
# @api public | ||
def load(source) | ||
::JSON.load(source) | ||
end | ||
|
||
# Write a hash to the JSON file. | ||
# | ||
# @param [#write] target | ||
# @param [Hash] hash | ||
# | ||
# @return [#read] | ||
# | ||
# @api public | ||
def write(target, hash) | ||
target.write(::JSON.dump(hash)) | ||
target | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end | ||
|
||
Hashie::Extensions::Persistable.register_adapter(:json, Hashie::Extensions::Persistable::Json) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
require 'yaml' | ||
|
||
module Hashie | ||
module Extensions | ||
module Persistable | ||
# Persistable adapter that serializes a Hash to a YAML file. | ||
# | ||
# @example | ||
# class PersistableHash < Hash | ||
# include Hashie::Extensions::Persistable.new(adapter: :yaml) | ||
# end | ||
# | ||
# data = PersistableHash[test: 'value'] | ||
# data.persist('data.yml') | ||
module Yaml | ||
# Accessor method for an instance of the YAML Adapter. | ||
# | ||
# @api private | ||
def adapter | ||
Adapter.new | ||
end | ||
|
||
# Class that handles serializing a Hash to a YAML file. | ||
# | ||
# @api public | ||
class Adapter | ||
# Load a hash from a YAML source. | ||
# | ||
# @param [#read, #to_io, #to_str] source The source to load from. | ||
# | ||
# @return [Hash] | ||
# | ||
# @api public | ||
def load(source) | ||
::YAML.load(source) | ||
end | ||
|
||
# Write a hash to the YAML file. | ||
# | ||
# @param [#write] target | ||
# @param [Hash] hash | ||
# | ||
# @return [#read] | ||
# | ||
# @api public | ||
def write(target, hash) | ||
target.write(::YAML.dump(hash.to_h)) | ||
target | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end | ||
|
||
Hashie::Extensions::Persistable.register_adapter(:yaml, Hashie::Extensions::Persistable::Yaml) |
Oops, something went wrong.