soup 0.2.1 → 0.9.9

Sign up to get free protection for your applications and to get access to all the features.
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)