rhubarb 0.2.7 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -1
- data/lib/rhubarb/classmixins.rb +34 -18
- data/lib/rhubarb/column.rb +2 -2
- data/lib/rhubarb/mixins/freshness.rb +2 -2
- data/lib/rhubarb/persisting.rb +6 -2
- data/ruby-rhubarb.spec.in +5 -1
- data/test/test_rhubarb.rb +93 -0
- metadata +37 -13
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
data/lib/rhubarb/classmixins.rb
CHANGED
@@ -19,8 +19,14 @@ module Rhubarb
|
|
19
19
|
PCM_OUTPUT_TRANSFORMERS = {:object=>Util.deswizzle_object_proc, :zblob=>Util.dezblobify_proc, }
|
20
20
|
# Returns the name of the database table modeled by this class.
|
21
21
|
# Defaults to the name of the class (sans module names)
|
22
|
-
def table_name
|
22
|
+
def table_name(quoted=false)
|
23
23
|
@table_name ||= self.name.split("::").pop.downcase
|
24
|
+
@quoted_table_name ||= "'#{@table_name}'"
|
25
|
+
quoted ? @quoted_table_name : @table_name
|
26
|
+
end
|
27
|
+
|
28
|
+
def quoted_table_name
|
29
|
+
table_name(true)
|
24
30
|
end
|
25
31
|
|
26
32
|
# Enables setting the table name to a custom name
|
@@ -56,19 +62,19 @@ module Rhubarb
|
|
56
62
|
valid_cols = self.colnames.intersection arg_hash.keys
|
57
63
|
select_criteria = valid_cols.map {|col| "#{col.to_s} = #{col.inspect}"}.join(" AND ")
|
58
64
|
arg_hash.each {|key,val| arg_hash[key] = val.row_id if val.respond_to? :row_id}
|
59
|
-
db.do_query("select * from #{
|
65
|
+
db.do_query("select * from #{quoted_table_name} where #{select_criteria} order by row_id", arg_hash) {|tup| results << self.new(tup) }
|
60
66
|
results
|
61
67
|
end
|
62
68
|
|
63
69
|
# Does what it says on the tin. Since this will allocate an object for each row, it isn't recomended for huge tables.
|
64
70
|
def find_all
|
65
71
|
results = []
|
66
|
-
db.do_query("SELECT * from #{
|
72
|
+
db.do_query("SELECT * from #{quoted_table_name}") {|tup| results << self.new(tup)}
|
67
73
|
results
|
68
74
|
end
|
69
75
|
|
70
76
|
def delete_all
|
71
|
-
db.do_query("DELETE from #{
|
77
|
+
db.do_query("DELETE from #{quoted_table_name}")
|
72
78
|
end
|
73
79
|
|
74
80
|
def delete_where(arg_hash)
|
@@ -76,7 +82,7 @@ module Rhubarb
|
|
76
82
|
valid_cols = self.colnames.intersection arg_hash.keys
|
77
83
|
select_criteria = valid_cols.map {|col| "#{col.to_s} = #{col.inspect}"}.join(" AND ")
|
78
84
|
arg_hash.each {|key,val| arg_hash[key] = val.row_id if val.respond_to? :row_id}
|
79
|
-
db.do_query("DELETE FROM #{
|
85
|
+
db.do_query("DELETE FROM #{quoted_table_name} WHERE #{select_criteria}", arg_hash)
|
80
86
|
end
|
81
87
|
|
82
88
|
# Declares a query method named +name+ and adds it to this class. The query method returns a list of objects corresponding to the rows returned by executing "+SELECT * FROM+ _table_ +WHERE+ _query_" on the database.
|
@@ -87,7 +93,7 @@ module Rhubarb
|
|
87
93
|
# Declares a custom query method named +name+, and adds it to this class. The custom query method returns a list of objects corresponding to the rows returned by executing +query+ on the database. +query+ should select all fields (with +SELECT *+). If +query+ includes the string +\_\_TABLE\_\_+, it will be expanded to the table name. Typically, you will want to use +declare\_query+ instead; this method is most useful for self-joins.
|
88
94
|
def declare_custom_query(name, query)
|
89
95
|
klass = (class << self; self end)
|
90
|
-
processed_query = query.gsub("__TABLE__", "#{self.
|
96
|
+
processed_query = query.gsub("__TABLE__", "#{self.quoted_table_name}")
|
91
97
|
|
92
98
|
klass.class_eval do
|
93
99
|
define_method name.to_s do |*args|
|
@@ -113,7 +119,7 @@ module Rhubarb
|
|
113
119
|
def declare_index_on(*fields)
|
114
120
|
@creation_callbacks << Proc.new do
|
115
121
|
idx_name = "idx_#{self.table_name}__#{fields.join('__')}__#{@creation_callbacks.size}"
|
116
|
-
creation_cmd = "create index #{idx_name} on #{self.
|
122
|
+
creation_cmd = "create index #{idx_name} on #{self.quoted_table_name} (#{fields.join(', ')})"
|
117
123
|
self.db.execute(creation_cmd)
|
118
124
|
end if fields.size > 0
|
119
125
|
end
|
@@ -127,7 +133,7 @@ module Rhubarb
|
|
127
133
|
|
128
134
|
find_method_name = "find_by_#{cname}".to_sym
|
129
135
|
find_first_method_name = "find_first_by_#{cname}".to_sym
|
130
|
-
find_query = "select * from #{
|
136
|
+
find_query = "select * from #{quoted_table_name} where \"#{cname}\" = ? order by row_id"
|
131
137
|
|
132
138
|
get_method_name = "#{cname}".to_sym
|
133
139
|
set_method_name = "#{cname}=".to_sym
|
@@ -173,8 +179,9 @@ module Rhubarb
|
|
173
179
|
xform = PCM_INPUT_TRANSFORMERS[kind]
|
174
180
|
|
175
181
|
define_method set_method_name do |arg|
|
176
|
-
|
177
|
-
|
182
|
+
new_val = xform ? xform.call(arg) : arg
|
183
|
+
@tuple["#{cname}"] = new_val
|
184
|
+
update cname, new_val
|
178
185
|
end
|
179
186
|
else
|
180
187
|
# this column references another table; create a set
|
@@ -202,15 +209,16 @@ module Rhubarb
|
|
202
209
|
# not expose the capacity to change row IDs.
|
203
210
|
|
204
211
|
rtable = rf.referent.table_name
|
212
|
+
qrtable = rf.referent.quoted_table_name
|
205
213
|
|
206
214
|
self.creation_callbacks << Proc.new do
|
207
215
|
@ccount ||= 0
|
208
216
|
|
209
217
|
insert_trigger_name, delete_trigger_name = %w{insert delete}.map {|op| "ri_#{op}_#{self.table_name}_#{@ccount}_#{rtable}" }
|
210
218
|
|
211
|
-
self.db.execute_batch("CREATE TRIGGER #{insert_trigger_name} BEFORE INSERT ON
|
219
|
+
self.db.execute_batch("CREATE TRIGGER #{insert_trigger_name} BEFORE INSERT ON #{quoted_table_name} WHEN new.\"#{cname}\" IS NOT NULL AND NOT EXISTS (SELECT 1 FROM #{qrtable} WHERE new.\"#{cname}\" == \"#{rf.column}\") BEGIN SELECT RAISE(ABORT, 'constraint #{insert_trigger_name} (#{rtable} missing foreign key row) failed'); END;")
|
212
220
|
|
213
|
-
self.db.execute_batch("CREATE TRIGGER #{delete_trigger_name} BEFORE DELETE ON
|
221
|
+
self.db.execute_batch("CREATE TRIGGER #{delete_trigger_name} BEFORE DELETE ON #{qrtable} WHEN EXISTS (SELECT 1 FROM #{quoted_table_name} WHERE old.\"#{rf.column}\" == \"#{cname}\") BEGIN DELETE FROM #{quoted_table_name} WHERE \"#{cname}\" = old.\"#{rf.column}\"; END;") if rf.options[:on_delete] == :cascade
|
214
222
|
|
215
223
|
@ccount = @ccount + 1
|
216
224
|
end
|
@@ -230,8 +238,8 @@ module Rhubarb
|
|
230
238
|
new_row = args[0]
|
231
239
|
new_row[:created] = new_row[:updated] = Util::timestamp
|
232
240
|
|
233
|
-
|
234
|
-
|
241
|
+
colspec, valspec = cvspecs[new_row.keys]
|
242
|
+
|
235
243
|
res = nil
|
236
244
|
|
237
245
|
# resolve any references in the args
|
@@ -240,7 +248,7 @@ module Rhubarb
|
|
240
248
|
new_row[column] = xform ? xform.call(value) : Util::rhubarb_fk_identity(value)
|
241
249
|
end
|
242
250
|
|
243
|
-
create_text = "insert into #{
|
251
|
+
create_text = "insert into #{quoted_table_name} (#{colspec}) values (#{valspec})"
|
244
252
|
db.do_query(create_text, new_row)
|
245
253
|
|
246
254
|
res = find(db.last_insert_row_id)
|
@@ -252,11 +260,12 @@ module Rhubarb
|
|
252
260
|
# corresponding to this class.
|
253
261
|
def table_decl
|
254
262
|
ddlspecs = [columns.join(", "), constraints.join(", ")].reject {|str| str.size==0}.join(", ")
|
255
|
-
"create table #{
|
263
|
+
"create table #{quoted_table_name} (#{ddlspecs});"
|
256
264
|
end
|
257
265
|
|
258
266
|
# Creates a table in the database corresponding to this class.
|
259
267
|
def create_table(dbkey=:default)
|
268
|
+
ensure_accessors
|
260
269
|
self.db ||= Persistence::dbs[dbkey] unless @explicitdb
|
261
270
|
self.db.execute(table_decl)
|
262
271
|
@creation_callbacks.each {|func| func.call}
|
@@ -298,16 +307,23 @@ module Rhubarb
|
|
298
307
|
|
299
308
|
# Returns the number of rows in the table backing this class
|
300
309
|
def count
|
301
|
-
self.db.get_first_value("select count(row_id) from #{
|
310
|
+
self.db.get_first_value("select count(row_id) from #{quoted_table_name}").to_i
|
302
311
|
end
|
303
312
|
|
304
313
|
def find_tuple(tid)
|
305
|
-
ft_text = "select * from #{
|
314
|
+
ft_text = "select * from #{quoted_table_name} where row_id = ?"
|
306
315
|
result = nil
|
307
316
|
db.do_query(ft_text, tid) {|tup| result = tup; break}
|
308
317
|
result
|
309
318
|
end
|
310
319
|
|
320
|
+
def cvspecs
|
321
|
+
@cvspecs ||= Hash.new do |h,new_row_keys|
|
322
|
+
cols = colnames.intersection new_row_keys
|
323
|
+
h[new_row_keys] = [Proc.new {|f| f.to_s.inspect}, Proc.new {|f| f.inspect}].map {|p| (cols.map {|col| p.call(col)}).join(", ")}
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
311
327
|
include FindFreshest
|
312
328
|
|
313
329
|
end
|
data/lib/rhubarb/column.rb
CHANGED
@@ -51,9 +51,9 @@ module Rhubarb
|
|
51
51
|
query = "
|
52
52
|
SELECT __freshest.* FROM (
|
53
53
|
SELECT #{projection.to_a.join(', ')} FROM (
|
54
|
-
SELECT * from #{
|
54
|
+
SELECT * from #{quoted_table_name} #{where_clause}
|
55
55
|
) #{group_by_clause}
|
56
|
-
) as __fresh INNER JOIN #{
|
56
|
+
) as __fresh INNER JOIN #{quoted_table_name} as __freshest ON #{join_clause} ORDER BY row_id"
|
57
57
|
self.db.execute(query, query_params).map {|tup| self.new(tup) }
|
58
58
|
end
|
59
59
|
end
|
data/lib/rhubarb/persisting.rb
CHANGED
@@ -23,6 +23,10 @@ module Rhubarb
|
|
23
23
|
attr_reader :created
|
24
24
|
attr_reader :updated
|
25
25
|
end
|
26
|
+
|
27
|
+
other.instance_eval do
|
28
|
+
private_class_method :ensure_accessors
|
29
|
+
end
|
26
30
|
end
|
27
31
|
|
28
32
|
def db
|
@@ -62,7 +66,7 @@ module Rhubarb
|
|
62
66
|
# Deletes the row corresponding to this object from the database;
|
63
67
|
# invalidates =self= and any other objects backed by this row
|
64
68
|
def delete
|
65
|
-
db.do_query("delete from #{self.class.
|
69
|
+
db.do_query("delete from #{self.class.quoted_table_name} where row_id = ?", @row_id)
|
66
70
|
mark_dirty
|
67
71
|
@tuple = nil
|
68
72
|
@row_id = nil
|
@@ -119,7 +123,7 @@ module Rhubarb
|
|
119
123
|
def update(attr_name, value)
|
120
124
|
mark_dirty
|
121
125
|
|
122
|
-
db.do_query("update #{self.class.
|
126
|
+
db.do_query("update #{self.class.quoted_table_name} set '#{attr_name}' = ?, updated = ? where row_id = ?", value, Util::timestamp, @row_id)
|
123
127
|
end
|
124
128
|
|
125
129
|
# Resolve any fields that reference other tables, replacing row ids with referred objects
|
data/ruby-rhubarb.spec.in
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
%{!?ruby_sitelib: %global ruby_sitelib %(ruby -rrbconfig -e 'puts Config::CONFIG["sitelibdir"] ')}
|
2
|
-
%define rel 1
|
2
|
+
%define rel 1
|
3
3
|
|
4
4
|
Summary: Simple versioned object-graph persistence layer
|
5
5
|
Name: ruby-rhubarb
|
@@ -48,6 +48,10 @@ rm -rf %{buildroot}
|
|
48
48
|
%{ruby_sitelib}/rhubarb/persistence.rb
|
49
49
|
|
50
50
|
%changelog
|
51
|
+
* Wed Sep 15 2010 willb <willb@redhat> - 0.2.7-1
|
52
|
+
- Updated to version 0.2.7
|
53
|
+
- fixed problems with hash-valued params in custom queries
|
54
|
+
|
51
55
|
* Wed Apr 28 2010 willb <willb@redhat> - 0.2.6-1.0
|
52
56
|
- Updated to version 0.2.6
|
53
57
|
|
data/test/test_rhubarb.rb
CHANGED
@@ -119,6 +119,25 @@ class ObjectTestTable
|
|
119
119
|
declare_column :obj, :object
|
120
120
|
end
|
121
121
|
|
122
|
+
class NoColumnsTestTable
|
123
|
+
include Rhubarb::Persisting
|
124
|
+
end
|
125
|
+
|
126
|
+
class Create
|
127
|
+
include Rhubarb::Persisting
|
128
|
+
declare_column :name, :string
|
129
|
+
end
|
130
|
+
|
131
|
+
class Group
|
132
|
+
include Rhubarb::Persisting
|
133
|
+
declare_column :other, :int, references(Create, :on_delete=>:cascade)
|
134
|
+
end
|
135
|
+
|
136
|
+
class Order
|
137
|
+
include Rhubarb::Persisting
|
138
|
+
declare_column :group, :int
|
139
|
+
end
|
140
|
+
|
122
141
|
class PreparedStmtBackendTests < Test::Unit::TestCase
|
123
142
|
def dbfile
|
124
143
|
ENV['RHUBARB_TEST_DB'] || ":memory:"
|
@@ -159,6 +178,44 @@ class PreparedStmtBackendTests < Test::Unit::TestCase
|
|
159
178
|
Rhubarb::Persistence::close()
|
160
179
|
end
|
161
180
|
|
181
|
+
def test_reserved_word_table
|
182
|
+
assert_nothing_raised do
|
183
|
+
Create.create_table
|
184
|
+
Create.create(:name=>"bar")
|
185
|
+
c = Create.find_first_by_name("bar")
|
186
|
+
c.name = "blah"
|
187
|
+
c.name = "bar"
|
188
|
+
assert(c.name == "bar")
|
189
|
+
c.delete
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def test_reserved_word_column
|
194
|
+
assert_nothing_raised do
|
195
|
+
Order.create_table
|
196
|
+
Order.create(:group=>42)
|
197
|
+
o = Order.find_first_by_group(42)
|
198
|
+
o.group = 37
|
199
|
+
assert(o.group == 37)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def test_reserved_word_fk_table
|
204
|
+
assert_nothing_raised do
|
205
|
+
Create.create_table
|
206
|
+
Group.create_table
|
207
|
+
Create.create(:name=>"bar")
|
208
|
+
Group.create(:other=>Create.create(:name=>"blah"))
|
209
|
+
assert(Group.find_all[0].other.name=="blah")
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def test_no_columns_table
|
214
|
+
assert_nothing_raised do
|
215
|
+
NoColumnsTestTable.create_table
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
162
219
|
def test_persistence_setup
|
163
220
|
assert Rhubarb::Persistence::db.type_translation, "type translation not enabled for db"
|
164
221
|
assert Rhubarb::Persistence::db.results_as_hash, "rows-as-hashes not enabled for db"
|
@@ -833,6 +890,42 @@ class PreparedStmtBackendTests < Test::Unit::TestCase
|
|
833
890
|
assert_equal(text, Zlib::Inflate.inflate(cbtrow.info))
|
834
891
|
end
|
835
892
|
|
893
|
+
def test_mutable_object_data
|
894
|
+
things = []
|
895
|
+
|
896
|
+
words = %w{It is a truth universally acknowledged that a single man in possession of a good fortune must be in want of a wife}
|
897
|
+
|
898
|
+
100.times do
|
899
|
+
things << words.sort_by {rand}
|
900
|
+
end
|
901
|
+
|
902
|
+
things.each_with_index do |thing, idx|
|
903
|
+
ObjectTestTable.create(:otype=>thing.class.name.to_s, :obj=>thing)
|
904
|
+
end
|
905
|
+
|
906
|
+
otts = ObjectTestTable.find_all
|
907
|
+
|
908
|
+
assert_equal otts.size, things.size
|
909
|
+
|
910
|
+
things.zip(otts) do |thing, ott|
|
911
|
+
assert_equal thing.class.name.to_s, ott.otype
|
912
|
+
assert_equal thing, ott.obj
|
913
|
+
end
|
914
|
+
|
915
|
+
otts.each_with_index do |ott, i|
|
916
|
+
obj = ott.obj.reverse
|
917
|
+
ott.obj = obj.dup
|
918
|
+
assert_equal obj, ott.obj
|
919
|
+
assert_equal obj, ObjectTestTable.find(ott.row_id).obj
|
920
|
+
things[i].reverse!
|
921
|
+
end
|
922
|
+
|
923
|
+
things.zip(otts) do |thing, ott|
|
924
|
+
assert_equal thing.class.name.to_s, ott.otype
|
925
|
+
assert_equal thing, ott.obj
|
926
|
+
end
|
927
|
+
end
|
928
|
+
|
836
929
|
|
837
930
|
def test_object_data
|
838
931
|
things = []
|
metadata
CHANGED
@@ -1,7 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rhubarb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
hash: 19
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 3
|
9
|
+
- 0
|
10
|
+
version: 0.3.0
|
5
11
|
platform: ruby
|
6
12
|
authors:
|
7
13
|
- William Benton
|
@@ -9,29 +15,41 @@ autorequire:
|
|
9
15
|
bindir: bin
|
10
16
|
cert_chain: []
|
11
17
|
|
12
|
-
date:
|
18
|
+
date: 2011-02-15 00:00:00 -06:00
|
13
19
|
default_executable:
|
14
20
|
dependencies:
|
15
21
|
- !ruby/object:Gem::Dependency
|
16
22
|
name: rspec
|
17
|
-
|
18
|
-
|
19
|
-
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
20
26
|
requirements:
|
21
27
|
- - ">="
|
22
28
|
- !ruby/object:Gem::Version
|
29
|
+
hash: 13
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 2
|
33
|
+
- 9
|
23
34
|
version: 1.2.9
|
24
|
-
|
35
|
+
type: :development
|
36
|
+
version_requirements: *id001
|
25
37
|
- !ruby/object:Gem::Dependency
|
26
38
|
name: sqlite3-ruby
|
27
|
-
|
28
|
-
|
29
|
-
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
30
42
|
requirements:
|
31
43
|
- - ">="
|
32
44
|
- !ruby/object:Gem::Version
|
45
|
+
hash: 27
|
46
|
+
segments:
|
47
|
+
- 1
|
48
|
+
- 2
|
49
|
+
- 2
|
33
50
|
version: 1.2.2
|
34
|
-
|
51
|
+
type: :runtime
|
52
|
+
version_requirements: *id002
|
35
53
|
description: Rhubarb is a simple object-graph persistence library implemented as a mixin. It also works with the SPQR library for straightforward object publishing over QMF.
|
36
54
|
email: willb@redhat.com
|
37
55
|
executables: []
|
@@ -72,21 +90,27 @@ rdoc_options:
|
|
72
90
|
require_paths:
|
73
91
|
- lib
|
74
92
|
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
none: false
|
75
94
|
requirements:
|
76
95
|
- - ">="
|
77
96
|
- !ruby/object:Gem::Version
|
97
|
+
hash: 3
|
98
|
+
segments:
|
99
|
+
- 0
|
78
100
|
version: "0"
|
79
|
-
version:
|
80
101
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
none: false
|
81
103
|
requirements:
|
82
104
|
- - ">="
|
83
105
|
- !ruby/object:Gem::Version
|
106
|
+
hash: 3
|
107
|
+
segments:
|
108
|
+
- 0
|
84
109
|
version: "0"
|
85
|
-
version:
|
86
110
|
requirements: []
|
87
111
|
|
88
112
|
rubyforge_project:
|
89
|
-
rubygems_version: 1.3.
|
113
|
+
rubygems_version: 1.3.7
|
90
114
|
signing_key:
|
91
115
|
specification_version: 3
|
92
116
|
summary: "Rhubarb: object graph persistence, easy as pie"
|