rhubarb 0.2.7 → 0.3.0

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/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"