vault 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/MIT-LICENSE +20 -0
- data/README.md +73 -0
- data/Rakefile +42 -0
- data/lib/vault/associations.rb +39 -0
- data/lib/vault/attribute_accessors.rb +29 -0
- data/lib/vault/bulk_attributes.rb +17 -0
- data/lib/vault/dirty.rb +37 -0
- data/lib/vault/finders.rb +24 -0
- data/lib/vault/persistance.rb +47 -0
- data/lib/vault/properties.rb +68 -0
- data/lib/vault/scoping.rb +64 -0
- data/lib/vault/storage/in_memory_store.rb +14 -0
- data/lib/vault/storage/yaml_store.rb +52 -0
- data/lib/vault/storage.rb +4 -0
- data/lib/vault/validations.rb +13 -0
- data/lib/vault/version.rb +8 -0
- data/lib/vault.rb +53 -0
- data/spec/active_model_compliance_spec.rb +33 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/support/helpers.rb +16 -0
- data/spec/support/storage_api.rb +14 -0
- data/spec/vault/associations_spec.rb +73 -0
- data/spec/vault/finders_spec.rb +69 -0
- data/spec/vault/persistance_spec.rb +126 -0
- data/spec/vault/properties_spec.rb +59 -0
- data/spec/vault/scoping_spec.rb +53 -0
- data/spec/vault/storage/in_memory_store_spec.rb +5 -0
- data/spec/vault/storage/yaml_store_spec.rb +29 -0
- data/spec/vault_spec.rb +33 -0
- metadata +119 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010-* Nicolás Sanguinetti
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
Vault, a lightweight Object Document Mapper
|
2
|
+
===========================================
|
3
|
+
|
4
|
+
class User
|
5
|
+
include Vault
|
6
|
+
|
7
|
+
key :id
|
8
|
+
property :name
|
9
|
+
property :email
|
10
|
+
end
|
11
|
+
|
12
|
+
user = User.new(:id => 1, :name => "John", :email => "john@example.org")
|
13
|
+
user.save
|
14
|
+
|
15
|
+
User.find(1) #=> user
|
16
|
+
User.all #=> [user]
|
17
|
+
User.size #=> 1
|
18
|
+
|
19
|
+
Storage
|
20
|
+
-------
|
21
|
+
|
22
|
+
By default Vault stores everything in an in-memory hash. Each model (class in
|
23
|
+
which you included the `Vault` module) has its own, independent storage.
|
24
|
+
|
25
|
+
Subclasses of a model share their storage with their parent.
|
26
|
+
|
27
|
+
To change the storage you can call `store_objects_in(store)` in the class
|
28
|
+
definition. A store is any object that implements this API:
|
29
|
+
|
30
|
+
* `Store#size` returns an integer with the total amount of elements in the store
|
31
|
+
* `Store#each(&block)` a store must implement `each` and include Enumerable
|
32
|
+
* `Store#[](key)` receiving the key it should return a hash of all attributes
|
33
|
+
**except for the key**.
|
34
|
+
* `Store#[]=(key, attributes)` attributes will be a hash with the attributes
|
35
|
+
**except** for the key.
|
36
|
+
* `Store#delete(key)` shall delete the item without the given key.
|
37
|
+
* `Store#filter(hash)` should return a new object of the same class as the
|
38
|
+
original store, but only with objects whose properties match those of the
|
39
|
+
argument.
|
40
|
+
|
41
|
+
This library provides two storage mechanisms out of the box:
|
42
|
+
|
43
|
+
* `Vault::Storage::InMemoryStore` is a simple hash storage (and the default)
|
44
|
+
* `Vault::Storage::YamlStore` serializes the contents to disk as a YAML file
|
45
|
+
|
46
|
+
TODO
|
47
|
+
----
|
48
|
+
|
49
|
+
* Relationships/Associations
|
50
|
+
|
51
|
+
More docs?
|
52
|
+
----------
|
53
|
+
|
54
|
+
I will get to it eventually. For now, read the specs and the source—it is a
|
55
|
+
small library. Or help and write some docs :)
|
56
|
+
|
57
|
+
Note on Patches/Pull Requests
|
58
|
+
-----------------------------
|
59
|
+
|
60
|
+
* Fork the project.
|
61
|
+
* Make your feature addition or bug fix.
|
62
|
+
* Add tests for it. This is important so I don’t break it in a future version
|
63
|
+
unintentionally.
|
64
|
+
* Commit, do not mess with Rakefile, version, or history. (if you want to have
|
65
|
+
your own version, that is fine but bump version in a commit by itself I can
|
66
|
+
ignore when I pull.)
|
67
|
+
* Send me a pull request. Bonus points for topic branches.
|
68
|
+
|
69
|
+
Copyright
|
70
|
+
---------
|
71
|
+
|
72
|
+
Copyright © 2010 [Nicolás Sanguinetti](http://github.com/foca), licensed under
|
73
|
+
an MIT license. See MIT-LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
|
2
|
+
|
3
|
+
require "rake"
|
4
|
+
require "rake/gempackagetask"
|
5
|
+
require "rake/rdoctask"
|
6
|
+
|
7
|
+
require "vault/version"
|
8
|
+
|
9
|
+
begin
|
10
|
+
require "spec/rake/spectask"
|
11
|
+
|
12
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
13
|
+
spec.pattern = "spec/**/*_spec.rb"
|
14
|
+
spec.spec_opts << "--color" << "--format specdoc"
|
15
|
+
end
|
16
|
+
|
17
|
+
task :default => ["spec"]
|
18
|
+
rescue LoadError
|
19
|
+
end
|
20
|
+
|
21
|
+
spec = Gem::Specification.new do |s|
|
22
|
+
s.name = "vault"
|
23
|
+
s.version = Vault::Version::STRING
|
24
|
+
s.summary = "Provides a very lightweight ODM"
|
25
|
+
s.author = "Nicolás Sanguinetti"
|
26
|
+
s.email = "hi@nicolassanguinetti.info"
|
27
|
+
s.homepage = "http://github.com/foca/vault"
|
28
|
+
|
29
|
+
s.has_rdoc = true
|
30
|
+
s.extra_rdoc_files = %w(README.md MIT-LICENSE)
|
31
|
+
|
32
|
+
s.files = Dir['Rakefile', '{bin,lib,man,test,spec}/**/*', 'README*', '*LICENSE*'] & `git ls-files -z`.split("\0")
|
33
|
+
|
34
|
+
s.require_paths = ["lib"]
|
35
|
+
|
36
|
+
s.add_dependency("activemodel", "3.0.0.beta.3")
|
37
|
+
s.add_development_dependency("rspec", "~> 1.3")
|
38
|
+
end
|
39
|
+
|
40
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
41
|
+
pkg.gem_spec = spec
|
42
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Vault
|
2
|
+
module Associations
|
3
|
+
def has_many(name, klass_name=name.to_s.classify, foreign_key="#{self.to_s.underscore.singularize}_key")
|
4
|
+
define_method name do
|
5
|
+
model = klass_name.is_a?(Class) ? klass_name : self.class.const_get(klass_name)
|
6
|
+
HasManyProxy.new(self, model, foreign_key, foreign_key => key)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def belongs_to(name, klass_name=name.to_s.classify)
|
11
|
+
foreign_key = "#{name}_key"
|
12
|
+
|
13
|
+
property(foreign_key)
|
14
|
+
|
15
|
+
define_method name do
|
16
|
+
model = klass_name.is_a?(Class) ? klass_name : self.class.const_get(klass_name)
|
17
|
+
model[send(foreign_key)]
|
18
|
+
end
|
19
|
+
|
20
|
+
define_method "#{name}=" do |object|
|
21
|
+
send("#{foreign_key}=", object.key)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class HasManyProxy < Scoping::Scope
|
26
|
+
def initialize(owner, model, foreign_key, conditions={})
|
27
|
+
super(model, conditions)
|
28
|
+
@owner = owner
|
29
|
+
@foreign_key = foreign_key
|
30
|
+
end
|
31
|
+
|
32
|
+
def <<(object)
|
33
|
+
object.send("#{@foreign_key}=", @owner.key)
|
34
|
+
object.save
|
35
|
+
self
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Vault
|
2
|
+
module AttributeAccessors
|
3
|
+
def initialize(*)
|
4
|
+
@_attributes = attributes_from_model_properties
|
5
|
+
super
|
6
|
+
end
|
7
|
+
|
8
|
+
def write_attribute(name, value)
|
9
|
+
@_attributes[name] = value
|
10
|
+
end
|
11
|
+
|
12
|
+
def read_attribute(name)
|
13
|
+
@_attributes[name]
|
14
|
+
end
|
15
|
+
|
16
|
+
def attributes
|
17
|
+
@_attributes
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def attributes_from_model_properties
|
23
|
+
self.class.properties.inject(HashWithIndifferentAccess.new) do |props, prop|
|
24
|
+
props[prop.name] = prop.default
|
25
|
+
props
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Vault
|
2
|
+
module BulkAttributes
|
3
|
+
def initialize(attrs={})
|
4
|
+
update(attrs)
|
5
|
+
changed_attributes.clear
|
6
|
+
end
|
7
|
+
|
8
|
+
def update(attrs={})
|
9
|
+
attrs.each do |key, value|
|
10
|
+
method = "#{key}="
|
11
|
+
__send__(method, value)
|
12
|
+
end
|
13
|
+
|
14
|
+
self
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/vault/dirty.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
module Vault
|
2
|
+
module Dirty
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
include ActiveModel::Dirty
|
7
|
+
end
|
8
|
+
|
9
|
+
def save(*)
|
10
|
+
super.tap do |saved|
|
11
|
+
if saved
|
12
|
+
if attribute_changed?(self.class.key)
|
13
|
+
self.class.store.delete(attribute_was(self.class.key))
|
14
|
+
end
|
15
|
+
|
16
|
+
@previously_changed = changes
|
17
|
+
changed_attributes.clear
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def write_attribute(name, value)
|
23
|
+
name = name.to_s
|
24
|
+
|
25
|
+
if attribute_changed?(name)
|
26
|
+
old = changed_attributes[name]
|
27
|
+
changed_attributes.delete(name) if old == value
|
28
|
+
else
|
29
|
+
old = read_attribute(name)
|
30
|
+
old = old.dup if old.duplicable?
|
31
|
+
changed_attributes[name] = old if old != value
|
32
|
+
end
|
33
|
+
|
34
|
+
super(name, value)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Vault
|
2
|
+
module Finders
|
3
|
+
delegate :size, :to => :store
|
4
|
+
alias_method :count, :size
|
5
|
+
|
6
|
+
def all(query={})
|
7
|
+
query.delete(key)
|
8
|
+
store.filter(query).map do |key_value, properties|
|
9
|
+
build(key_value, properties)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def [](key_value)
|
14
|
+
properties = store[key_value]
|
15
|
+
build(key_value, properties) if properties
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def build(key_value, properties)
|
21
|
+
new(properties.update(key => key_value))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Vault
|
2
|
+
module Persistance
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
cattr_accessor :store
|
7
|
+
extend Storage
|
8
|
+
|
9
|
+
store_objects_in Vault::Storage::InMemoryStore.new
|
10
|
+
end
|
11
|
+
|
12
|
+
module Storage
|
13
|
+
def store_objects_in(store)
|
14
|
+
self.store = store
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(*)
|
19
|
+
@_new = true
|
20
|
+
@_destroyed = false
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
def persisted?
|
25
|
+
!@_new && !@_destroyed
|
26
|
+
end
|
27
|
+
|
28
|
+
def save(run_validations=true)
|
29
|
+
return false if run_validations && !valid?
|
30
|
+
self.class.store[key] = attributes_except_key
|
31
|
+
@_new = false
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
35
|
+
def destroy
|
36
|
+
self.class.store.delete(key)
|
37
|
+
@_destroyed = true
|
38
|
+
freeze
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def attributes_except_key
|
44
|
+
attributes.except(self.class.key)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Vault
|
2
|
+
module Properties
|
3
|
+
def key(key=nil)
|
4
|
+
return @_key if key.nil?
|
5
|
+
@_key = property(key, true).name
|
6
|
+
end
|
7
|
+
|
8
|
+
def property(name, primary=false, &block)
|
9
|
+
Property.new(name, primary, &block).tap do |prop|
|
10
|
+
properties << prop
|
11
|
+
define_property_methods(prop.name)
|
12
|
+
|
13
|
+
# FIXME: Ugh, ActiveModel fails with this, I can't do incremental method
|
14
|
+
# definition, you have to define them all at once (so undefine/define
|
15
|
+
# each time if you don't know all the properties upfront, like here.)
|
16
|
+
undefine_attribute_methods
|
17
|
+
define_attribute_methods(property_names)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def properties
|
22
|
+
@_properties ||= Set.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def property_names
|
26
|
+
properties.map(&:name)
|
27
|
+
end
|
28
|
+
|
29
|
+
def define_property_methods(name)
|
30
|
+
class_eval <<-ruby, __FILE__, __LINE__
|
31
|
+
def #{name}
|
32
|
+
read_attribute(:#{name})
|
33
|
+
end
|
34
|
+
|
35
|
+
def #{name}=(value)
|
36
|
+
write_attribute(:#{name}, value)
|
37
|
+
end
|
38
|
+
ruby
|
39
|
+
end
|
40
|
+
|
41
|
+
class Property
|
42
|
+
attr_reader :name
|
43
|
+
|
44
|
+
def initialize(name, primary=false, &default)
|
45
|
+
@name = name.to_s
|
46
|
+
@default = default || lambda {}
|
47
|
+
@primary = primary
|
48
|
+
end
|
49
|
+
|
50
|
+
def primary?
|
51
|
+
@primary
|
52
|
+
end
|
53
|
+
|
54
|
+
def default
|
55
|
+
@default.call
|
56
|
+
end
|
57
|
+
|
58
|
+
# Set requires both #hash and #eql? to check for inclusion
|
59
|
+
def hash # :nodoc:
|
60
|
+
name.hash
|
61
|
+
end
|
62
|
+
|
63
|
+
def eql?(other) # :nodoc:
|
64
|
+
name == other.name
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Vault
|
2
|
+
module Scoping
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def scopes
|
6
|
+
@_scopes ||= Hash.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def scope(name, &conditions)
|
10
|
+
name = name.to_s
|
11
|
+
scopes[name] = Scope.new(self, conditions.call)
|
12
|
+
define_scope_method(name)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def define_scope_method(name)
|
18
|
+
singleton_class.instance_eval do
|
19
|
+
define_method name do
|
20
|
+
scopes[name]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Scope
|
26
|
+
include Finders
|
27
|
+
|
28
|
+
delegate :new, :key, :model_name, :to => :model
|
29
|
+
|
30
|
+
attr_reader :conditions, :model
|
31
|
+
|
32
|
+
def initialize(model, conditions)
|
33
|
+
@model = model
|
34
|
+
@conditions = conditions.stringify_keys
|
35
|
+
end
|
36
|
+
|
37
|
+
def store
|
38
|
+
@store ||= @model.store.filter(conditions)
|
39
|
+
end
|
40
|
+
|
41
|
+
def method_missing(method, *)
|
42
|
+
if model_has_scope?(method)
|
43
|
+
merge(model.scopes[method.to_s])
|
44
|
+
else
|
45
|
+
super
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def respond_to?(method, include_private=false)
|
50
|
+
model_has_scope?(method) || super
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def model_has_scope?(name)
|
56
|
+
model.scopes.include?(name.to_s)
|
57
|
+
end
|
58
|
+
|
59
|
+
def merge(scope)
|
60
|
+
Scope.new(model, conditions.merge(scope.conditions))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Vault
|
2
|
+
module Storage
|
3
|
+
class InMemoryStore < Hash
|
4
|
+
def filter(query)
|
5
|
+
return self if query.blank?
|
6
|
+
|
7
|
+
inject(InMemoryStore.new) do |result, (key, properties)|
|
8
|
+
result[key] = properties if properties.merge(query) == properties
|
9
|
+
result
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require "yaml"
|
2
|
+
require "active_support/core_ext/module/delegation"
|
3
|
+
|
4
|
+
module Vault
|
5
|
+
module Storage
|
6
|
+
class YamlStore
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
delegate :[], :[]=, :size, :each, :delete, :to => :doc
|
10
|
+
|
11
|
+
def initialize(file=nil)
|
12
|
+
@file = file
|
13
|
+
at_exit { flush if @file.present? }
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize_copy(*)
|
17
|
+
@file = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def filter(query)
|
21
|
+
return filtered_copy(@doc) if query.blank?
|
22
|
+
|
23
|
+
results = doc.inject(ActiveSupport::OrderedHash.new) do |result, (key, properties)|
|
24
|
+
result[key] = properties if properties.merge(query) == properties
|
25
|
+
result
|
26
|
+
end
|
27
|
+
|
28
|
+
filtered_copy(results)
|
29
|
+
end
|
30
|
+
|
31
|
+
def flush
|
32
|
+
File.open(@file, "w+") {|f| YAML.dump(@doc, f) }
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
def doc=(doc)
|
38
|
+
@doc = doc
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def filtered_copy(doc)
|
44
|
+
dup.tap {|store| store.doc = doc }
|
45
|
+
end
|
46
|
+
|
47
|
+
def doc
|
48
|
+
@doc ||= ActiveSupport::OrderedHash.new(YAML.load_file(@file))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/vault.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require "set"
|
2
|
+
require "active_model"
|
3
|
+
require "active_support/core_ext/hash/except"
|
4
|
+
require "active_support/core_ext/module/delegation"
|
5
|
+
require "active_support/core_ext/class/attribute_accessors"
|
6
|
+
|
7
|
+
module Vault
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
extend ActiveSupport::Autoload
|
10
|
+
|
11
|
+
autoload :Associations
|
12
|
+
autoload :AttributeAccessors
|
13
|
+
autoload :BulkAttributes
|
14
|
+
autoload :Dirty
|
15
|
+
autoload :Finders
|
16
|
+
autoload :Persistance
|
17
|
+
autoload :Properties
|
18
|
+
autoload :Scoping
|
19
|
+
autoload :Storage
|
20
|
+
autoload :Validations
|
21
|
+
|
22
|
+
module Storage
|
23
|
+
extend ActiveSupport::Autoload
|
24
|
+
autoload :InMemoryStore
|
25
|
+
autoload :YamlStore
|
26
|
+
end
|
27
|
+
|
28
|
+
included do
|
29
|
+
extend ActiveModel::Naming
|
30
|
+
extend Properties
|
31
|
+
extend Finders
|
32
|
+
extend Scoping
|
33
|
+
extend Associations
|
34
|
+
|
35
|
+
include BulkAttributes
|
36
|
+
include AttributeAccessors
|
37
|
+
include Persistance
|
38
|
+
include Dirty
|
39
|
+
include Validations
|
40
|
+
|
41
|
+
# Convenience methods to provide ActiveModel's API
|
42
|
+
include ActiveModel::Conversion
|
43
|
+
end
|
44
|
+
|
45
|
+
def key
|
46
|
+
send(self.class.key)
|
47
|
+
end
|
48
|
+
alias_method :id, :key
|
49
|
+
|
50
|
+
def ==(other)
|
51
|
+
key == other.key
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "ActiveModel compliance" do
|
4
|
+
class Person
|
5
|
+
include Vault
|
6
|
+
|
7
|
+
key :id
|
8
|
+
property :first_name
|
9
|
+
property :last_name
|
10
|
+
end
|
11
|
+
|
12
|
+
include ActiveModel::Lint::Tests
|
13
|
+
|
14
|
+
instance_methods.grep(/^test_/).each do |method|
|
15
|
+
it method.gsub("_", " ").gsub(/to (\w+)/, 'to_\1') do
|
16
|
+
send method
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def model
|
23
|
+
Person.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def assert(cond, msg=nil)
|
27
|
+
flunk msg unless cond
|
28
|
+
end
|
29
|
+
|
30
|
+
def assert_kind_of(kind, object, msg=nil)
|
31
|
+
flunk msg unless object.kind_of?(kind)
|
32
|
+
end
|
33
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
module SpecHelpers
|
2
|
+
def model(&block)
|
3
|
+
Class.new do
|
4
|
+
include Vault
|
5
|
+
class_eval(&block) if block
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def named_model(name, &block)
|
10
|
+
model = model(&block)
|
11
|
+
self.class.class_eval do
|
12
|
+
remove_const(name) if const_defined?(name)
|
13
|
+
const_set(name, model)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
shared_examples_for "A storage adapter" do
|
2
|
+
it { should respond_to(:size) }
|
3
|
+
it { should respond_to(:delete) }
|
4
|
+
it { should respond_to(:[]) }
|
5
|
+
it { should respond_to(:[]=) }
|
6
|
+
|
7
|
+
it { should respond_to(:each) }
|
8
|
+
it { should be_an(Enumerable) }
|
9
|
+
|
10
|
+
it { should respond_to(:filter) }
|
11
|
+
it "is closed under #filter" do
|
12
|
+
subject.filter({}).should be_a(described_class)
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Vault, "model" do
|
4
|
+
class Book
|
5
|
+
include Vault
|
6
|
+
key :title
|
7
|
+
has_many :authors
|
8
|
+
end
|
9
|
+
|
10
|
+
class Author
|
11
|
+
include Vault
|
12
|
+
key :name
|
13
|
+
belongs_to :book
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:pickaxe_book) { Book.new(:title => "Programming Ruby") }
|
17
|
+
|
18
|
+
let(:dave_thomas) { Author.new(:name => "Dave Thomas") }
|
19
|
+
let(:chad_fowler) { Author.new(:name => "Chad Fowler") }
|
20
|
+
let(:andy_hunt) { Author.new(:name => "Andy Hunt") }
|
21
|
+
let(:sam_ruby) { Author.new(:name => "Sam Ruby") }
|
22
|
+
|
23
|
+
describe ".belongs_to" do
|
24
|
+
before do
|
25
|
+
dave_thomas.save
|
26
|
+
pickaxe_book.save
|
27
|
+
end
|
28
|
+
|
29
|
+
it "can assign the associated object directly" do
|
30
|
+
dave_thomas.book = pickaxe_book
|
31
|
+
dave_thomas.book.should == pickaxe_book
|
32
|
+
end
|
33
|
+
|
34
|
+
it "can assign the key" do
|
35
|
+
dave_thomas.book_key = pickaxe_book.key
|
36
|
+
dave_thomas.book.should == pickaxe_book
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe ".has_many" do
|
41
|
+
before do
|
42
|
+
dave_thomas.book = pickaxe_book
|
43
|
+
chad_fowler.book = pickaxe_book
|
44
|
+
|
45
|
+
dave_thomas.save
|
46
|
+
chad_fowler.save
|
47
|
+
|
48
|
+
pickaxe_book.save
|
49
|
+
end
|
50
|
+
|
51
|
+
subject { pickaxe_book.authors.all }
|
52
|
+
|
53
|
+
it "can find all associated objects" do
|
54
|
+
should include(dave_thomas, chad_fowler)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "doesn't find objects that haven't been associated to the model" do
|
58
|
+
should_not include(sam_ruby)
|
59
|
+
end
|
60
|
+
|
61
|
+
context "adding objects directly to the association" do
|
62
|
+
it "persists both the container and containee" do
|
63
|
+
pickaxe_book.authors << andy_hunt
|
64
|
+
should include(andy_hunt)
|
65
|
+
end
|
66
|
+
|
67
|
+
it "can chain additions" do
|
68
|
+
pickaxe_book.authors << andy_hunt << sam_ruby
|
69
|
+
should include(andy_hunt, sam_ruby)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Vault do
|
4
|
+
let :person_klass do
|
5
|
+
model do
|
6
|
+
key :name
|
7
|
+
property :email
|
8
|
+
property :age
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
let :john do
|
13
|
+
person_klass.new(:name => "John", :email => "john@example.org", :age => 28)
|
14
|
+
end
|
15
|
+
|
16
|
+
let :mary do
|
17
|
+
person_klass.new(:name => "Mary", :email => "mary@example.org", :age => 23)
|
18
|
+
end
|
19
|
+
|
20
|
+
let :bob do
|
21
|
+
person_klass.new(:name => "Bob", :email => "bob@example.org", :age => 29)
|
22
|
+
end
|
23
|
+
|
24
|
+
let :jane do
|
25
|
+
person_klass.new(:name => "Jane", :email => "jane@example.org", :age => 28)
|
26
|
+
end
|
27
|
+
|
28
|
+
before do
|
29
|
+
john.save
|
30
|
+
mary.save
|
31
|
+
bob.save
|
32
|
+
jane.save
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "#all" do
|
36
|
+
subject { person_klass.all }
|
37
|
+
|
38
|
+
it { should have(4).elements }
|
39
|
+
|
40
|
+
it "includes all the persisted objects" do
|
41
|
+
should include(john)
|
42
|
+
should include(mary)
|
43
|
+
should include(bob)
|
44
|
+
should include(jane)
|
45
|
+
end
|
46
|
+
|
47
|
+
it { should be_an(Enumerable) }
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "#[]" do
|
51
|
+
it "finds an object by key" do
|
52
|
+
person_klass["Bob"].should == bob
|
53
|
+
end
|
54
|
+
|
55
|
+
it "returns nil if it doesn't find it" do
|
56
|
+
person_klass["Jamie"].should be_nil
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "#size" do
|
61
|
+
it "returns the amount of elements in the store" do
|
62
|
+
person_klass.size.should == 4
|
63
|
+
end
|
64
|
+
|
65
|
+
it "is aliased to #count" do
|
66
|
+
person_klass.count.should == person_klass.size
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Vault do
|
4
|
+
let :book_klass do
|
5
|
+
model do
|
6
|
+
key :isbn
|
7
|
+
property :title
|
8
|
+
property :author
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
let :store do
|
13
|
+
book_klass.store
|
14
|
+
end
|
15
|
+
|
16
|
+
let :alice do
|
17
|
+
book_klass.new("title" => "Alice in Wonderland",
|
18
|
+
"author" => "Lewis Carroll",
|
19
|
+
"isbn" => "978-0862033248")
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#save" do
|
23
|
+
it "returns true" do
|
24
|
+
alice.save.should be(true)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "marks the object as persisted" do
|
28
|
+
alice.save
|
29
|
+
alice.should be_persisted
|
30
|
+
end
|
31
|
+
|
32
|
+
it "stores the object in the model's store" do
|
33
|
+
alice.save
|
34
|
+
store[alice.isbn].should include("title", "author")
|
35
|
+
end
|
36
|
+
|
37
|
+
it "doesn't store the key among the object properties" do
|
38
|
+
alice.save
|
39
|
+
store[alice.isbn].should_not include("isbn")
|
40
|
+
end
|
41
|
+
|
42
|
+
it "updates an already saved object's attributes" do
|
43
|
+
alice.save
|
44
|
+
alice.update(:title => "Alice in Wonderland Illustrated")
|
45
|
+
alice.save
|
46
|
+
|
47
|
+
store[alice.isbn]["title"].should == "Alice in Wonderland Illustrated"
|
48
|
+
end
|
49
|
+
|
50
|
+
it "clears the tracked changed attributes" do
|
51
|
+
alice.save
|
52
|
+
alice.should_not be_changed
|
53
|
+
end
|
54
|
+
|
55
|
+
it "doesn't keep the old key when you change it" do
|
56
|
+
alice.save
|
57
|
+
alice.update("isbn" => "978-0517223628")
|
58
|
+
old_isbn = alice.isbn_was
|
59
|
+
alice.save
|
60
|
+
|
61
|
+
store[old_isbn].should be_blank
|
62
|
+
store[alice.isbn].should_not be_blank
|
63
|
+
end
|
64
|
+
|
65
|
+
context "when the object is invalid" do
|
66
|
+
let :person_klass do
|
67
|
+
named_model :Person do
|
68
|
+
key :email
|
69
|
+
property :name
|
70
|
+
|
71
|
+
validates :email, :presence => true
|
72
|
+
validates :name, :presence => true
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
let :store do
|
77
|
+
person_klass.store
|
78
|
+
end
|
79
|
+
|
80
|
+
let :nameless_person do
|
81
|
+
person_klass.new(:email => "john.doe@example.org")
|
82
|
+
end
|
83
|
+
|
84
|
+
it "returns false" do
|
85
|
+
nameless_person.save.should be(false)
|
86
|
+
end
|
87
|
+
|
88
|
+
it "doesn't persist the object" do
|
89
|
+
nameless_person.save
|
90
|
+
nameless_person.should_not be_persisted
|
91
|
+
end
|
92
|
+
|
93
|
+
context "but you skip validations" do
|
94
|
+
it "returns true" do
|
95
|
+
nameless_person.save(false).should be(true)
|
96
|
+
end
|
97
|
+
|
98
|
+
it "persists the object anyway" do
|
99
|
+
nameless_person.save(false)
|
100
|
+
nameless_person.should be_persisted
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe "#destroy" do
|
107
|
+
before do
|
108
|
+
alice.save
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should no longer be persisted after being destroyed" do
|
112
|
+
alice.destroy
|
113
|
+
alice.should_not be_persisted
|
114
|
+
end
|
115
|
+
|
116
|
+
it "removes the object from the model's store" do
|
117
|
+
alice.destroy
|
118
|
+
store[alice.isbn].should be_blank
|
119
|
+
end
|
120
|
+
|
121
|
+
it "freezes the model" do
|
122
|
+
alice.destroy
|
123
|
+
alice.should be_frozen
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Vault, "defining properties" do
|
4
|
+
it "defines accessors for each property" do
|
5
|
+
person_klass = model do
|
6
|
+
property :name
|
7
|
+
property :age
|
8
|
+
end
|
9
|
+
|
10
|
+
person = person_klass.new
|
11
|
+
person.should respond_to(:name, :name=, :age, :age=)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "doesn't define duplicated properties" do
|
15
|
+
person_klass = model do
|
16
|
+
property :name
|
17
|
+
property :name
|
18
|
+
end
|
19
|
+
|
20
|
+
person_klass.should have(1).properties
|
21
|
+
end
|
22
|
+
|
23
|
+
it "doesn't support more than one key" do
|
24
|
+
person_klass = model do
|
25
|
+
key :name
|
26
|
+
key :age
|
27
|
+
end
|
28
|
+
|
29
|
+
person = person_klass.new(:name => "John", :age => 28)
|
30
|
+
person.save
|
31
|
+
person.to_key.should == [person.age]
|
32
|
+
end
|
33
|
+
|
34
|
+
it "can specify default values" do
|
35
|
+
person_klass = model do
|
36
|
+
property(:name) { "John" }
|
37
|
+
property(:age) { 28 }
|
38
|
+
end
|
39
|
+
|
40
|
+
person = person_klass.new
|
41
|
+
person.name.should == "John"
|
42
|
+
person.age.should == 28
|
43
|
+
end
|
44
|
+
|
45
|
+
it "doesn't flag an attribute as changed if you revert the change" do
|
46
|
+
person_klass = model do
|
47
|
+
property :name
|
48
|
+
end
|
49
|
+
|
50
|
+
person = person_klass.new(:name => "John")
|
51
|
+
person.name = "Joe"
|
52
|
+
person.name = "John"
|
53
|
+
person.should_not be_changed
|
54
|
+
end
|
55
|
+
|
56
|
+
it "provides 'dirty' attribute tracking" do
|
57
|
+
model.included_modules.should include(ActiveModel::Dirty)
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Vault do
|
4
|
+
let :book_klass do
|
5
|
+
model do
|
6
|
+
scope :by_lewis_carroll do
|
7
|
+
{ "author" => "Lewis Carroll" }
|
8
|
+
end
|
9
|
+
|
10
|
+
scope :titled_alice do
|
11
|
+
{ "title" => "Alice in Wonderland" }
|
12
|
+
end
|
13
|
+
|
14
|
+
key :isbn
|
15
|
+
property :title
|
16
|
+
property :author
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
let :alice_in_wonderland do
|
21
|
+
book_klass.new(:isbn => "978-0517223628",
|
22
|
+
:title => "Alice in Wonderland",
|
23
|
+
:author => "Lewis Carroll")
|
24
|
+
end
|
25
|
+
|
26
|
+
let :through_the_looking_glass do
|
27
|
+
book_klass.new(:isbn => "978-0140367096",
|
28
|
+
:title => "Through the Looking Glass",
|
29
|
+
:author => "Lewis Carroll")
|
30
|
+
end
|
31
|
+
|
32
|
+
let :the_wonderful_wizard_of_oz do
|
33
|
+
book_klass.new(:isbn => "978-0451530295",
|
34
|
+
:title => "The Wonderful Wizard of Oz",
|
35
|
+
:author => "Lyman Frank Baum")
|
36
|
+
end
|
37
|
+
|
38
|
+
before do
|
39
|
+
alice_in_wonderland.save
|
40
|
+
through_the_looking_glass.save
|
41
|
+
the_wonderful_wizard_of_oz.save
|
42
|
+
end
|
43
|
+
|
44
|
+
it "filters the collection by the given scope" do
|
45
|
+
scoped = book_klass.by_lewis_carroll
|
46
|
+
scoped.all.should =~ [alice_in_wonderland, through_the_looking_glass]
|
47
|
+
end
|
48
|
+
|
49
|
+
it "lets you chain scopes" do
|
50
|
+
scoped = book_klass.by_lewis_carroll.titled_alice
|
51
|
+
scoped.all.should == [alice_in_wonderland]
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "tempfile"
|
3
|
+
|
4
|
+
describe Vault::Storage::YamlStore do
|
5
|
+
let :path do
|
6
|
+
begin
|
7
|
+
file = Tempfile.new("store.yml")
|
8
|
+
file.path
|
9
|
+
ensure
|
10
|
+
file.close
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
subject do
|
15
|
+
described_class.new(path)
|
16
|
+
end
|
17
|
+
|
18
|
+
before do
|
19
|
+
subject["key_a"] = { "attr_1" => "value_1", "attr_2" => "value_2" }
|
20
|
+
subject["key_b"] = { "attr_3" => "value_3" }
|
21
|
+
end
|
22
|
+
|
23
|
+
it_should_behave_like "A storage adapter"
|
24
|
+
|
25
|
+
it "serializes the file to disk on #flush" do
|
26
|
+
subject.flush
|
27
|
+
YAML.load_file(path).should include("key_a", "key_b")
|
28
|
+
end
|
29
|
+
end
|
data/spec/vault_spec.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Vault do
|
4
|
+
let :book_klass do
|
5
|
+
model do
|
6
|
+
key :isbn
|
7
|
+
property :title
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
it "can initialize a new model from a hash of attributes" do
|
12
|
+
book = book_klass.new(:title => "Some Book", :isbn => "1234567890")
|
13
|
+
book.title.should == "Some Book"
|
14
|
+
book.isbn.should == "1234567890"
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "#update" do
|
18
|
+
let :book do
|
19
|
+
book_klass.new(:title => "Some Book", :isbn => "1234567890")
|
20
|
+
end
|
21
|
+
|
22
|
+
it "effectively changes the attribute values" do
|
23
|
+
book.update(:title => "Awesome Book", :isbn => "0987654321")
|
24
|
+
book.title.should == "Awesome Book"
|
25
|
+
book.isbn.should == "0987654321"
|
26
|
+
end
|
27
|
+
|
28
|
+
it "flags attributes as changed when bulk-updating" do
|
29
|
+
book.update(:title => "Awesome Book")
|
30
|
+
book.changes.should include(:title)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: vault
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- "Nicol\xC3\xA1s Sanguinetti"
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-05-14 00:00:00 -03:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: activemodel
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 3
|
29
|
+
- 0
|
30
|
+
- 0
|
31
|
+
- beta
|
32
|
+
- 3
|
33
|
+
version: 3.0.0.beta.3
|
34
|
+
type: :runtime
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rspec
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
segments:
|
44
|
+
- 1
|
45
|
+
- 3
|
46
|
+
version: "1.3"
|
47
|
+
type: :development
|
48
|
+
version_requirements: *id002
|
49
|
+
description:
|
50
|
+
email: hi@nicolassanguinetti.info
|
51
|
+
executables: []
|
52
|
+
|
53
|
+
extensions: []
|
54
|
+
|
55
|
+
extra_rdoc_files:
|
56
|
+
- README.md
|
57
|
+
- MIT-LICENSE
|
58
|
+
files:
|
59
|
+
- Rakefile
|
60
|
+
- lib/vault/associations.rb
|
61
|
+
- lib/vault/attribute_accessors.rb
|
62
|
+
- lib/vault/bulk_attributes.rb
|
63
|
+
- lib/vault/dirty.rb
|
64
|
+
- lib/vault/finders.rb
|
65
|
+
- lib/vault/persistance.rb
|
66
|
+
- lib/vault/properties.rb
|
67
|
+
- lib/vault/scoping.rb
|
68
|
+
- lib/vault/storage/in_memory_store.rb
|
69
|
+
- lib/vault/storage/yaml_store.rb
|
70
|
+
- lib/vault/storage.rb
|
71
|
+
- lib/vault/validations.rb
|
72
|
+
- lib/vault/version.rb
|
73
|
+
- lib/vault.rb
|
74
|
+
- spec/active_model_compliance_spec.rb
|
75
|
+
- spec/spec_helper.rb
|
76
|
+
- spec/support/helpers.rb
|
77
|
+
- spec/support/storage_api.rb
|
78
|
+
- spec/vault/associations_spec.rb
|
79
|
+
- spec/vault/finders_spec.rb
|
80
|
+
- spec/vault/persistance_spec.rb
|
81
|
+
- spec/vault/properties_spec.rb
|
82
|
+
- spec/vault/scoping_spec.rb
|
83
|
+
- spec/vault/storage/in_memory_store_spec.rb
|
84
|
+
- spec/vault/storage/yaml_store_spec.rb
|
85
|
+
- spec/vault_spec.rb
|
86
|
+
- README.md
|
87
|
+
- MIT-LICENSE
|
88
|
+
has_rdoc: true
|
89
|
+
homepage: http://github.com/foca/vault
|
90
|
+
licenses: []
|
91
|
+
|
92
|
+
post_install_message:
|
93
|
+
rdoc_options: []
|
94
|
+
|
95
|
+
require_paths:
|
96
|
+
- lib
|
97
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
segments:
|
102
|
+
- 0
|
103
|
+
version: "0"
|
104
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
segments:
|
109
|
+
- 0
|
110
|
+
version: "0"
|
111
|
+
requirements: []
|
112
|
+
|
113
|
+
rubyforge_project:
|
114
|
+
rubygems_version: 1.3.6
|
115
|
+
signing_key:
|
116
|
+
specification_version: 3
|
117
|
+
summary: Provides a very lightweight ODM
|
118
|
+
test_files: []
|
119
|
+
|