New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Hashie::Extensions::Persistable #262
base: master
Are you sure you want to change the base?
Conversation
module Hashie | ||
module Extensions | ||
# Marker module to indicate has a single-argument hash constructor. | ||
module HashInitializer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: I created Hashie::Extensions::HashInitializer
that's just a marker module, because I didn't have a way to tell what classes could be initialized from a Hash. That's the behavior for Mash
and Dash
, but that's not how the initializer works (by default) for ::Hash
or Hashie::Hash
. It is the behavior if you mixin Hashie::Extensions::MergeInitializer
, though. I'm open to other ideas, though, perhaps a .from_hash
method instead of a marker module?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is OK.
I like the Mixin approach very much and this is definitely on the right track. I'd like the implementation to be future-proof (unlike Mash which is more garbage in, garbage out :), you should be able to mix Let me know what you think, happy to review more code! |
Rather than This might look a little odd at first, and slightly trickier to implement (but should be possible - modularity uses this pattern) but I think it will make it easier to extend. The advantages of specifying the format as an argument to a module rather than as a module constant is:
I think you get the idea. The caller requests persistance to a format, and plugins provide formats. But the caller doesn't need to know if Note: I'm not sure any of moneta's current serializers would be very hashie friendly, because a "key/value store" API is subtly different from a hash API (moneta allows random access but not iteration over keys) but the basic idea applies. Any gem with a collection of classes to serialize/deserialize a hash to a format could easily be registered as a plugin. This approach might be a slightly less exotic API with the same benefits: # Default persistance settings in class
class MyConfigFile < Dash
include Persistable
persist_to :yaml, 'config.yaml'
property :foo
property :bar
end
config = MyConfigFile.load
# modify config...
config.save
# Alternately, on an object:
hash = { foo: 'bar' }
hash.extend Persistable
# Explicitly set the peristance target
hash.persist_to :json, 'data.json'
hash.save
# Or implicit set it as an argument
# hash.save :json, 'data.json' |
I am OK with using symbols, however try to keep things short and simple to begin with. Up to you. Looking forward to it! |
IMHO, API should look like this: h = {foo: 'bar'}
Hashie::Persistable.persist(h, adapter: :json, target: 'foo.json')
class Foo < Dash
include Hashie::Persistable.new(persist_method: :commit)
end
Foo.new.commit(adapter: :json, target: 'foo.json') |
Bump @maxlinc - Just wanted to check if you've worked on the changes you mentioned back in January. I like the idea of a persistable framework and between the work you've outlined and something along the lines of Gregory's suggested syntax, I think it could be a great feature. |
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.
I didn't see #185 until just now, so this might be a duplicate of that, although this takes a fairly different approach (extension vs class). But that's why I'm opening this PR now to get some early feedback, rather than implementing the other features on my mind (autoload/autosave).
Hashie already has
Hashie::Mash#load
, but it doesn't haveHashie::Mash#save
, and there's also no way to load or save a Dash or other Hashie classes. This adds a Persistable mixin so any hash-like class can be loaded (if it has a compatible initializer) or saved.Usage:
I had planned on implementing autosave, but I want some feedback before going any further.
Also, right now it's only persisting YAML, but I think it wouldn't be hard to modify to support JSON and "YamlErb" (which is what's currently used by Hashie::Mash). You just need to swap out the adapter class and/or options... in theory you could even use Moneta, though I think the fact that Moneta doesn't support
#keys
,#each
or#to_hash
would be problematic.Any thoughts before I go further down this path?