soup 0.1.1

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.
@@ -0,0 +1,44 @@
1
+ require 'rubygems'
2
+ require 'active_record'
3
+
4
+ class Tuple < ActiveRecord::Base
5
+ def self.prepare_database
6
+ ActiveRecord::Migration.create_table :tuples, :force => true do |t|
7
+ t.column :snip_id, :integer
8
+ t.column :name, :string
9
+ t.column :value, :text
10
+ t.column :created_at, :datetime
11
+ t.column :updated_at, :datetime
12
+ end
13
+ end
14
+
15
+ def self.for_snip(id)
16
+ find_all_by_snip_id(id)
17
+ end
18
+
19
+ def self.find_matching(name, value_conditions=nil)
20
+ condition_sql = "name = '#{name}'"
21
+ condition_sql += " AND value #{value_conditions}" if value_conditions
22
+ find(:all, :conditions => condition_sql)
23
+ end
24
+
25
+ def self.all_for_snip_named(name)
26
+ id = find_by_name_and_value("name", name).snip_id
27
+ for_snip(id)
28
+ end
29
+
30
+ # TODO: *totally* not threadsafe.
31
+ def self.next_snip_id
32
+ maximum(:snip_id) + 1 rescue 1
33
+ end
34
+
35
+ def save
36
+ super if new_record? || dirty?
37
+ end
38
+
39
+ private
40
+
41
+ def dirty?
42
+ true # hmm.
43
+ end
44
+ end
@@ -0,0 +1,47 @@
1
+ require 'rubygems'
2
+ require 'data_mapper'
3
+
4
+ DataMapper::Database.setup({
5
+ :adapter => 'sqlite3',
6
+ :database => 'soup_development.db'
7
+ })
8
+
9
+ # This tuple implementation is broken - there's a weird interaction
10
+ # where values are not set within the web application.
11
+ #
12
+ class Tuple < DataMapper::Base
13
+
14
+ property :snip_id, :integer
15
+
16
+ property :name, :string
17
+ property :value, :text
18
+
19
+ property :created_at, :datetime
20
+ property :updated_at, :datetime
21
+
22
+ def self.prepare_database
23
+ DataMapper::Persistence.auto_migrate!
24
+ end
25
+
26
+ def self.for_snip(id)
27
+ all(:snip_id => id)
28
+ end
29
+
30
+ def self.all_for_snip_named(name)
31
+ id = first(:name => "name", :value => name).snip_id
32
+ for_snip(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
@@ -0,0 +1,29 @@
1
+ require 'rubygems'
2
+ require 'sequel'
3
+
4
+ DB = Sequel.sqlite 'soup_development.db'
5
+
6
+ class Tuple < Sequel::Model(:tuples)
7
+ set_schema do
8
+ primary_key :id
9
+ integer :snip_id
10
+ string :name
11
+ string :value
12
+ datetime :created_at
13
+ datetime :updated_at
14
+ end
15
+
16
+ def self.for_snip(id)
17
+ Tuple.filter(:snip_id => id).to_a
18
+ end
19
+
20
+ # TODO: *totally* not threadsafe.
21
+ def self.next_snip_id
22
+ max(:snip_id) + 1
23
+ end
24
+ end
25
+
26
+
27
+ # Create the table with this:
28
+ #
29
+ # Tuple.create_table
@@ -0,0 +1,142 @@
1
+ require 'rubygems'
2
+ require 'blankslate'
3
+
4
+ # methods called on Tuple:
5
+ # Tuple.for_snip(id)
6
+ # Tuple.find_matching(tuple_name, tuple_value_conditions)
7
+ # Tuple.all_for_snip_named(name)
8
+ # Tuple.next_snip_id
9
+ # Tuple#save
10
+ # Tuple#name
11
+ # Tuple#value
12
+ # Tuple#destroy
13
+
14
+ class Snip < BlankSlate
15
+
16
+ # Returns the snip with the given name (i.e. the snip with the tuple of "name" -> name)
17
+ #
18
+ def self.[](name)
19
+ tuples = Tuple.all_for_snip_named(name)
20
+ snip = Snip.new(:__id => tuples.first.snip_id)
21
+ snip.replace_tuples(tuples)
22
+ snip
23
+ rescue
24
+ return nil
25
+ end
26
+
27
+ # Returns all snips which match the given criteria, i.e. which have a tuple that
28
+ # matches the given conditions. For example:
29
+ #
30
+ # Snip.with(:created_at, "> '2007-01-01'")
31
+ #
32
+ # should return all Snips who have a 'created_at' value greater than '2007-01-01'.
33
+ #
34
+ def self.with(name, tuple_value_conditions=nil)
35
+ matching_tuples = Tuple.find_matching(name, tuple_value_conditions)
36
+ matching_tuples.map { |t| t.snip_id }.uniq.map { |snip_id| find(snip_id) }
37
+ end
38
+
39
+ # Returns the snip with the given ID (i.e. the collection of all tuples
40
+ # with the matching snip_id, gathered into a magical snip.)
41
+ #
42
+ def self.find(id)
43
+ raise "not found" unless (tuples = Tuple.for_snip(id)).any?
44
+ snip = Snip.new(:__id => id)
45
+ snip.replace_tuples(tuples)
46
+ snip
47
+ end
48
+
49
+ attr_reader :tuples
50
+
51
+ def initialize(attributes = {})
52
+ set_id(attributes.delete(:__id))
53
+ @tuples = {}
54
+ attributes.each { |name, value| set_value(name, value) }
55
+ end
56
+
57
+ def save
58
+ raise "Saving would be pointless - there's no data!" if @tuples.empty?
59
+ set_id_if_necessary
60
+ each_tuple { |t| t.save }
61
+ self
62
+ end
63
+
64
+ def destroy
65
+ each_tuple { |t| t.destroy }
66
+ end
67
+
68
+ def reload
69
+ return self unless self.id
70
+ replace_tuples(Tuple.for_snip(id))
71
+ self
72
+ end
73
+
74
+ def attributes
75
+ @tuples.inject({}) { |h, (name, t)| h[name] = t.value; h }
76
+ end
77
+
78
+ def replace_tuples(new_tuples)
79
+ @tuples.clear
80
+ new_tuples.each { |tuple| @tuples[tuple.name] = tuple }
81
+ end
82
+
83
+ def to_s
84
+ "<Snip id:#{self.id || "unset"} #{tuples_as_string}>"
85
+ end
86
+
87
+ def inspect
88
+ "<Snip id:#{self.id || "unset"} name:#{self.name}>"
89
+ end
90
+
91
+ def method_missing(method, *args)
92
+ value = args.length > 1 ? args : args.first
93
+ if method.to_s =~ /(.*)=\Z/ # || value - could be a nice DSL touch.
94
+ set_value($1, value)
95
+ else
96
+ get_value(method.to_s)
97
+ end
98
+ end
99
+
100
+ def id #:nodoc: why is ID special?
101
+ @id
102
+ end
103
+
104
+
105
+ private
106
+
107
+ def each_tuple
108
+ @tuples.values.each { |tuple| yield tuple }
109
+ end
110
+
111
+ def set_id(id)
112
+ @id = id
113
+ self
114
+ end
115
+
116
+ def set_id_if_necessary
117
+ if self.id.nil?
118
+ set_id(Tuple.next_snip_id)
119
+ @tuples.values.each { |tuple| tuple.snip_id = self.id }
120
+ end
121
+ end
122
+
123
+ def tuples_as_string
124
+ @tuples.inject("") { |hash, (name, tuple)| hash += " #{name}:'#{tuple.value}'" }.strip
125
+ end
126
+
127
+ def get_value(name)
128
+ @tuples[name.to_s] ? @tuples[name.to_s].value : nil
129
+ end
130
+
131
+ def set_value(name, value)
132
+ tuple = @tuples[name.to_s]
133
+ if tuple
134
+ tuple.value = value
135
+ else
136
+ attributes = {:snip_id => self.id, :name => name.to_s, :value => value}
137
+ tuple = @tuples[name.to_s] = Tuple.new(attributes)
138
+ end
139
+ tuple.value
140
+ end
141
+
142
+ end
@@ -0,0 +1,7 @@
1
+ # require 'data_mapper_tuple'
2
+ # or
3
+ require 'active_record_tuple'
4
+ # or
5
+ # require 'sequel_tuple'
6
+
7
+ require 'snip'
metadata ADDED
@@ -0,0 +1,57 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: soup
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - James Adam
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-03-04 00:00:00 +00:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: james@lazyatom.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - lib/active_record_tuple.rb
26
+ - lib/data_mapper_tuple.rb
27
+ - lib/sequel_tuple.rb
28
+ - lib/snip.rb
29
+ - lib/soup.rb
30
+ has_rdoc: false
31
+ homepage:
32
+ post_install_message:
33
+ rdoc_options: []
34
+
35
+ require_paths:
36
+ - lib
37
+ required_ruby_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: "0"
42
+ version:
43
+ required_rubygems_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: "0"
48
+ version:
49
+ requirements: []
50
+
51
+ rubyforge_project: soup
52
+ rubygems_version: 0.9.5
53
+ signing_key:
54
+ specification_version: 2
55
+ summary: I suppose it's a document database. Or a tuple store. But really, it's just data sloshing around, waiting to be used.
56
+ test_files: []
57
+