soup 0.2.1 → 0.9.9

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/Manifest CHANGED
@@ -1,12 +1,6 @@
1
1
  lib/soup/empty_class.rb
2
2
  lib/soup/snip.rb
3
- lib/soup/tuples/active_record_tuple.rb
4
- lib/soup/tuples/data_mapper_tuple.rb
5
- lib/soup/tuples/sequel_tuple.rb
6
3
  lib/soup.rb
7
4
  Manifest
8
5
  README
9
- spec/snip_spec.rb
10
- spec/soup_spec.rb
11
- spec/spec_helper.rb
12
- spec/spec_runner.rb
6
+ test/soup_test.rb
@@ -0,0 +1,115 @@
1
+ require "rubygems"
2
+ require "rake/gempackagetask"
3
+ require "rake/rdoctask"
4
+
5
+ task :default => :test
6
+
7
+ require "rake/testtask"
8
+ Rake::TestTask.new do |t|
9
+ t.libs << "test"
10
+ t.test_files = FileList["test/**/*_test.rb"]
11
+ t.verbose = true
12
+ end
13
+
14
+ # This builds the actual gem. For details of what all these options
15
+ # mean, and other ones you can add, check the documentation here:
16
+ #
17
+ # http://rubygems.org/read/chapter/20
18
+ #
19
+ spec = Gem::Specification.new do |s|
20
+
21
+ # Change these as appropriate
22
+ s.name = "soup"
23
+ s.version = "0.9.9"
24
+ s.summary = "A super-simple data store"
25
+ s.author = "James Adam"
26
+ s.email = "james@lazyatom.com"
27
+ s.homepage = "http://lazyatom.com"
28
+
29
+ s.has_rdoc = true
30
+ s.extra_rdoc_files = %w(README)
31
+ s.rdoc_options = %w(--main README)
32
+
33
+ # Add any extra files to include in the gem
34
+ s.files = %w(Manifest Rakefile README) + Dir.glob("{test,lib}/**/*")
35
+
36
+ s.require_paths = ["lib"]
37
+
38
+ # If you want to depend on other gems, add them here, along with any
39
+ # relevant versions
40
+ # s.add_dependency("some_other_gem", "~> 0.1.0")
41
+
42
+ # If your tests use any gems, include them here
43
+ # s.add_development_dependency("mocha")
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"
48
+ end
49
+
50
+ # This task actually builds the gem. We also regenerate a static
51
+ # .gemspec file, which is useful if something (i.e. GitHub) will
52
+ # be automatically building a gem for this project. If you're not
53
+ # using GitHub, edit as appropriate.
54
+ Rake::GemPackageTask.new(spec) do |pkg|
55
+ pkg.gem_spec = spec
56
+
57
+ # Generate the gemspec file for github.
58
+ file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
59
+ File.open(file, "w") {|f| f << spec.to_ruby }
60
+ end
61
+
62
+ # Generate documentation
63
+ Rake::RDocTask.new do |rd|
64
+ rd.main = "README"
65
+ rd.rdoc_files.include("README", "lib/**/*.rb")
66
+ rd.rdoc_dir = "rdoc"
67
+ end
68
+
69
+ desc 'Clear out RDoc and generated packages'
70
+ task :clean => [:clobber_rdoc, :clobber_package] do
71
+ rm "#{spec.name}.gemspec"
72
+ 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
@@ -2,95 +2,121 @@
2
2
  $LOAD_PATH.unshift(File.dirname(__FILE__)).uniq!
3
3
 
4
4
  require 'soup/snip'
5
+ require 'yaml'
6
+ require 'fileutils'
5
7
 
6
- module Soup
7
- VERSION = "0.2.1"
8
-
9
- DEFAULT_CONFIG = {
10
- :adapter => 'sqlite3',
11
- :database => 'soup.db'
12
- }
13
-
14
- DEFAULT_TUPLE_IMPLEMENTATION = "active_record_tuple"
15
-
16
- # Set the base of this soup, i.e. where to get the data. This is the
17
- # database configuration, i.e.
18
- #
19
- # Soup.base = {:database => 'my_soup.db'}
20
- #
21
- def self.base=(database_config)
22
- @database_config = database_config
23
- end
24
-
25
- # Call this to set which tuple implementation to use, i.e.
26
- #
27
- # Soup.flavour = :active_record
28
- #
29
- def self.flavour=(tuple_implementation)
30
- @tuple_implementation = "#{tuple_implementation}_tuple"
31
- # We want to reset the tuple class if we re-flavour the soup.
32
- @tuple_class = nil
33
- end
34
-
35
- def self.tuple_class
36
- @tuple_class ||= case (@tuple_implementation || DEFAULT_TUPLE_IMPLEMENTATION)
37
- when "active_record_tuple", nil
38
- Soup::Tuples::ActiveRecordTuple
39
- when "data_mapper_tuple"
40
- Soup::Tuples::DataMapperTuple
41
- when "sequel_tuple"
42
- Soup::Tuples::SequelTuple
43
- end
8
+ class Soup
9
+ VERSION = "0.9.9"
10
+
11
+ # You can access a default soup using this methods.
12
+
13
+ def self.default_instance #:nodoc:
14
+ @@instance ||= new
15
+ end
16
+
17
+ def self.[](*args)
18
+ default_instance[*args]
19
+ end
20
+
21
+ def self.<<(attributes)
22
+ default_instance << attributes
23
+ end
24
+
25
+ def self.sieve(*args)
26
+ default_instance.sieve(*args)
44
27
  end
45
-
28
+
29
+ def self.destroy(*args)
30
+ default_instance.destroy(*args)
31
+ end
32
+
33
+ attr_reader :base_path
34
+
46
35
  # Get the soup ready!
47
- def self.prepare
48
- require "soup/tuples/#{@tuple_implementation || DEFAULT_TUPLE_IMPLEMENTATION}"
49
- tuple_class.prepare_database(DEFAULT_CONFIG.merge(@database_config || {}))
36
+ def initialize(base_path=nil)
37
+ @base_path = base_path || "soup"
38
+ FileUtils.mkdir_p(base_path)
50
39
  end
51
-
40
+
52
41
  # The main interface
53
42
  # ==================
54
-
55
- # Finds bits in the soup that make the given attribute hash.
56
- # This method should eventually be delegated to the underlying persistence
57
- # layers (i.e. Snips and Tuples, or another document database). The expected
58
- # behaviour is
59
- def self.sieve(*args)
60
- Snip.sieve(*args)
43
+
44
+ # A shorthand for #sieve, with the addition that only a name may be
45
+ # supplied (i.e. Soup['my snip'])
46
+ def [](conditions)
47
+ conditions = {:name => conditions} unless conditions.respond_to?(:keys)
48
+ sieve(conditions)
61
49
  end
62
-
50
+
63
51
  # Puts some data into the soup, and returns an object that contains
64
52
  # that data. The object should respond to accessing and setting its
65
53
  # attributes as if they were defined using attr_accessor on the object's
66
54
  # class.
67
- def self.<<(attributes)
68
- s = Snip.new(attributes)
69
- s.save
70
- s
55
+ def <<(attributes)
56
+ save_snip(attributes)
57
+ Snip.new(attributes, self)
71
58
  end
72
-
73
- # A shortcut to sieve by name attribute only
74
- def self.[](*args)
75
- results = if args[0].is_a?(Hash) || args.length > 1
76
- sieve(*args)
59
+
60
+ # Finds bits in the soup that make the given attribute hash.
61
+ # This method should eventually be delegated to the underlying persistence
62
+ # layers (i.e. Snips and Tuples, or another document database). The expected
63
+ # behaviour is
64
+ def sieve(conditions)
65
+ conditions = symbolize_keys(conditions)
66
+ if conditions.keys == [:name]
67
+ load_snip(conditions[:name])
77
68
  else
78
- sieve(:name => args[0])
69
+ all_snips.select do |s|
70
+ conditions.inject(true) do |matches, (key, value)|
71
+ matches && (s.__send__(key) == value)
72
+ end
73
+ end
79
74
  end
80
- results.length == 1 ? results.first : results
81
- end
82
-
83
- # ==== (interface ends) =====
84
-
85
- # Save the current state of the soup into a YAML file.
86
- def self.preserve(filename='soup.yml')
87
- snips = {}
88
- 1.upto(Soup.tuple_class.next_snip_id) do |id|
89
- snip = Snip.find(id) rescue nil
90
- snips[snip.id] = snip if snip
75
+ end
76
+
77
+ def destroy(name)
78
+ File.delete(path_for(name))
79
+ end
80
+
81
+ def all_snips
82
+ Dir[path_for("*")].map do |path|
83
+ load_snip(File.basename(path, ".yml"))
91
84
  end
92
- File.open(filename, 'w') do |f|
93
- f.puts snips.to_yaml
85
+ end
86
+
87
+ private
88
+
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)
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
+ attribute_start = file.index(attribute_token)
103
+ content = file.slice(0...attribute_start)
104
+ attributes = YAML.load(file.slice(attribute_start..-1)).merge(:content => content)
105
+ Snip.new(attributes, self)
106
+ else
107
+ nil
94
108
  end
95
109
  end
96
- end
110
+
111
+ def path_for(filename)
112
+ File.join(base_path, filename + ".yml")
113
+ end
114
+
115
+ def attribute_token
116
+ "--- # Soup attributes"
117
+ end
118
+
119
+ def symbolize_keys(hash)
120
+ hash.inject({}) { |h,(k,v)| h[k.to_sym] = v; h }
121
+ end
122
+ end
@@ -1,5 +1,5 @@
1
1
  # Based on Builder's BlankSlate object
2
- module Soup
2
+ class Soup
3
3
  class EmptyClass
4
4
  instance_methods.each { |m| undef_method(m) unless m =~ /^(__|instance_eval|respond_to\?)/ }
5
5
  end
@@ -1,148 +1,38 @@
1
- require 'rubygems'
2
1
  require 'soup/empty_class'
3
2
 
4
- # methods called on Tuple:
5
- # Tuple.for_snip(id)
6
- # Tuple.find_matching(tuple_name, tuple_value_conditions)
7
- # Tuple.find_matching_hash(key => value, key2 => value2, ...)
8
- # Tuple.next_snip_id
9
- # Tuple#save
10
- # Tuple#name
11
- # Tuple#value
12
- # Tuple#destroy
13
-
14
3
  class Snip < Soup::EmptyClass
4
+ attr_reader :attributes
15
5
 
16
- # Returns all snips which match the given criteria, i.e. which have a tuple that
17
- # matches the given conditions. For example:
18
- #
19
- # Snip.sieve(:created_at, "> '2007-01-01'")
20
- #
21
- # should return all Snips who have a 'created_at' value greater than '2007-01-01'.
22
- #
23
- def self.sieve(*args)
24
- if args.length > 1
25
- name = args[0]
26
- tuple_value_conditions = args[1]
27
- matching_tuples = Soup.tuple_class.find_matching(name, tuple_value_conditions)
28
- matching_tuples.map { |t| t.snip_id }.uniq.map { |snip_id| find(snip_id) }
29
- else
30
- pairs = args[0].inject([]) { |ary, (name, value)| ary << [name, value] }
31
- matching_tuples = pairs.map { |(name, value)| Soup.tuple_class.find_matching(name, "='#{value}'") }.flatten
32
- snips = matching_tuples.map { |t| t.snip_id }.uniq.map { |snip_id| find(snip_id) }
33
- snips.reject { |s| pairs.map { |(name, value)| s.get_value(name) == value }.include?(false) }
34
- end
35
- end
36
-
37
- # Returns the snip with the given ID (i.e. the collection of all tuples
38
- # with the matching snip_id, gathered into a magical snip.)
39
- #
40
- def self.find(id)
41
- raise "not found" unless (tuples = Soup.tuple_class.for_snip(id)).any?
42
- snip = Snip.new(:__id => id)
43
- snip.replace_tuples(tuples)
44
- snip
45
- end
46
-
47
- attr_reader :tuples
48
-
49
- def initialize(attributes = {})
50
- set_id(attributes.delete(:__id))
51
- @tuples = {}
52
- attributes.each { |name, value| set_value(name, value) }
53
- end
54
-
55
- def respond_to?(method)
56
- attributes.keys.include?(method.to_s)
6
+ def initialize(attributes = {}, soup = Soup)
7
+ @attributes = attributes
8
+ @soup = soup
57
9
  end
58
-
10
+
59
11
  def save
60
- raise "Saving would be pointless - there's no data!" if @tuples.empty?
61
- set_id_if_necessary
62
- each_tuple { |t| t.save }
12
+ @soup << @attributes
63
13
  self
64
14
  end
65
-
15
+
66
16
  def destroy
67
- each_tuple { |t| t.destroy }
68
- end
69
-
70
- def reload
71
- return self unless self.id
72
- replace_tuples(Soup.tuple_class.for_snip(id))
17
+ @soup.destroy(self.name)
73
18
  self
74
19
  end
75
-
76
- def attributes
77
- @tuples.inject({}) { |h, (name, t)| h[name] = t.value; h }
78
- end
79
20
 
80
- def replace_tuples(new_tuples)
81
- @tuples.clear
82
- new_tuples.each { |tuple| @tuples[tuple.name] = tuple }
83
- end
84
-
85
- def to_s
86
- "<Snip id:#{self.id || "unset"} #{tuples_as_string}>"
87
- end
88
-
89
21
  def inspect
90
- "<Snip id:#{self.id || "unset"} name:#{self.name}>"
22
+ "<Snip name:#{self.name}>"
91
23
  end
92
-
93
- def to_yaml(*args)
94
- attributes.to_yaml(*args)
24
+
25
+ def respond_to?(method)
26
+ @attributes.keys.include?(method.to_s)
95
27
  end
96
-
28
+
97
29
  def method_missing(method, *args)
98
30
  value = args.length > 1 ? args : args.first
99
- if method.to_s =~ /(.*)=\Z/ # || value - could be a nice DSL touch.
100
- set_value($1, value)
101
- else
102
- get_value(method.to_s)
103
- end
104
- end
105
-
106
- def id #:nodoc: why is ID special?
107
- @id
108
- end
109
-
110
- def get_value(name)
111
- @tuples[name.to_s] ? @tuples[name.to_s].value : nil
112
- end
113
-
114
- def set_value(name, value)
115
- tuple = @tuples[name.to_s]
116
- if tuple
117
- tuple.value = value
31
+ if method.to_s =~ /(.*)=\Z/
32
+ @attributes[$1.to_sym] = value
118
33
  else
119
- attributes = {:snip_id => self.id, :name => name.to_s, :value => value}
120
- tuple = @tuples[name.to_s] = Soup.tuple_class.new(attributes)
34
+ @attributes[method]
121
35
  end
122
- tuple.value
123
- end
124
-
125
-
126
- private
127
-
128
- def each_tuple
129
- @tuples.values.each { |tuple| yield tuple }
130
- end
131
-
132
- def set_id(id)
133
- @id = id
134
- self
135
36
  end
136
37
 
137
- def set_id_if_necessary
138
- if self.id.nil?
139
- set_id(Soup.tuple_class.next_snip_id)
140
- @tuples.values.each { |tuple| tuple.snip_id = self.id }
141
- end
142
- end
143
-
144
- def tuples_as_string
145
- @tuples.inject("") { |hash, (name, tuple)| hash += " #{name}:'#{tuple.value}'" }.strip
146
- end
147
-
148
- end
38
+ end
@@ -0,0 +1,54 @@
1
+ require 'test/unit'
2
+ require 'shoulda'
3
+ require 'soup'
4
+
5
+ class SoupTest < Test::Unit::TestCase
6
+
7
+ context "Given a soup" do
8
+ setup do
9
+ @soup = Soup.new(File.join(File.dirname(__FILE__), *%w[.. tmp soup]))
10
+ end
11
+
12
+ should "be able to store content" do
13
+ @soup << {:name => 'test', :content => "I like stuff, and things"}
14
+ assert_equal "I like stuff, and things", @soup['test'].content
15
+ end
16
+
17
+ context "when sieving the soup" do
18
+ setup do
19
+ @james = @soup << {:name => 'james', :spirit_guide => 'fox', :colour => 'blue', :powers => 'yes'}
20
+ @murray = @soup << {:name => 'murray', :spirit_guide => 'chaffinch', :colour => 'red', :powers => 'yes'}
21
+ end
22
+
23
+ should "find snips by name if the parameter is a string" do
24
+ assert_equal @james, @soup['james']
25
+ end
26
+
27
+ should "find snips using exact matching of keys and values if the parameter is a hash" do
28
+ assert_equal @murray, @soup[:name => 'murray']
29
+ end
30
+
31
+ should "match using all parameters" do
32
+ assert_equal @james, @soup[:powers => 'yes', :colour => 'red']
33
+ end
34
+
35
+ should "return an array if more than one snip matches" do
36
+ assert_equal [@james, @murray], @soup[:powers => 'yes']
37
+ end
38
+
39
+ should "return an empty array if no matching snips exist" do
40
+ assert_equal [], @soup[:powers => 'maybe']
41
+ end
42
+ end
43
+
44
+ context "when deleting snips" do
45
+ should "allow deletion of snips" do
46
+ snip = @soup << {:name => 'test', :content => 'content'}
47
+ assert_equal snip, @soup['test']
48
+
49
+ @soup['test'].destroy
50
+ assert @soup['test'].nil?
51
+ end
52
+ end
53
+ end
54
+ end
metadata CHANGED
@@ -1,67 +1,62 @@
1
1
  --- !ruby/object:Gem::Specification
2
- rubygems_version: 0.9.4
3
- specification_version: 1
4
2
  name: soup
5
3
  version: !ruby/object:Gem::Version
6
- version: 0.2.1
7
- date: 2008-04-17 00:00:00 +01:00
8
- summary: Soup is a bit of everything, summoned from nothing. Soup is like an imaginary friend - comforting,
9
- require_paths:
10
- - lib
11
- email:
12
- - james@lazyatom.com
13
- homepage: ""
14
- rubyforge_project: soup
15
- description: Soup is a bit of everything, summoned from nothing. Soup is like an imaginary friend - comforting,
16
- autorequire:
17
- default_executable:
18
- bindir: bin
19
- has_rdoc: true
20
- required_ruby_version: !ruby/object:Gem::Version::Requirement
21
- requirements:
22
- - - ">"
23
- - !ruby/object:Gem::Version
24
- version: 0.0.0
25
- version:
4
+ version: 0.9.9
26
5
  platform: ruby
27
- signing_key:
28
- cert_chain:
29
- post_install_message:
30
6
  authors:
31
7
  - James Adam
32
- files:
33
- - lib/soup/empty_class.rb
34
- - lib/soup/snip.rb
35
- - lib/soup/tuples/active_record_tuple.rb
36
- - lib/soup/tuples/data_mapper_tuple.rb
37
- - lib/soup/tuples/sequel_tuple.rb
38
- - lib/soup.rb
39
- - Manifest
40
- - README
41
- - spec/snip_spec.rb
42
- - spec/soup_spec.rb
43
- - spec/spec_helper.rb
44
- - spec/spec_runner.rb
45
- - soup.gemspec
46
- test_files: []
47
-
48
- rdoc_options: []
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
49
11
 
50
- extra_rdoc_files: []
12
+ date: 2009-10-12 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies: []
51
15
 
16
+ description:
17
+ email: james@lazyatom.com
52
18
  executables: []
53
19
 
54
20
  extensions: []
55
21
 
22
+ extra_rdoc_files:
23
+ - README
24
+ files:
25
+ - Manifest
26
+ - Rakefile
27
+ - README
28
+ - test/soup_test.rb
29
+ - lib/soup/empty_class.rb
30
+ - lib/soup/snip.rb
31
+ - lib/soup.rb
32
+ has_rdoc: true
33
+ homepage: http://lazyatom.com
34
+ licenses: []
35
+
36
+ post_install_message:
37
+ rdoc_options:
38
+ - --main
39
+ - README
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: "0"
47
+ version:
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: "0"
53
+ version:
56
54
  requirements: []
57
55
 
58
- dependencies:
59
- - !ruby/object:Gem::Dependency
60
- name: activerecord
61
- version_requirement:
62
- version_requirements: !ruby/object:Gem::Version::Requirement
63
- requirements:
64
- - - ">="
65
- - !ruby/object:Gem::Version
66
- version: 2.0.2
67
- version:
56
+ rubyforge_project: soup
57
+ rubygems_version: 1.3.3
58
+ signing_key:
59
+ specification_version: 3
60
+ summary: A super-simple data store
61
+ test_files: []
62
+
@@ -1,48 +0,0 @@
1
- require 'rubygems'
2
- gem 'activerecord', '>=2.0.2'
3
- require 'activerecord'
4
-
5
- module Soup
6
- module Tuples
7
- class ActiveRecordTuple < ActiveRecord::Base
8
- set_table_name :tuples
9
-
10
- def self.prepare_database(config)
11
- ActiveRecord::Base.establish_connection(config)
12
- return if connection.tables.include?("tuples") # NOTE - this probably isn't good enough (what if the schema has changed?)
13
- ActiveRecord::Migration.create_table :tuples, :force => true do |t|
14
- t.column :snip_id, :integer
15
- t.column :name, :string
16
- t.column :value, :text
17
- t.column :created_at, :datetime
18
- t.column :updated_at, :datetime
19
- end
20
- end
21
-
22
- def self.for_snip(id)
23
- find_all_by_snip_id(id)
24
- end
25
-
26
- def self.find_matching(name, value_conditions=nil)
27
- condition_sql = "name = '#{name}'"
28
- condition_sql += " AND value #{value_conditions}" if value_conditions
29
- find(:all, :conditions => condition_sql)
30
- end
31
-
32
- # TODO: *totally* not threadsafe.
33
- def self.next_snip_id
34
- maximum(:snip_id) + 1 rescue 1
35
- end
36
-
37
- def save
38
- super if new_record? || dirty?
39
- end
40
-
41
- private
42
-
43
- def dirty?
44
- true # hmm.
45
- end
46
- end
47
- end
48
- end
@@ -1,50 +0,0 @@
1
- require 'rubygems'
2
- gem 'datamapper'
3
- require 'data_mapper'
4
-
5
- module Soup
6
- module Tuples
7
-
8
- # This tuple implementation is broken - there's a weird interaction
9
- # where values are not set within the web application.
10
- #
11
- class DataMapperTuple < DataMapper::Base
12
- def self.prepare_database(config)
13
- DataMapper::Database.setup(config)
14
- # NOTE: so um, this property stuff doesn't like it if you're not connected to the db
15
- # lets only have it once we are? Seems mental.
16
- self.class_eval {
17
- set_table_name 'tuples'
18
-
19
- property :snip_id, :integer
20
-
21
- property :name, :string
22
- property :value, :text
23
-
24
- property :created_at, :datetime
25
- property :updated_at, :datetime
26
- }
27
- return if self.table.exists? # NOTE - this probably isn't good enough (what if the schema has changed?)
28
- DataMapper::Persistence.auto_migrate!
29
- end
30
-
31
- def self.for_snip(id)
32
- all(:snip_id => id)
33
- end
34
-
35
- # TODO: *totally* not threadsafe.
36
- def self.next_snip_id
37
- database.query("SELECT MAX(snip_id) + 1 FROM tuples")[0] || 1
38
- end
39
-
40
- def save
41
- if dirty? or new_record?
42
- super
43
- end
44
- end
45
-
46
- alias_method :destroy, :destroy!
47
- end
48
-
49
- end
50
- end
@@ -1,36 +0,0 @@
1
- require 'rubygems'
2
- gem 'sequel'
3
- require 'sequel'
4
-
5
- DB = Sequel.sqlite 'soup_development.db'
6
-
7
- module Soup
8
- module Tuples
9
-
10
- class SequelTuple < Sequel::Model(:tuples)
11
- set_schema do
12
- primary_key :id
13
- integer :snip_id
14
- string :name
15
- string :value
16
- datetime :created_at
17
- datetime :updated_at
18
- end
19
-
20
- def self.prepare_database(config)
21
- # ummm... how to connect?
22
- create_table # TODO: detect if the table already exists
23
- end
24
-
25
- def self.for_snip(id)
26
- filter(:snip_id => id).to_a
27
- end
28
-
29
- # TODO: *totally* not threadsafe.
30
- def self.next_snip_id
31
- max(:snip_id) + 1
32
- end
33
- end
34
-
35
- end
36
- end
@@ -1,68 +0,0 @@
1
-
2
- # Gem::Specification for Soup-0.2.1
3
- # Originally generated by Echoe
4
-
5
- Gem::Specification.new do |s|
6
- s.name = %q{soup}
7
- s.version = "0.2.1"
8
- s.date = %q{2008-04-17}
9
- s.summary = %q{Soup is a bit of everything, summoned from nothing. Soup is like an imaginary friend - comforting,}
10
- s.email = ["james@lazyatom.com"]
11
- s.homepage = %q{}
12
- s.rubyforge_project = %q{soup}
13
- s.description = %q{Soup is a bit of everything, summoned from nothing. Soup is like an imaginary friend - comforting,}
14
- s.has_rdoc = true
15
- s.authors = ["James Adam"]
16
- s.files = ["lib/soup/empty_class.rb", "lib/soup/snip.rb", "lib/soup/tuples/active_record_tuple.rb", "lib/soup/tuples/data_mapper_tuple.rb", "lib/soup/tuples/sequel_tuple.rb", "lib/soup.rb", "Manifest", "README", "spec/snip_spec.rb", "spec/soup_spec.rb", "spec/spec_helper.rb", "spec/spec_runner.rb", "soup.gemspec"]
17
- s.add_dependency(%q<activerecord>, [">= 2.0.2"])
18
- end
19
-
20
-
21
- # # Original Rakefile source (requires the Echoe gem):
22
- #
23
- # require 'rubygems'
24
- #
25
- # require 'lib/soup'
26
- #
27
- # begin
28
- # require 'echoe'
29
- #
30
- # Echoe.new("soup", Soup::VERSION) do |soup|
31
- # soup.author = ["James Adam"]
32
- # soup.email = ["james@lazyatom.com"]
33
- # soup.description = File.readlines("README").first
34
- # soup.dependencies = ["activerecord >=2.0.2"]
35
- # soup.clean_pattern = ["*.db", "meta", "pkg"]
36
- # soup.ignore_pattern = [".git", "*.db", "meta"]
37
- # end
38
- #
39
- # rescue LoadError
40
- # puts "You need to install the echoe gem to perform meta operations on this gem"
41
- # end
42
- #
43
- # begin
44
- # require 'spec'
45
- # require 'spec/rake/spectask'
46
- #
47
- # Spec::Rake::SpecTask.new do |t|
48
- # t.spec_opts = ["--format", "specdoc", "--colour"]
49
- # t.spec_files = Dir['spec/**/*_spec.rb'].sort
50
- # t.libs = ['lib']
51
- # #t.rcov = true
52
- # #t.rcov_dir = 'meta/coverage'
53
- # end
54
- #
55
- # task :show_rcov do
56
- # system 'open meta/coverage/index.html' if PLATFORM['darwin']
57
- # end
58
- #
59
- # rescue LoadError
60
- # puts "You need RSpec installed to run the spec (default) task on this gem"
61
- # end
62
- #
63
- # desc "Open an irb session preloaded with this library"
64
- # task :console do
65
- # sh "irb --prompt simple -rubygems -r ./lib/soup.rb"
66
- # end
67
- #
68
- # task :default => [:spec, :show_rcov]
@@ -1,102 +0,0 @@
1
- require File.join(File.dirname(__FILE__), "spec_helper")
2
-
3
- describe Snip do
4
- before(:each) do
5
- Soup.base = {:database => "soup_test.db"}
6
- Soup.prepare
7
- clear_database
8
- end
9
-
10
- describe "when newly created" do
11
- before(:each) { @snip = Snip.new }
12
-
13
- it "should not have a name" do
14
- @snip.name.should be_nil
15
- end
16
-
17
- it "should return nil for any attributes" do
18
- @snip.other_attribute.should be_nil
19
- end
20
-
21
- it "should not have an id yet" do
22
- @snip.id.should be_nil
23
- end
24
- end
25
-
26
- describe "when being created with attributes" do
27
-
28
- it "should set attributes as passed in" do
29
- @snip = Snip.new(:beats => 'phat', :rhymes => 100)
30
- @snip.beats.should == 'phat'
31
- @snip.rhymes.should == 100
32
- end
33
-
34
- it "should ignore any id passed in" do
35
- @snip = Snip.new(:id => 1000)
36
- @snip.id.should be_nil
37
- end
38
-
39
- it "should not ignore the secret __id" do
40
- @snip = Snip.new(:__id => 1000)
41
- @snip.id.should == 1000
42
- end
43
- end
44
-
45
- describe "when setting attributes" do
46
- before(:each) { @snip = Snip.new }
47
-
48
- it "should allow setting attributes" do
49
- @snip.something = "blah"
50
- @snip.something.should == "blah"
51
- end
52
-
53
- it "should not respond to attributes that have not been set" do
54
- @snip.should_not respond_to(:monkey)
55
- end
56
-
57
- it "should respond to attributes that have been set" do
58
- @snip.monkey = true
59
- @snip.should respond_to(:monkey)
60
- end
61
-
62
- it "should not allow setting of the id" do
63
- @snip.id = 100
64
- @snip.id.should_not == 100
65
- @snip.id.should be_nil
66
- end
67
- end
68
-
69
- describe "when saving" do
70
- before(:each) { @snip = Snip.new }
71
-
72
- it "should not save if there's no data" do
73
- lambda { @snip.save }.should raise_error
74
- end
75
-
76
- it "should return all attributes when reloading" do
77
- @snip.name = "something"
78
- @snip.jazz = "smooth"
79
- @snip.save
80
-
81
- other_snip = Soup['something']
82
- other_snip.jazz.should == "smooth"
83
- end
84
-
85
- it "should generate an id" do
86
- @snip.name = "something"
87
- @snip.save
88
-
89
- other_snip = Soup['something']
90
- other_snip.id.should_not be_nil
91
- end
92
-
93
- it "should not overwrite an existing id created via __id" do
94
- @snip = Snip.new(:__id => 100)
95
- @snip.name = "something_else"
96
- @snip.save
97
-
98
- other_snip = Soup['something_else']
99
- other_snip.id.should == 100
100
- end
101
- end
102
- end
@@ -1,148 +0,0 @@
1
- require 'soup'
2
-
3
- describe Soup do
4
-
5
- describe "when unflavoured or based" do
6
- before(:each) { Soup.class_eval { @database_config = nil; @tuple_class = nil } }
7
- it "should use the default database config" do
8
- # I think this set of mock / expectations might be super wrong
9
- Soup::DEFAULT_CONFIG.should_receive(:merge).with({}).and_return(Soup::DEFAULT_CONFIG)
10
- Soup.tuple_class.should_receive(:prepare_database).with(Soup::DEFAULT_CONFIG)
11
- Soup.prepare
12
- end
13
-
14
- it "should use the default tuple implementation" do
15
- # No real idea how to mock the require, or use aught but Secret Knowledge that AR == Default
16
- Soup.tuple_class.should == Soup::Tuples::ActiveRecordTuple
17
- Soup::Tuples::ActiveRecordTuple.should_receive(:prepare_database)
18
- Soup.prepare
19
- end
20
-
21
- end
22
-
23
- describe "when being based" do
24
- before(:each) { Soup.class_eval { @database_config = nil; @tuple_class = nil } }
25
-
26
- it "should allow the base of the soup to be set" do
27
- Soup.should respond_to(:base=)
28
- end
29
-
30
- it "should use the new base when preparing the soup" do
31
- bouillabaisse = {:database => 'fishy.db', :adapter => 'fishdb'}
32
- Soup.base = bouillabaisse
33
- Soup.tuple_class.should_receive(:prepare_database).with(bouillabaisse)
34
- Soup.prepare
35
- end
36
-
37
- it "should merge incomplete bases with the default" do
38
- tasteless = {:database => 'water.db'}
39
- Soup.base = tasteless
40
- Soup.tuple_class.should_receive(:prepare_database).with(Soup::DEFAULT_CONFIG.merge(tasteless))
41
- Soup.prepare
42
- end
43
-
44
- it "should allow the base to be reset" do
45
- bouillabaisse = {:database => 'fishy.db', :adapter => 'fishdb'}
46
- Soup.base = bouillabaisse
47
- Soup.tuple_class.should_receive(:prepare_database).once.with(bouillabaisse).ordered
48
- Soup.prepare
49
-
50
- gazpacho = {:database => 'tomato.db', :adapter => 'colddb'}
51
- Soup.base = gazpacho
52
- Soup.tuple_class.should_receive(:prepare_database).once.with(gazpacho).ordered
53
- Soup.prepare
54
- end
55
-
56
- it "should not allow old bases to interfere with new ones" do
57
- bouillabaisse = {:database => 'fishy.db', :adapter => 'fishdb'}
58
- Soup.base = bouillabaisse
59
- Soup.tuple_class.should_receive(:prepare_database).once.with(bouillabaisse).ordered
60
- Soup.prepare
61
-
62
- tasteless = {:database => 'water.db'}
63
- Soup.base = tasteless
64
- Soup.tuple_class.should_receive(:prepare_database).once.with(Soup::DEFAULT_CONFIG.merge(tasteless)).ordered
65
- Soup.tuple_class.should_not_receive(:prepare_database).with(bouillabaisse.merge(tasteless))
66
- Soup.prepare
67
- end
68
- end
69
-
70
- describe "when being flavoured" do
71
- before(:each) { Soup.class_eval { @database_config = nil; @tuple_class = nil } }
72
-
73
- it "should allow the soup to be flavoured" do
74
- Soup.should respond_to(:flavour=)
75
- end
76
-
77
- it "should determine the tuple class based on the flavour" do
78
- require 'soup/tuples/data_mapper_tuple'
79
- Soup.flavour = :data_mapper
80
- Soup.tuple_class.should == Soup::Tuples::DataMapperTuple
81
- end
82
-
83
- it "should allow the flavour to be set multiple times" do
84
- require 'soup/tuples/data_mapper_tuple'
85
- Soup.flavour = :data_mapper
86
- Soup.tuple_class.should == Soup::Tuples::DataMapperTuple
87
-
88
- require 'soup/tuples/sequel_tuple'
89
- Soup.flavour = :sequel
90
- Soup.tuple_class.should_not == Soup::Tuples::DataMapperTuple
91
- Soup.tuple_class.should == Soup::Tuples::SequelTuple
92
- end
93
-
94
- it "should use have no tuple class if the flavour is unknowable" do
95
- Soup.flavour = :shoggoth
96
- Soup.tuple_class.should == nil
97
- end
98
- end
99
-
100
- describe "when adding data to the Soup directly" do
101
- before(:each) do
102
- Soup.base = {:database => "soup_test.db"}
103
- Soup.flavour = :active_record
104
- Soup.prepare
105
- clear_database
106
- end
107
-
108
- it "should create a new snip" do
109
- attributes = {:name => 'monkey'}
110
- Snip.should_receive(:new).with(attributes).and_return(mock('snip', :null_object => true))
111
- Soup << attributes
112
- end
113
-
114
- it "should save the snip" do
115
- attributes = {:name => 'monkey'}
116
- Snip.should_receive(:new).with(attributes).and_return(snip = mock('snip'))
117
- snip.should_receive(:save)
118
- Soup << attributes
119
- end
120
- end
121
-
122
- describe "when sieving the soup" do
123
- before(:each) do
124
- Soup.base = {:database => "soup_test.db"}
125
- Soup.flavour = :active_record
126
- Soup.prepare
127
- clear_database
128
- @james = Soup << {:name => 'james', :spirit_guide => 'fox', :colour => 'blue', :powers => 'yes'}
129
- @murray = Soup << {:name => 'murray', :spirit_guide => 'chaffinch', :colour => 'red', :powers => 'yes'}
130
- end
131
-
132
- it "should find snips by name if the parameter is a string" do
133
- Soup['james'].should == @james
134
- end
135
-
136
- it "should find snips using exact matching of keys and values if the parameter is a hash" do
137
- Soup[:name => 'murray'].should == @murray
138
- end
139
-
140
- it "should match using all parameters" do
141
- Soup[:powers => 'yes', :colour => 'red'].should == @james
142
- end
143
-
144
- it "should return an array if more than one snip matches" do
145
- Soup[:powers => 'yes'].should == [@james, @murray]
146
- end
147
- end
148
- end
@@ -1,8 +0,0 @@
1
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), *%w[.. lib]))
2
-
3
- require 'soup'
4
- require "soup/tuples/active_record_tuple"
5
-
6
- def clear_database
7
- Soup::Tuples::ActiveRecordTuple.destroy_all
8
- end
@@ -1,12 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- # We need to do a bit of extra work here, because Snip will undefine a
4
- # whole bunch of stuff that spec adds, like the Object#should method.
5
- $LOAD_PATH.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
6
- require 'soup'
7
-
8
- # This is basically the contents of rspec's own spec runner. It's just
9
- # that we needed to require 'soup' before it.
10
- require 'rubygems'
11
- require 'spec'
12
- exit ::Spec::Runner::CommandLine.run(rspec_options)