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 CHANGED
@@ -1 +1 @@
1
- 0.2.7
1
+ 0.3.0
@@ -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 #{table_name} where #{select_criteria} order by row_id", arg_hash) {|tup| results << self.new(tup) }
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 #{table_name}") {|tup| results << self.new(tup)}
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 #{table_name}")
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 #{table_name} WHERE #{select_criteria}", arg_hash)
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.table_name}")
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.table_name} (#{fields.join(', ')})"
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 #{table_name} where #{cname} = ? order by row_id"
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
- @tuple["#{cname}"] = xform ? xform.call(arg) : arg
177
- update cname, arg
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 \"#{self.table_name}\" WHEN new.\"#{cname}\" IS NOT NULL AND NOT EXISTS (SELECT 1 FROM \"#{rtable}\" WHERE new.\"#{cname}\" == \"#{rf.column}\") BEGIN SELECT RAISE(ABORT, 'constraint #{insert_trigger_name} (#{rtable} missing foreign key row) failed'); END;")
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 \"#{rtable}\" WHEN EXISTS (SELECT 1 FROM \"#{self.table_name}\" WHERE old.\"#{rf.column}\" == \"#{cname}\") BEGIN DELETE FROM \"#{self.table_name}\" WHERE \"#{cname}\" = old.\"#{rf.column}\"; END;") if rf.options[:on_delete] == :cascade
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
- cols = colnames.intersection new_row.keys
234
- colspec, valspec = [:to_s, :inspect].map {|msg| (cols.map {|col| col.send(msg)}).join(", ")}
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 #{table_name} (#{colspec}) values (#{valspec})"
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 #{table_name} (#{ddlspecs});"
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 #{table_name}").to_i
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 #{table_name} where row_id = ?"
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
@@ -23,9 +23,9 @@ module Rhubarb
23
23
  def to_s
24
24
  qualifiers = @quals.join(" ")
25
25
  if qualifiers == ""
26
- "#@name #@kind"
26
+ "'#@name' #@kind"
27
27
  else
28
- "#@name #@kind #{qualifiers}"
28
+ "'#@name' #@kind #{qualifiers}"
29
29
  end
30
30
  end
31
31
  end
@@ -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 #{table_name} #{where_clause}
54
+ SELECT * from #{quoted_table_name} #{where_clause}
55
55
  ) #{group_by_clause}
56
- ) as __fresh INNER JOIN #{table_name} as __freshest ON #{join_clause} ORDER BY row_id"
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
@@ -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.table_name} where row_id = ?", @row_id)
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.table_name} set #{attr_name} = ?, updated = ? where row_id = ?", value, Util::timestamp, @row_id)
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.0
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
- version: 0.2.7
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: 2010-08-31 00:00:00 -05:00
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
- type: :development
18
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
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
- version:
35
+ type: :development
36
+ version_requirements: *id001
25
37
  - !ruby/object:Gem::Dependency
26
38
  name: sqlite3-ruby
27
- type: :runtime
28
- version_requirement:
29
- version_requirements: !ruby/object:Gem::Requirement
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
- version:
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.5
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"