graffiti 2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +676 -0
- data/ChangeLog.mtn +233 -0
- data/README.rdoc +129 -0
- data/TODO +30 -0
- data/doc/diagrams/graffiti-classes.svg +157 -0
- data/doc/diagrams/graffiti-deployment.svg +117 -0
- data/doc/diagrams/graffiti-store-sequence.svg +69 -0
- data/doc/diagrams/squish-select-sequence.svg +266 -0
- data/doc/examples/samizdat-rdf-config.yaml +77 -0
- data/doc/examples/samizdat-triggers-pgsql.sql +266 -0
- data/doc/papers/collreif.tex +462 -0
- data/doc/papers/rdf-to-relational-query-translation-icis2009.tex +936 -0
- data/doc/papers/rel-rdf.tex +545 -0
- data/doc/rdf-impl-report.txt +126 -0
- data/graffiti.gemspec +21 -0
- data/lib/graffiti.rb +15 -0
- data/lib/graffiti/debug.rb +34 -0
- data/lib/graffiti/exceptions.rb +20 -0
- data/lib/graffiti/rdf_config.rb +78 -0
- data/lib/graffiti/rdf_property_map.rb +92 -0
- data/lib/graffiti/sql_mapper.rb +916 -0
- data/lib/graffiti/squish.rb +568 -0
- data/lib/graffiti/store.rb +100 -0
- data/setup.rb +1360 -0
- data/test/ts_graffiti.rb +455 -0
- metadata +122 -0
data/test/ts_graffiti.rb
ADDED
@@ -0,0 +1,455 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Graffiti RDF Store tests
|
4
|
+
#
|
5
|
+
# Copyright (c) 2002-2009 Dmitry Borodaenko <angdraug@debian.org>
|
6
|
+
#
|
7
|
+
# This program is free software.
|
8
|
+
# You can distribute/modify this program under the terms of
|
9
|
+
# the GNU General Public License version 3 or later.
|
10
|
+
#
|
11
|
+
# vim: et sw=2 sts=2 ts=8 tw=0
|
12
|
+
|
13
|
+
require 'test/unit'
|
14
|
+
require 'yaml'
|
15
|
+
require 'sequel'
|
16
|
+
require 'graffiti'
|
17
|
+
|
18
|
+
include Graffiti
|
19
|
+
|
20
|
+
class TC_Storage < Test::Unit::TestCase
|
21
|
+
|
22
|
+
def setup
|
23
|
+
config = File.open(
|
24
|
+
File.join(
|
25
|
+
File.dirname(File.dirname(__FILE__)),
|
26
|
+
'doc', 'examples', 'samizdat-rdf-config.yaml'
|
27
|
+
)
|
28
|
+
) {|f| YAML.load(f.read) }
|
29
|
+
@db = create_mock_db
|
30
|
+
@store = Store.new(@db, config)
|
31
|
+
@ns = @store.config.ns
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_query_select
|
35
|
+
squish = %{
|
36
|
+
SELECT ?msg, ?title, ?name, ?date, ?rating
|
37
|
+
WHERE (dc::title ?msg ?title)
|
38
|
+
(dc::creator ?msg ?creator)
|
39
|
+
(s::fullName ?creator ?name)
|
40
|
+
(dc::date ?msg ?date)
|
41
|
+
(rdf::subject ?stmt ?msg)
|
42
|
+
(rdf::predicate ?stmt dc::relation)
|
43
|
+
(rdf::object ?stmt s::Quality)
|
44
|
+
(s::rating ?stmt ?rating)
|
45
|
+
LITERAL ?rating >= -1
|
46
|
+
ORDER BY ?rating DESC
|
47
|
+
USING PRESET NS}
|
48
|
+
|
49
|
+
sql = "SELECT DISTINCT c.id AS msg, c.title AS title, b.full_name AS name, d.published_date AS date, a.rating AS rating
|
50
|
+
FROM statement AS a
|
51
|
+
INNER JOIN message AS c ON (c.id = a.subject)
|
52
|
+
INNER JOIN member AS b ON (c.creator = b.id)
|
53
|
+
INNER JOIN resource AS d ON (c.id = d.id)
|
54
|
+
INNER JOIN resource AS e ON (a.object = e.id) AND (e.uriref = 't' AND e.label = 'http://www.nongnu.org/samizdat/rdf/schema#Quality')
|
55
|
+
INNER JOIN resource AS f ON (a.predicate = f.id) AND (f.uriref = 't' AND f.label = 'http://purl.org/dc/elements/1.1/relation')
|
56
|
+
WHERE (a.id IS NOT NULL)
|
57
|
+
AND (d.published_date IS NOT NULL)
|
58
|
+
AND (b.full_name IS NOT NULL)
|
59
|
+
AND (c.title IS NOT NULL)
|
60
|
+
AND (a.rating >= -1)
|
61
|
+
ORDER BY a.rating DESC"
|
62
|
+
|
63
|
+
test_squish_select(squish, sql) do |query|
|
64
|
+
assert_equal %w[?msg ?title ?name ?date ?rating], query.nodes
|
65
|
+
assert query.pattern.include?(["#{@ns['dc']}title", "?msg", "?title", nil, false])
|
66
|
+
assert_equal '?rating >= -1', query.literal
|
67
|
+
assert_equal '?rating', query.order
|
68
|
+
assert_equal 'DESC', query.order_dir
|
69
|
+
assert_equal @ns['s'], query.ns['s']
|
70
|
+
end
|
71
|
+
|
72
|
+
assert_equal [], @store.select_all(squish)
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_query_assert
|
76
|
+
# initialize
|
77
|
+
query_text = %{
|
78
|
+
INSERT ?msg
|
79
|
+
UPDATE ?title = 'Test Message', ?content = 'Some ''text''.'
|
80
|
+
WHERE (dc::creator ?msg 1)
|
81
|
+
(dc::title ?msg ?title)
|
82
|
+
(s::content ?msg ?content)
|
83
|
+
USING dc FOR #{@ns['dc']}
|
84
|
+
s FOR #{@ns['s']}}
|
85
|
+
begin
|
86
|
+
query = SquishAssert.new(@store.config, query_text)
|
87
|
+
rescue
|
88
|
+
assert false, "SquishAssert initialization raised #{$!.class}: #{$!}"
|
89
|
+
end
|
90
|
+
|
91
|
+
# query parser
|
92
|
+
assert_equal ['?msg'], query.insert
|
93
|
+
assert_equal({'?title' => "'0'", '?content' => "'1'"}, query.update)
|
94
|
+
assert query.pattern.include?(["#{@ns['dc']}title", "?msg", "?title", nil, false])
|
95
|
+
assert_equal @ns['s'], query.ns['s']
|
96
|
+
assert_equal "'Test Message'", query.substitute_literals("'0'")
|
97
|
+
assert_equal "'Some ''text''.'", query.substitute_literals("'1'")
|
98
|
+
|
99
|
+
# mock db
|
100
|
+
ids = @store.assert(query_text)
|
101
|
+
assert_equal [1], ids
|
102
|
+
assert_equal 'Test Message', @db[:Message][:id => 1][:title]
|
103
|
+
|
104
|
+
id2 = @store.assert(query_text)
|
105
|
+
query_text = %{
|
106
|
+
UPDATE ?rating = :rating
|
107
|
+
WHERE (rdf::subject ?stmt :related)
|
108
|
+
(rdf::predicate ?stmt dc::relation)
|
109
|
+
(rdf::object ?stmt 1)
|
110
|
+
(s::voteProposition ?vote ?stmt)
|
111
|
+
(s::voteMember ?vote :member)
|
112
|
+
(s::voteRating ?vote ?rating)}
|
113
|
+
params = {:rating => -1, :related => 2, :member => 3}
|
114
|
+
ids = @store.assert(query_text, params)
|
115
|
+
assert_equal [], ids
|
116
|
+
assert vote = @db[:vote].order(:id).last
|
117
|
+
assert_equal -1, vote[:rating].to_i
|
118
|
+
|
119
|
+
params[:rating] = -2
|
120
|
+
@store.assert(query_text, params)
|
121
|
+
assert vote2 = @db[:vote].order(:id).last
|
122
|
+
assert_equal -2, vote2[:rating].to_i
|
123
|
+
assert_equal vote[:id], vote2[:id]
|
124
|
+
end
|
125
|
+
|
126
|
+
def test_query_assert_expression
|
127
|
+
query_text = %{
|
128
|
+
UPDATE ?rating = 2 * :rating
|
129
|
+
WHERE (rdf::subject ?stmt :related)
|
130
|
+
(rdf::predicate ?stmt dc::relation)
|
131
|
+
(rdf::object ?stmt 1)
|
132
|
+
(s::voteProposition ?vote ?stmt)
|
133
|
+
(s::voteMember ?vote :member)
|
134
|
+
(s::voteRating ?vote ?rating)}
|
135
|
+
params = {:rating => -1, :related => 2, :member => 3}
|
136
|
+
@store.assert(query_text, params)
|
137
|
+
assert vote = @db[:vote].order(:id).last
|
138
|
+
assert_equal -2, vote[:rating].to_i
|
139
|
+
end
|
140
|
+
private :test_query_assert_expression
|
141
|
+
|
142
|
+
def test_dangling_blank_node
|
143
|
+
squish = %{
|
144
|
+
SELECT ?msg
|
145
|
+
WHERE (s::inReplyTo ?msg ?parent)
|
146
|
+
USING s FOR #{@ns['s']}}
|
147
|
+
|
148
|
+
sql = "SELECT DISTINCT a.id AS msg
|
149
|
+
FROM resource AS a
|
150
|
+
INNER JOIN resource AS b ON (a.part_of_subproperty = b.id) AND (b.uriref = 't' AND b.label = 'http://www.nongnu.org/samizdat/rdf/schema#inReplyTo')
|
151
|
+
WHERE (a.id IS NOT NULL)"
|
152
|
+
|
153
|
+
test_squish_select(squish, sql) do |query|
|
154
|
+
assert_equal %w[?msg], query.nodes
|
155
|
+
assert query.pattern.include?(["#{@ns['s']}inReplyTo", "?msg", "?parent", nil, false])
|
156
|
+
assert_equal @ns['s'], query.ns['s']
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def test_external_resource_no_self_join
|
161
|
+
squish = %{SELECT ?id WHERE (s::id tag::Translation ?id)}
|
162
|
+
|
163
|
+
sql = "SELECT DISTINCT a.id AS id
|
164
|
+
FROM resource AS a
|
165
|
+
WHERE (a.id IS NOT NULL)
|
166
|
+
AND ((a.uriref = 't' AND a.label = 'http://www.nongnu.org/samizdat/rdf/tag#Translation'))"
|
167
|
+
|
168
|
+
test_squish_select(squish, sql) do |query|
|
169
|
+
assert_equal %w[?id], query.nodes
|
170
|
+
assert query.pattern.include?(["#{@ns['s']}id", "#{@ns['tag']}Translation", "?id", nil, false])
|
171
|
+
assert_equal @ns['s'], query.ns['s']
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
#def test_internal_resource
|
176
|
+
#end
|
177
|
+
|
178
|
+
#def test_external_subject_internal_property
|
179
|
+
#end
|
180
|
+
|
181
|
+
def test_except
|
182
|
+
squish = %{
|
183
|
+
SELECT ?msg
|
184
|
+
WHERE (dc::date ?msg ?date)
|
185
|
+
EXCEPT (s::inReplyTo ?msg ?parent)
|
186
|
+
(dct::isVersionOf ?msg ?version_of)
|
187
|
+
(dc::creator ?version_of 1)
|
188
|
+
ORDER BY ?date DESC}
|
189
|
+
|
190
|
+
sql = "SELECT DISTINCT b.id AS msg, b.published_date AS date
|
191
|
+
FROM resource AS b
|
192
|
+
LEFT JOIN (
|
193
|
+
SELECT b.id AS _field_f
|
194
|
+
FROM message AS a
|
195
|
+
INNER JOIN resource AS b ON (b.part_of = a.id)
|
196
|
+
INNER JOIN resource AS c ON (b.part_of_subproperty = c.id) AND (c.uriref = 't' AND c.label = 'http://purl.org/dc/terms/isVersionOf')
|
197
|
+
WHERE (a.creator = 1)
|
198
|
+
) AS _subquery_a ON (b.id = _subquery_a._field_f)
|
199
|
+
LEFT JOIN resource AS d ON (b.part_of_subproperty = d.id) AND (d.uriref = 't' AND d.label = 'http://www.nongnu.org/samizdat/rdf/schema#inReplyTo')
|
200
|
+
WHERE (b.published_date IS NOT NULL)
|
201
|
+
AND (b.id IS NOT NULL)
|
202
|
+
AND (_subquery_a._field_f IS NULL)
|
203
|
+
AND (d.id IS NULL)
|
204
|
+
ORDER BY b.published_date DESC"
|
205
|
+
|
206
|
+
test_squish_select(squish, sql)
|
207
|
+
end
|
208
|
+
|
209
|
+
def test_except_group_by
|
210
|
+
squish = %{
|
211
|
+
SELECT ?msg
|
212
|
+
WHERE (rdf::predicate ?stmt dc::relation)
|
213
|
+
(rdf::subject ?stmt ?msg)
|
214
|
+
(rdf::object ?stmt ?tag)
|
215
|
+
(dc::date ?stmt ?date)
|
216
|
+
(s::rating ?stmt ?rating FILTER ?rating >= 1.5)
|
217
|
+
(s::hidden ?msg ?hidden FILTER ?hidden = 'f')
|
218
|
+
EXCEPT (dct::isPartOf ?msg ?parent)
|
219
|
+
GROUP BY ?msg
|
220
|
+
ORDER BY max(?date) DESC}
|
221
|
+
|
222
|
+
sql = "SELECT DISTINCT a.subject AS msg, max(b.published_date)
|
223
|
+
FROM statement AS a
|
224
|
+
INNER JOIN resource AS b ON (a.id = b.id)
|
225
|
+
INNER JOIN message AS c ON (a.subject = c.id) AND (c.hidden = 'f')
|
226
|
+
INNER JOIN resource AS d ON (a.subject = d.id)
|
227
|
+
INNER JOIN resource AS e ON (a.predicate = e.id) AND (e.uriref = 't' AND e.label = 'http://purl.org/dc/elements/1.1/relation')
|
228
|
+
WHERE (c.hidden IS NOT NULL)
|
229
|
+
AND (b.published_date IS NOT NULL)
|
230
|
+
AND (a.object IS NOT NULL)
|
231
|
+
AND (a.rating IS NOT NULL)
|
232
|
+
AND (d.part_of IS NULL)
|
233
|
+
AND ((a.rating >= 1.5))
|
234
|
+
GROUP BY a.subject
|
235
|
+
ORDER BY max(b.published_date) DESC"
|
236
|
+
|
237
|
+
test_squish_select(squish, sql)
|
238
|
+
end
|
239
|
+
|
240
|
+
def test_optional
|
241
|
+
squish = %{
|
242
|
+
SELECT ?date, ?creator, ?lang, ?parent, ?version_of, ?hidden, ?open
|
243
|
+
WHERE (dc::date 1 ?date)
|
244
|
+
OPTIONAL (dc::creator 1 ?creator)
|
245
|
+
(dc::language 1 ?lang)
|
246
|
+
(s::inReplyTo 1 ?parent)
|
247
|
+
(dct::isVersionOf 1 ?version_of)
|
248
|
+
(s::hidden 1 ?hidden)
|
249
|
+
(s::openForAll 1 ?open)}
|
250
|
+
|
251
|
+
sql = "SELECT DISTINCT a.published_date AS date, b.creator AS creator, b.language AS lang, select_subproperty(a.part_of, d.id) AS parent, select_subproperty(a.part_of, c.id) AS version_of, b.hidden AS hidden, b.open AS open
|
252
|
+
FROM resource AS a
|
253
|
+
INNER JOIN message AS b ON (a.id = b.id)
|
254
|
+
LEFT JOIN resource AS c ON (a.part_of_subproperty = c.id) AND (c.uriref = 't' AND c.label = 'http://purl.org/dc/terms/isVersionOf')
|
255
|
+
LEFT JOIN resource AS d ON (a.part_of_subproperty = d.id) AND (d.uriref = 't' AND d.label = 'http://www.nongnu.org/samizdat/rdf/schema#inReplyTo')
|
256
|
+
WHERE (a.published_date IS NOT NULL)
|
257
|
+
AND ((a.id = 1))"
|
258
|
+
|
259
|
+
test_squish_select(squish, sql)
|
260
|
+
end
|
261
|
+
|
262
|
+
def test_except_optional_transitive
|
263
|
+
squish = %{
|
264
|
+
SELECT ?msg
|
265
|
+
WHERE (rdf::subject ?stmt ?msg)
|
266
|
+
(rdf::predicate ?stmt dc::relation)
|
267
|
+
(rdf::object ?stmt ?tag)
|
268
|
+
(s::rating ?stmt ?rating FILTER ?rating > 0)
|
269
|
+
(dc::date ?msg ?date)
|
270
|
+
EXCEPT (dct::isPartOf ?msg ?parent)
|
271
|
+
OPTIONAL (dct::isPartOf ?tag ?supertag TRANSITIVE)
|
272
|
+
LITERAL ?tag = 1 OR ?supertag = 1
|
273
|
+
ORDER BY ?date DESC}
|
274
|
+
|
275
|
+
sql = "SELECT DISTINCT a.subject AS msg, b.published_date AS date
|
276
|
+
FROM statement AS a
|
277
|
+
INNER JOIN resource AS b ON (a.subject = b.id)
|
278
|
+
INNER JOIN resource AS d ON (a.predicate = d.id) AND (d.uriref = 't' AND d.label = 'http://purl.org/dc/elements/1.1/relation')
|
279
|
+
LEFT JOIN part AS c ON (a.object = c.id)
|
280
|
+
WHERE (a.id IS NOT NULL)
|
281
|
+
AND (b.published_date IS NOT NULL)
|
282
|
+
AND (a.rating IS NOT NULL)
|
283
|
+
AND (b.part_of IS NULL)
|
284
|
+
AND ((a.rating > 0))
|
285
|
+
AND (a.object = 1 OR c.part_of = 1)
|
286
|
+
ORDER BY b.published_date DESC"
|
287
|
+
|
288
|
+
test_squish_select(squish, sql)
|
289
|
+
end
|
290
|
+
|
291
|
+
def test_optional_connect_by_object
|
292
|
+
squish = %{
|
293
|
+
SELECT ?event
|
294
|
+
WHERE (ical::dtstart ?event ?dtstart FILTER ?dtstart >= 'now')
|
295
|
+
(ical::dtend ?event ?dtend)
|
296
|
+
OPTIONAL (s::rruleEvent ?rrule ?event)
|
297
|
+
(ical::until ?rrule ?until FILTER ?until IS NULL OR ?until > 'now')
|
298
|
+
LITERAL ?dtend > 'now' OR ?rrule IS NOT NULL
|
299
|
+
ORDER BY ?event DESC}
|
300
|
+
|
301
|
+
sql = "SELECT DISTINCT b.id AS event
|
302
|
+
FROM event AS b
|
303
|
+
LEFT JOIN recurrence AS a ON (b.id = a.event) AND (a.until IS NULL OR a.until > 'now')
|
304
|
+
WHERE (b.dtstart IS NOT NULL)
|
305
|
+
AND ((b.dtstart >= 'now'))
|
306
|
+
AND (b.dtend > 'now' OR a.id IS NOT NULL)
|
307
|
+
ORDER BY b.id DESC"
|
308
|
+
|
309
|
+
test_squish_select(squish, sql)
|
310
|
+
end
|
311
|
+
private :test_optional_connect_by_object
|
312
|
+
|
313
|
+
def test_many_to_many
|
314
|
+
# pretend that Vote is a many-to-many relation table
|
315
|
+
squish = %{
|
316
|
+
SELECT ?p, ?date
|
317
|
+
WHERE (s::voteRating ?p ?vote1 FILTER ?vote1 > 0)
|
318
|
+
(s::voteRating ?p ?vote2 FILTER ?vote2 < 0)
|
319
|
+
(dc::date ?p ?date)
|
320
|
+
ORDER BY ?date DESC}
|
321
|
+
|
322
|
+
sql = "SELECT DISTINCT a.id AS p, c.published_date AS date
|
323
|
+
FROM vote AS a
|
324
|
+
INNER JOIN vote AS b ON (a.id = b.id) AND (b.rating < 0)
|
325
|
+
INNER JOIN resource AS c ON (a.id = c.id)
|
326
|
+
WHERE (c.published_date IS NOT NULL)
|
327
|
+
AND (a.rating IS NOT NULL)
|
328
|
+
AND (b.rating IS NOT NULL)
|
329
|
+
AND ((a.rating > 0))
|
330
|
+
ORDER BY c.published_date DESC"
|
331
|
+
|
332
|
+
test_squish_select(squish, sql)
|
333
|
+
end
|
334
|
+
|
335
|
+
def test_update_null_and_subproperty
|
336
|
+
query_text =
|
337
|
+
%{INSERT ?msg
|
338
|
+
UPDATE ?parent = :parent
|
339
|
+
WHERE (dct::isPartOf ?msg ?parent)}
|
340
|
+
@store.assert(query_text, :id => 1, :parent => 3)
|
341
|
+
assert_equal 3, @db[:resource].filter(:id => 1).get(:part_of)
|
342
|
+
|
343
|
+
# check that subproperty is set
|
344
|
+
query_text =
|
345
|
+
%{UPDATE ?parent = :parent
|
346
|
+
WHERE (s::subTagOf :id ?parent)}
|
347
|
+
@store.assert(query_text, :id => 1, :parent => 3)
|
348
|
+
assert_equal 3, @db[:resource].filter(:id => 1).get(:part_of)
|
349
|
+
assert_equal 2, @db[:resource].filter(:id => 1).get(:part_of_subproperty)
|
350
|
+
|
351
|
+
# check that NULL is handled correctly and that subproperty is unset
|
352
|
+
query_text =
|
353
|
+
%{UPDATE ?parent = NULL
|
354
|
+
WHERE (dct::isPartOf :id ?parent)}
|
355
|
+
@store.assert(query_text, :id => 1)
|
356
|
+
assert_equal nil, @db[:resource].filter(:id => 1).get(:part_of)
|
357
|
+
assert_equal nil, @db[:resource].filter(:id => 1).get(:part_of_subproperty)
|
358
|
+
end
|
359
|
+
|
360
|
+
private
|
361
|
+
|
362
|
+
def test_squish_select(squish, sql)
|
363
|
+
begin
|
364
|
+
query = SquishSelect.new(@store.config, squish)
|
365
|
+
rescue
|
366
|
+
assert false, "SquishSelect initialization raised #{$!.class}: #{$!}"
|
367
|
+
end
|
368
|
+
|
369
|
+
yield query if block_given?
|
370
|
+
|
371
|
+
# query result
|
372
|
+
begin
|
373
|
+
sql1 = @store.select(query)
|
374
|
+
rescue
|
375
|
+
assert false, "select with pre-parsed query raised #{$!.class}: #{$!}"
|
376
|
+
end
|
377
|
+
begin
|
378
|
+
sql2 = @store.select(squish)
|
379
|
+
rescue
|
380
|
+
assert false, "select with query text raised #{$!.class}: #{$!}"
|
381
|
+
end
|
382
|
+
assert sql1 == sql2
|
383
|
+
|
384
|
+
# transform result
|
385
|
+
assert_equal normalize(sql), normalize(sql1),
|
386
|
+
"Query doesn't match. Expected:\n#{sql}\nReceived:\n#{sql1}"
|
387
|
+
end
|
388
|
+
|
389
|
+
def normalize(sql)
|
390
|
+
# alias labels and where conditions may be reordered, but the query string
|
391
|
+
# length should remain the same
|
392
|
+
sql.size
|
393
|
+
end
|
394
|
+
|
395
|
+
def create_mock_db
|
396
|
+
db = Sequel.sqlite(:quote_identifiers => false)
|
397
|
+
|
398
|
+
db.create_table(:resource) do
|
399
|
+
primary_key :id
|
400
|
+
Time :published_date
|
401
|
+
Integer :part_of
|
402
|
+
Integer :part_of_subproperty
|
403
|
+
Integer :part_sequence_number
|
404
|
+
TrueClass :literal
|
405
|
+
TrueClass :uriref
|
406
|
+
String :label
|
407
|
+
end
|
408
|
+
|
409
|
+
db.create_table(:statement) do
|
410
|
+
primary_key :id
|
411
|
+
Integer :subject
|
412
|
+
Integer :predicate
|
413
|
+
Integer :object
|
414
|
+
BigDecimal :rating, :size => [4, 2]
|
415
|
+
end
|
416
|
+
|
417
|
+
db.create_table(:member) do
|
418
|
+
primary_key :id
|
419
|
+
String :login
|
420
|
+
String :full_name
|
421
|
+
String :email
|
422
|
+
end
|
423
|
+
|
424
|
+
db.create_table(:message) do
|
425
|
+
primary_key :id
|
426
|
+
String :title
|
427
|
+
Integer :creator
|
428
|
+
String :format
|
429
|
+
String :language
|
430
|
+
TrueClass :open
|
431
|
+
TrueClass :hidden
|
432
|
+
TrueClass :locked
|
433
|
+
String :content
|
434
|
+
String :html_full
|
435
|
+
String :html_short
|
436
|
+
end
|
437
|
+
|
438
|
+
db.create_table(:vote) do
|
439
|
+
primary_key :id
|
440
|
+
Integer :proposition
|
441
|
+
Integer :member
|
442
|
+
BigDecimal :rating, :size => 2
|
443
|
+
end
|
444
|
+
|
445
|
+
db
|
446
|
+
end
|
447
|
+
|
448
|
+
def create_mock_member(db)
|
449
|
+
db[:member].insert(
|
450
|
+
:login => 'test',
|
451
|
+
:full_name => 'test',
|
452
|
+
:email => 'test@localhost'
|
453
|
+
)
|
454
|
+
end
|
455
|
+
end
|