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.
- data/lib/active_record_tuple.rb +44 -0
- data/lib/data_mapper_tuple.rb +47 -0
- data/lib/sequel_tuple.rb +29 -0
- data/lib/snip.rb +142 -0
- data/lib/soup.rb +7 -0
- metadata +57 -0
@@ -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
|
data/lib/sequel_tuple.rb
ADDED
@@ -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
|
data/lib/snip.rb
ADDED
@@ -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
|
data/lib/soup.rb
ADDED
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
|
+
|