soup 0.9.9.2 → 0.9.10
Sign up to get free protection for your applications and to get access to all the features.
- data/README +8 -9
- data/Rakefile +10 -52
- data/lib/soup.rb +13 -64
- data/lib/soup/backends.rb +13 -0
- data/lib/soup/backends/multi_soup.rb +22 -0
- data/lib/soup/backends/read_only.rb +17 -0
- data/lib/soup/backends/yaml_backend.rb +78 -0
- data/lib/soup/snip.rb +29 -28
- data/test/multi_soup_backend_test.rb +65 -0
- data/test/soup_test.rb +24 -13
- data/test/test_helper.rb +3 -0
- metadata +32 -8
data/README
CHANGED
@@ -4,26 +4,25 @@ and will often talk to you, but when you look closely, they don't exist.
|
|
4
4
|
Terrifying. And so:
|
5
5
|
|
6
6
|
require 'soup'
|
7
|
-
Soup.
|
7
|
+
soup = Soup.new(File.join("soup", __FILE__))
|
8
8
|
|
9
|
-
|
10
|
-
Soup << {
|
9
|
+
soup << {
|
11
10
|
:name => "James",
|
12
11
|
:skills => "Bowstaff, nunchuck"
|
13
12
|
}
|
14
|
-
|
13
|
+
|
15
14
|
# ...much later...
|
16
|
-
|
17
|
-
s =
|
15
|
+
|
16
|
+
s = soup['james']
|
18
17
|
s.skills # => "Bowstaff, nunchuck"
|
19
|
-
|
20
|
-
|
18
|
+
|
19
|
+
soup << {
|
21
20
|
:mane => "Lush and thick"
|
22
21
|
:teeth => "Sharp and ready"
|
23
22
|
:position => "Above my bed!!!"
|
24
23
|
}
|
25
24
|
|
26
|
-
|
25
|
+
|
27
26
|
The point is that you can set any attribute on a Soup data, and it will be persisted without
|
28
27
|
care. With reckless abandon, really.
|
29
28
|
|
data/Rakefile
CHANGED
@@ -7,6 +7,7 @@ task :default => :test
|
|
7
7
|
require "rake/testtask"
|
8
8
|
Rake::TestTask.new do |t|
|
9
9
|
t.libs << "test"
|
10
|
+
t.ruby_opts << "-rubygems"
|
10
11
|
t.test_files = FileList["test/**/*_test.rb"]
|
11
12
|
t.verbose = true
|
12
13
|
end
|
@@ -20,7 +21,7 @@ spec = Gem::Specification.new do |s|
|
|
20
21
|
|
21
22
|
# Change these as appropriate
|
22
23
|
s.name = "soup"
|
23
|
-
s.version = "0.9.
|
24
|
+
s.version = "0.9.10"
|
24
25
|
s.summary = "A super-simple data store"
|
25
26
|
s.author = "James Adam"
|
26
27
|
s.email = "james@lazyatom.com"
|
@@ -40,25 +41,25 @@ spec = Gem::Specification.new do |s|
|
|
40
41
|
# s.add_dependency("some_other_gem", "~> 0.1.0")
|
41
42
|
|
42
43
|
# If your tests use any gems, include them here
|
43
|
-
|
44
|
-
|
45
|
-
# If you want to publish automatically to rubyforge, you'll may need
|
46
|
-
# to tweak this, and the publishing task below too.
|
47
|
-
s.rubyforge_project = "soup"
|
44
|
+
s.add_development_dependency("shoulda")
|
48
45
|
end
|
49
46
|
|
50
|
-
# This task actually builds the gem. We also regenerate a static
|
47
|
+
# This task actually builds the gem. We also regenerate a static
|
51
48
|
# .gemspec file, which is useful if something (i.e. GitHub) will
|
52
49
|
# be automatically building a gem for this project. If you're not
|
53
50
|
# using GitHub, edit as appropriate.
|
54
51
|
Rake::GemPackageTask.new(spec) do |pkg|
|
55
52
|
pkg.gem_spec = spec
|
56
|
-
|
57
|
-
|
53
|
+
end
|
54
|
+
|
55
|
+
desc "Build the gemspec file #{spec.name}.gemspec"
|
56
|
+
task :gemspec do
|
58
57
|
file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
|
59
58
|
File.open(file, "w") {|f| f << spec.to_ruby }
|
60
59
|
end
|
61
60
|
|
61
|
+
task :package => :gemspec
|
62
|
+
|
62
63
|
# Generate documentation
|
63
64
|
Rake::RDocTask.new do |rd|
|
64
65
|
rd.main = "README"
|
@@ -70,46 +71,3 @@ desc 'Clear out RDoc and generated packages'
|
|
70
71
|
task :clean => [:clobber_rdoc, :clobber_package] do
|
71
72
|
rm "#{spec.name}.gemspec"
|
72
73
|
end
|
73
|
-
|
74
|
-
# If you want to publish to RubyForge automatically, here's a simple
|
75
|
-
# task to help do that. If you don't, just get rid of this.
|
76
|
-
# Be sure to set up your Rubyforge account details with the Rubyforge
|
77
|
-
# gem; you'll need to run `rubyforge setup` and `rubyforge config` at
|
78
|
-
# the very least.
|
79
|
-
begin
|
80
|
-
require "rake/contrib/sshpublisher"
|
81
|
-
namespace :rubyforge do
|
82
|
-
|
83
|
-
desc "Release gem and RDoc documentation to RubyForge"
|
84
|
-
task :release => ["rubyforge:release:gem", "rubyforge:release:docs"]
|
85
|
-
|
86
|
-
namespace :release do
|
87
|
-
desc "Release a new version of this gem"
|
88
|
-
task :gem => [:package] do
|
89
|
-
require 'rubyforge'
|
90
|
-
rubyforge = RubyForge.new
|
91
|
-
rubyforge.configure
|
92
|
-
rubyforge.login
|
93
|
-
rubyforge.userconfig['release_notes'] = spec.summary
|
94
|
-
path_to_gem = File.join(File.dirname(__FILE__), "pkg", "#{spec.name}-#{spec.version}.gem")
|
95
|
-
puts "Publishing #{spec.name}-#{spec.version.to_s} to Rubyforge..."
|
96
|
-
rubyforge.add_release(spec.rubyforge_project, spec.name, spec.version.to_s, path_to_gem)
|
97
|
-
end
|
98
|
-
|
99
|
-
desc "Publish RDoc to RubyForge."
|
100
|
-
task :docs => [:rdoc] do
|
101
|
-
config = YAML.load(
|
102
|
-
File.read(File.expand_path('~/.rubyforge/user-config.yml'))
|
103
|
-
)
|
104
|
-
|
105
|
-
host = "#{config['username']}@rubyforge.org"
|
106
|
-
remote_dir = "/var/www/gforge-projects/soup/" # Should be the same as the rubyforge project name
|
107
|
-
local_dir = 'rdoc'
|
108
|
-
|
109
|
-
Rake::SshDirPublisher.new(host, remote_dir, local_dir).upload
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
113
|
-
rescue LoadError
|
114
|
-
puts "Rake SshDirPublisher is unavailable or your rubyforge environment is not configured."
|
115
|
-
end
|
data/lib/soup.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
# Let us require stuff in lib without saying lib/ all the time
|
2
2
|
$LOAD_PATH.unshift(File.dirname(__FILE__)).uniq!
|
3
3
|
|
4
|
-
require 'soup/snip'
|
5
4
|
require 'yaml'
|
6
5
|
require 'fileutils'
|
7
6
|
|
8
7
|
class Soup
|
9
|
-
|
8
|
+
autoload :Backends, 'soup/backends'
|
9
|
+
autoload :Snip, 'soup/snip'
|
10
10
|
|
11
11
|
# You can access a default soup using this methods.
|
12
12
|
|
@@ -22,7 +22,7 @@ class Soup
|
|
22
22
|
default_instance << attributes
|
23
23
|
end
|
24
24
|
|
25
|
-
def self.
|
25
|
+
def self.with(*args)
|
26
26
|
default_instance.sieve(*args)
|
27
27
|
end
|
28
28
|
|
@@ -30,22 +30,20 @@ class Soup
|
|
30
30
|
default_instance.destroy(*args)
|
31
31
|
end
|
32
32
|
|
33
|
-
attr_reader :base_path
|
34
|
-
|
35
33
|
# Get the soup ready!
|
36
|
-
def initialize(
|
37
|
-
@
|
38
|
-
|
34
|
+
def initialize(backend=nil)
|
35
|
+
@backend = backend || Soup::Backends::YAMLBackend.new
|
36
|
+
@backend.prepare
|
39
37
|
end
|
40
38
|
|
41
39
|
# The main interface
|
42
40
|
# ==================
|
43
41
|
|
44
|
-
# A shorthand for #
|
45
|
-
# supplied (i.e.
|
42
|
+
# A shorthand for #with, with the addition that only a name may be
|
43
|
+
# supplied (i.e. soup['my snip'])
|
46
44
|
def [](conditions)
|
47
45
|
conditions = {:name => conditions} unless conditions.respond_to?(:keys)
|
48
|
-
|
46
|
+
with(conditions)
|
49
47
|
end
|
50
48
|
|
51
49
|
# Puts some data into the soup, and returns an object that contains
|
@@ -53,72 +51,23 @@ class Soup
|
|
53
51
|
# attributes as if they were defined using attr_accessor on the object's
|
54
52
|
# class.
|
55
53
|
def <<(attributes)
|
56
|
-
save_snip(attributes)
|
57
|
-
Snip.new(attributes, self)
|
54
|
+
@backend.save_snip(symbolize_keys(attributes))
|
58
55
|
end
|
59
56
|
|
60
57
|
# Finds bits in the soup that make the given attribute hash.
|
61
58
|
# This method should eventually be delegated to the underlying persistence
|
62
59
|
# layers (i.e. Snips and Tuples, or another document database). The expected
|
63
60
|
# behaviour is
|
64
|
-
def
|
65
|
-
|
66
|
-
if conditions.keys == [:name]
|
67
|
-
load_snip(conditions[:name])
|
68
|
-
else
|
69
|
-
all_snips.select do |s|
|
70
|
-
conditions.inject(true) do |matches, (key, value)|
|
71
|
-
matches && (s.__send__(key) == value)
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
61
|
+
def with(conditions)
|
62
|
+
@backend.find(symbolize_keys(conditions))
|
75
63
|
end
|
76
64
|
|
77
65
|
def destroy(name)
|
78
|
-
|
79
|
-
end
|
80
|
-
|
81
|
-
def all_snips
|
82
|
-
Dir[path_for("*")].map do |path|
|
83
|
-
load_snip(File.basename(path, ".yml"))
|
84
|
-
end
|
66
|
+
@backend.destroy(name)
|
85
67
|
end
|
86
68
|
|
87
69
|
private
|
88
70
|
|
89
|
-
def save_snip(attributes)
|
90
|
-
attributes = symbolize_keys(attributes)
|
91
|
-
File.open(path_for(attributes[:name]), 'w') do |f|
|
92
|
-
content = attributes.delete(:content)
|
93
|
-
f.write content
|
94
|
-
f.write attributes.to_yaml.gsub(/^---\s/, attribute_token) if attributes.any?
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
def load_snip(name)
|
99
|
-
path = path_for(name)
|
100
|
-
if File.exist?(path)
|
101
|
-
file = File.read(path)
|
102
|
-
if attribute_start = file.index(attribute_token)
|
103
|
-
content = file.slice(0...attribute_start)
|
104
|
-
attributes = {:name => name}.merge(YAML.load(file.slice(attribute_start..-1)).merge(:content => content))
|
105
|
-
else
|
106
|
-
attributes = {:content => file, :name => name}
|
107
|
-
end
|
108
|
-
Snip.new(attributes, self)
|
109
|
-
else
|
110
|
-
nil
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
def path_for(filename)
|
115
|
-
File.join(base_path, filename + ".yml")
|
116
|
-
end
|
117
|
-
|
118
|
-
def attribute_token
|
119
|
-
"--- # Soup attributes"
|
120
|
-
end
|
121
|
-
|
122
71
|
def symbolize_keys(hash)
|
123
72
|
hash.inject({}) { |h,(k,v)| h[k.to_sym] = v; h }
|
124
73
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Soup
|
2
|
+
# Backends should implement (or delegate) the following API:
|
3
|
+
# * #prepare - will be called when a Soup is created
|
4
|
+
# * #names - should return the names of all snips contained
|
5
|
+
# * #load_snip(name) - should return a Soup::Snip, or nil if it couldn't be loaded
|
6
|
+
# * #save_snip(attribute_hash) - should store and return a Soup::Snip, or nil if it couldn't be saved
|
7
|
+
# * #destroy(name) - should return true if the snip was removed, or false if otherwise
|
8
|
+
module Backends
|
9
|
+
autoload :YAMLBackend, 'soup/backends/yaml_backend'
|
10
|
+
autoload :MultiSoup, 'soup/backends/multi_soup'
|
11
|
+
autoload :ReadOnly, 'soup/backends/read_only'
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Soup
|
2
|
+
module Backends
|
3
|
+
class MultiSoup
|
4
|
+
def initialize(*backends)
|
5
|
+
@backends = backends
|
6
|
+
end
|
7
|
+
|
8
|
+
def prepare
|
9
|
+
@backends.each { |b| b.prepare }
|
10
|
+
end
|
11
|
+
|
12
|
+
def method_missing(*args)
|
13
|
+
@backends.each do |backend|
|
14
|
+
if result = backend.__send__(*args)
|
15
|
+
return result
|
16
|
+
end
|
17
|
+
end
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
class Soup
|
2
|
+
module Backends
|
3
|
+
class YAMLBackend
|
4
|
+
ATTRIBUTE_TOKEN = "--- # Soup attributes"
|
5
|
+
|
6
|
+
def initialize(path="soup")
|
7
|
+
@base_path = path
|
8
|
+
end
|
9
|
+
|
10
|
+
def prepare
|
11
|
+
FileUtils.mkdir_p(@base_path)
|
12
|
+
end
|
13
|
+
|
14
|
+
def names
|
15
|
+
Dir[path_for("*")].map { |s| File.basename(s, ".yml") }
|
16
|
+
end
|
17
|
+
|
18
|
+
def find(conditions)
|
19
|
+
if conditions.keys == [:name]
|
20
|
+
load_snip(conditions[:name])
|
21
|
+
else
|
22
|
+
all_snips.select do |s|
|
23
|
+
conditions.inject(true) do |matches, (key, value)|
|
24
|
+
matches && (s.__send__(key) == value)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def load_snip(name)
|
31
|
+
path = path_for(name)
|
32
|
+
if File.exist?(path)
|
33
|
+
file = File.read(path)
|
34
|
+
if attribute_start = file.index(ATTRIBUTE_TOKEN)
|
35
|
+
content = file.slice(0...attribute_start)
|
36
|
+
attributes = {:name => name}.merge(YAML.load(file.slice(attribute_start..-1)).merge(:content => content))
|
37
|
+
else
|
38
|
+
attributes = {:content => file, :name => name}
|
39
|
+
end
|
40
|
+
Snip.new(attributes, self)
|
41
|
+
else
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def save_snip(attributes)
|
47
|
+
File.open(path_for(attributes[:name]), 'w') do |f|
|
48
|
+
content = attributes.delete(:content)
|
49
|
+
f.write content
|
50
|
+
f.write attributes.to_yaml.gsub(/^---\s/, ATTRIBUTE_TOKEN) if attributes.any?
|
51
|
+
end
|
52
|
+
Snip.new(attributes, self)
|
53
|
+
end
|
54
|
+
|
55
|
+
def destroy(name)
|
56
|
+
path = path_for(name)
|
57
|
+
if File.exist?(path)
|
58
|
+
File.delete(path)
|
59
|
+
true
|
60
|
+
else
|
61
|
+
nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def path_for(name)
|
68
|
+
File.join(@base_path, name + ".yml")
|
69
|
+
end
|
70
|
+
|
71
|
+
def all_snips
|
72
|
+
Dir[path_for("*")].map do |key|
|
73
|
+
load_snip(File.basename(key, ".yml"))
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/soup/snip.rb
CHANGED
@@ -1,38 +1,39 @@
|
|
1
1
|
require 'soup/empty_class'
|
2
2
|
|
3
|
-
class
|
4
|
-
|
5
|
-
|
6
|
-
def initialize(attributes = {}, soup = Soup)
|
7
|
-
@attributes = attributes
|
8
|
-
@soup = soup
|
9
|
-
end
|
3
|
+
class Soup
|
4
|
+
class Snip < Soup::EmptyClass
|
5
|
+
attr_reader :attributes
|
10
6
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
7
|
+
def initialize(attributes, backend)
|
8
|
+
@attributes = attributes
|
9
|
+
@backend = backend
|
10
|
+
end
|
15
11
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
12
|
+
def save
|
13
|
+
@backend.save_snip(@attributes)
|
14
|
+
self
|
15
|
+
end
|
20
16
|
|
21
|
-
|
22
|
-
|
23
|
-
|
17
|
+
def destroy
|
18
|
+
@backend.destroy(self.name)
|
19
|
+
self
|
20
|
+
end
|
24
21
|
|
25
|
-
|
26
|
-
|
27
|
-
|
22
|
+
def inspect
|
23
|
+
"<Snip name:#{self.name}>"
|
24
|
+
end
|
28
25
|
|
29
|
-
|
30
|
-
|
31
|
-
if method.to_s =~ /(.*)=\Z/
|
32
|
-
@attributes[$1.to_sym] = value
|
33
|
-
else
|
34
|
-
@attributes[method]
|
26
|
+
def respond_to?(method)
|
27
|
+
@attributes.keys.include?(method.to_s)
|
35
28
|
end
|
36
|
-
end
|
37
29
|
|
30
|
+
def method_missing(method, *args)
|
31
|
+
value = args.length > 1 ? args : args.first
|
32
|
+
if method.to_s =~ /(.*)=\Z/
|
33
|
+
@attributes[$1.to_sym] = value
|
34
|
+
else
|
35
|
+
@attributes[method]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
38
39
|
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class MultiSoupBackendTest < Test::Unit::TestCase
|
4
|
+
context "A Soup with multiple backends" do
|
5
|
+
setup do
|
6
|
+
@base_path = File.join(File.dirname(__FILE__), *%w[.. tmp soup])
|
7
|
+
@basic_soup_backend_one = Soup::Backends::YAMLBackend.new(File.join(@base_path, "soup_one"))
|
8
|
+
@basic_soup_backend_two = Soup::Backends::YAMLBackend.new(File.join(@base_path, "soup_two"))
|
9
|
+
@soup_one = Soup.new(@basic_soup_backend_one)
|
10
|
+
@soup_two = Soup.new(@basic_soup_backend_two)
|
11
|
+
multi_soup_backend = Soup::Backends::MultiSoup.new(@basic_soup_backend_one, @basic_soup_backend_two)
|
12
|
+
@soup = Soup.new(multi_soup_backend)
|
13
|
+
end
|
14
|
+
|
15
|
+
teardown do
|
16
|
+
FileUtils.rm_rf(@base_path)
|
17
|
+
end
|
18
|
+
|
19
|
+
should "return nil when the requested snip is not present in any backend" do
|
20
|
+
assert_nil @soup["snip"]
|
21
|
+
end
|
22
|
+
|
23
|
+
should "return a snip if any backend contains it" do
|
24
|
+
@soup_one << {:name => "snip", :body => "hello"}
|
25
|
+
assert_equal "hello", @soup["snip"].body
|
26
|
+
|
27
|
+
@soup_two << {:name => "other_snip", :body => "hi!"}
|
28
|
+
assert_equal "hi!", @soup["other_snip"].body
|
29
|
+
end
|
30
|
+
|
31
|
+
context "when snips of the same name exist in multiple backends" do
|
32
|
+
setup do
|
33
|
+
@soup_one << {:name => "snip", :body => "from soup one"}
|
34
|
+
@soup_two << {:name => "snip", :body => "from soup two"}
|
35
|
+
end
|
36
|
+
|
37
|
+
should "load the snip from the backend with the higher precidence" do
|
38
|
+
assert_equal "from soup one", @soup["snip"].body
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
should "save snips" do
|
43
|
+
@soup << {:name => "snip", :body => "bad snip"}
|
44
|
+
@soup.destroy("snip")
|
45
|
+
assert_nil @soup["snip"]
|
46
|
+
end
|
47
|
+
|
48
|
+
context "when a backend is read-only" do
|
49
|
+
setup do
|
50
|
+
readonly_backend = Soup::Backends::ReadOnly.new(@basic_soup_backend_one)
|
51
|
+
@soup_one = Soup.new(readonly_backend)
|
52
|
+
@soup_two = Soup.new(@basic_soup_backend_two)
|
53
|
+
multi_soup_backend = Soup::Backends::MultiSoup.new(readonly_backend, @basic_soup_backend_two)
|
54
|
+
@soup = Soup.new(multi_soup_backend)
|
55
|
+
end
|
56
|
+
|
57
|
+
should "store snips in the writeable backend" do
|
58
|
+
@soup << {:name => "snip", :body => "hello"}
|
59
|
+
assert_equal "hello", @soup["snip"].body
|
60
|
+
assert_nil @soup_one["snip"]
|
61
|
+
assert_not_nil @soup_two["snip"]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/test/soup_test.rb
CHANGED
@@ -1,14 +1,27 @@
|
|
1
|
-
require
|
2
|
-
require 'shoulda'
|
3
|
-
require 'soup'
|
1
|
+
require "test_helper"
|
4
2
|
|
5
3
|
class SoupTest < Test::Unit::TestCase
|
6
4
|
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
def self.each_backend(&block)
|
6
|
+
base_path = File.join(File.dirname(__FILE__), *%w[.. tmp soup])
|
7
|
+
backends = [
|
8
|
+
yaml_backend = Soup::Backends::YAMLBackend.new(base_path),
|
9
|
+
Soup::Backends::MultiSoup.new(yaml_backend)
|
10
|
+
]
|
11
|
+
backends.each do |backend|
|
12
|
+
context "The #{backend.class.name} Soup backend" do
|
13
|
+
setup do
|
14
|
+
@soup = Soup.new(backend)
|
15
|
+
end
|
16
|
+
teardown do
|
17
|
+
FileUtils.rm_rf(base_path)
|
18
|
+
end
|
19
|
+
yield backend
|
20
|
+
end
|
10
21
|
end
|
22
|
+
end
|
11
23
|
|
24
|
+
each_backend do |backend|
|
12
25
|
should "be able to store content" do
|
13
26
|
@soup << {:name => 'test', :content => "I like stuff, and things"}
|
14
27
|
assert_equal "I like stuff, and things", @soup['test'].content
|
@@ -41,14 +54,12 @@ class SoupTest < Test::Unit::TestCase
|
|
41
54
|
end
|
42
55
|
end
|
43
56
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
assert_equal snip, @soup['test']
|
57
|
+
should "allow deletion of snips" do
|
58
|
+
snip = @soup << {:name => 'test', :content => 'content'}
|
59
|
+
assert_equal snip, @soup['test']
|
48
60
|
|
49
|
-
|
50
|
-
|
51
|
-
end
|
61
|
+
@soup['test'].destroy
|
62
|
+
assert @soup['test'].nil?
|
52
63
|
end
|
53
64
|
end
|
54
65
|
end
|
data/test/test_helper.rb
ADDED
metadata
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: soup
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 9
|
8
|
+
- 10
|
9
|
+
version: 0.9.10
|
5
10
|
platform: ruby
|
6
11
|
authors:
|
7
12
|
- James Adam
|
@@ -9,10 +14,21 @@ autorequire:
|
|
9
14
|
bindir: bin
|
10
15
|
cert_chain: []
|
11
16
|
|
12
|
-
date:
|
17
|
+
date: 2010-05-30 00:00:00 +02:00
|
13
18
|
default_executable:
|
14
|
-
dependencies:
|
15
|
-
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: shoulda
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
version: "0"
|
30
|
+
type: :development
|
31
|
+
version_requirements: *id001
|
16
32
|
description:
|
17
33
|
email: james@lazyatom.com
|
18
34
|
executables: []
|
@@ -25,7 +41,13 @@ files:
|
|
25
41
|
- Manifest
|
26
42
|
- Rakefile
|
27
43
|
- README
|
44
|
+
- test/multi_soup_backend_test.rb
|
28
45
|
- test/soup_test.rb
|
46
|
+
- test/test_helper.rb
|
47
|
+
- lib/soup/backends/multi_soup.rb
|
48
|
+
- lib/soup/backends/read_only.rb
|
49
|
+
- lib/soup/backends/yaml_backend.rb
|
50
|
+
- lib/soup/backends.rb
|
29
51
|
- lib/soup/empty_class.rb
|
30
52
|
- lib/soup/snip.rb
|
31
53
|
- lib/soup.rb
|
@@ -43,18 +65,20 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
43
65
|
requirements:
|
44
66
|
- - ">="
|
45
67
|
- !ruby/object:Gem::Version
|
68
|
+
segments:
|
69
|
+
- 0
|
46
70
|
version: "0"
|
47
|
-
version:
|
48
71
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
72
|
requirements:
|
50
73
|
- - ">="
|
51
74
|
- !ruby/object:Gem::Version
|
75
|
+
segments:
|
76
|
+
- 0
|
52
77
|
version: "0"
|
53
|
-
version:
|
54
78
|
requirements: []
|
55
79
|
|
56
|
-
rubyforge_project:
|
57
|
-
rubygems_version: 1.3.
|
80
|
+
rubyforge_project:
|
81
|
+
rubygems_version: 1.3.6
|
58
82
|
signing_key:
|
59
83
|
specification_version: 3
|
60
84
|
summary: A super-simple data store
|