soup 0.9.9.2 → 0.9.10
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/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
|