spqr 0.1.0 → 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/CHANGES CHANGED
@@ -1,3 +1,19 @@
1
+ version 0.1.1
2
+
3
+ * Other minor Rhubarb fixes.
4
+
5
+ * Rhubarb now properly handles persisting classes declared in
6
+ different modules and supports defining custom table names.
7
+
8
+ * spqr-gen now has preliminary support for generating classes that
9
+ persist via rhubarb.
10
+
11
+ * Fixes and cleanups to class- and package-name metadata. Previously,
12
+ the app skeleton would fail to find some manageable classes
13
+ declared in separate Ruby modules. In addition, the manageable
14
+ mixin now sets sensible default names for the QMF class and package
15
+ (when one is not provided within the class declaration).
16
+
1
17
  version 0.1.0 (3a3ca52c4f086d1f20fdf5ed89dda262622c171d)
2
18
 
3
19
  * Note that this version breaks backwards compatibility, for reasons
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.1.1
@@ -18,8 +18,8 @@ $: << File.expand_path(File.dirname(__FILE__))
18
18
  require 'spqr/spqr'
19
19
 
20
20
  def main
21
- $WRITEMODE
22
21
  $OUTDIR = "."
22
+ $DO_RHUBARB = false
23
23
 
24
24
  op = OptionParser.new do |opts|
25
25
  opts.banner = "Usage spqr.rb [options] schema-file"
@@ -31,6 +31,10 @@ def main
31
31
  opts.on("-d", "--output-dir DIR", "directory in which to place generated app and controllers") do |dir|
32
32
  $OUTDIR = dir
33
33
  end
34
+
35
+ opts.on("-r", "--rhubarb", "enable support for rhubarb in generated code") do
36
+ $DO_RHUBARB = true
37
+ end
34
38
  end
35
39
 
36
40
  begin
@@ -102,10 +102,16 @@ end
102
102
  # Methods mixed in to the class object of a persisting class
103
103
  module PersistingClassMixins
104
104
  # Returns the name of the database table modeled by this class.
105
+ # Defaults to the name of the class (sans module names)
105
106
  def table_name
106
- self.name.downcase
107
+ @table_name ||= self.name.split("::").pop.downcase
107
108
  end
108
109
 
110
+ # Enables setting the table name to a custom name
111
+ def declare_table_name(nm)
112
+ @table_name = nm
113
+ end
114
+
109
115
  # Models a foreign-key relationship. +options+ is a hash of options, which include
110
116
  # +:column => + _name_:: specifies the name of the column to reference in +klass+ (defaults to row id)
111
117
  # +:on_delete => :cascade+:: specifies that deleting the referenced row in +klass+ will delete all rows referencing that row through this reference
@@ -131,7 +137,7 @@ module PersistingClassMixins
131
137
  select_criteria = valid_cols.map {|col| "#{col.to_s} = #{col.inspect}"}.join(" AND ")
132
138
  arg_hash.each {|k,v| arg_hash[k] = v.row_id if v.respond_to? :row_id}
133
139
 
134
- Persistence::execute("select * from #{table_name} where #{select_criteria}", arg_hash).map {|tup| self.new(tup) }
140
+ Persistence::execute("select * from #{table_name} where #{select_criteria} order by row_id", arg_hash).map {|tup| self.new(tup) }
135
141
  end
136
142
 
137
143
  # args contains the following keys
@@ -297,10 +303,17 @@ SELECT __freshest.* FROM (
297
303
  # Note that we do not support update triggers, since the API does
298
304
  # not expose the capacity to change row IDs.
299
305
 
300
- self.creation_callbacks << Proc.new do
301
- Persistence::db.execute_batch("CREATE TRIGGER refint_insert_#{self.table_name}_#{rf.referent.table_name}_#{self.creation_callbacks.size} BEFORE INSERT ON \"#{self.table_name}\" WHEN new.\"#{cname}\" IS NOT NULL AND NOT EXISTS (SELECT 1 FROM \"#{rf.referent.table_name}\" WHERE new.\"#{cname}\" == \"#{rf.column}\") BEGIN SELECT RAISE(ABORT, 'constraint failed'); END;")
306
+ self.creation_callbacks << Proc.new do
307
+ @ccount ||= 0
308
+
309
+ insert_trigger_name = "ri_insert_#{self.table_name}_#{@ccount}_#{rf.referent.table_name}"
310
+ delete_trigger_name = "ri_delete_#{self.table_name}_#{@ccount}_#{rf.referent.table_name}"
302
311
 
303
- Persistence::db.execute_batch("CREATE TRIGGER refint_delete_#{self.table_name}_#{rf.referent.table_name}_#{self.creation_callbacks.size} BEFORE DELETE ON \"#{rf.referent.table_name}\" 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
312
+ Persistence::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 \"#{rf.referent.table_name}\" WHERE new.\"#{cname}\" == \"#{rf.column}\") BEGIN SELECT RAISE(ABORT, 'constraint #{insert_trigger_name} (#{rf.referent.table_name} missing foreign key row) failed'); END;")
313
+
314
+ Persistence::db.execute_batch("CREATE TRIGGER #{delete_trigger_name} BEFORE DELETE ON \"#{rf.referent.table_name}\" 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
315
+
316
+ @ccount = @ccount + 1
304
317
  end
305
318
  end
306
319
  end
@@ -52,14 +52,14 @@ module SPQR
52
52
  manageable_ks = ks.select {|kl| manageable? kl}
53
53
  unmanageable_ks = ks.select {|kl| not manageable? kl}
54
54
  manageable_ks.each do |klass|
55
- @log.info("SPQR will manage registered class #{klass} (#{klass.name})...")
55
+ @log.info("SPQR will manage registered class #{klass} (#{klass.spqr_meta.classname})...")
56
56
 
57
57
  schemaclass = schematize(klass)
58
58
 
59
59
  klass.log = @log
60
60
 
61
61
  @classes_by_id[klass.class_id] = klass
62
- @classes_by_name[klass.name] = ClassMeta.new(klass, schemaclass)
62
+ @classes_by_name[klass.spqr_meta.classname.to_s] = ClassMeta.new(klass, schemaclass)
63
63
  end
64
64
 
65
65
  unmanageable_ks.each do |klass|
@@ -125,6 +125,8 @@ module SPQR
125
125
  def get_query(context, query, user_id)
126
126
  @log.debug "query: user=#{user_id} context=#{context} class=#{query.class_name} object_num=#{query.object_id.object_num_low if query.object_id} details=#{query} haveSelect=#{query.impl and query.impl.haveSelect} getSelect=#{query.impl and query.impl.getSelect} (#{query.impl and query.impl.getSelect and query.impl.getSelect.methods.inspect})"
127
127
 
128
+ @log.debug "classes_by_name is #{@classes_by_name.inspect}"
129
+
128
130
  cmeta = @classes_by_name[query.class_name]
129
131
  objs = []
130
132
 
@@ -204,7 +206,7 @@ module SPQR
204
206
  end
205
207
 
206
208
  def schematize(klass)
207
- @log.info("Making a QMF schema for #{klass}")
209
+ @log.info("Making a QMF schema for #{klass.spqr_meta.classname}")
208
210
 
209
211
  meta = klass.spqr_meta
210
212
  package = meta.package.to_s
@@ -290,7 +292,7 @@ module SPQR
290
292
  # turns an instance of a managed object into a QmfObject
291
293
  def qmfify(obj)
292
294
  @log.debug("trying to qmfify #{obj}: qmf_oid is #{obj.qmf_oid} and class_id is #{obj.class.class_id}")
293
- cm = @classes_by_name[obj.class.name]
295
+ cm = @classes_by_name[obj.class.spqr_meta.classname.to_s]
294
296
  return nil unless cm
295
297
 
296
298
  qmfobj = Qmf::AgentObject.new(cm.schema_class)
@@ -168,6 +168,7 @@ module SPQR
168
168
  private
169
169
  def gen_class
170
170
  pp "require 'spqr/spqr'"
171
+ pp "require 'rhubarb/rhubarb'" if $DO_RHUBARB
171
172
  pp ""
172
173
 
173
174
  @package_list.map {|pkg| pkg.capitalize}.each do |modname|
@@ -179,22 +180,25 @@ module SPQR
179
180
  pkgname = (@package_list.map {|pkg| pkg.capitalize}).join("::")
180
181
  fqcn = ("#{pkgname}::#{@sc.name}" if pkgname) or @sc.name
181
182
 
182
- pp "include ::SPQR::Manageable"
183
- pp ""
184
-
183
+ pp "include ::Rhubarb::Persisting" if $DO_RHUBARB
184
+ pp "include ::SPQR::Manageable\n"
185
+
185
186
  pp "qmf_package_name '#{@package_list.join(".")}'"
186
187
  pp "qmf_class_name '#{@sc.name.split("::")[-1]}'"
187
188
 
188
- pp '# Find method (NB: you must implement this)'
189
- pp_decl :def, "#{@sc.name}.find_by_id", "(objid)" do
190
- pp "#{@sc.name}.new"
191
- end
192
-
193
- pp ""
189
+ unless $DO_RHUBARB
190
+
191
+ pp '# Find method (NB: you must implement this)'
192
+ pp_decl :def, "#{@sc.name}.find_by_id", "(objid)" do
193
+ pp "#{@sc.name}.new"
194
+ end
194
195
 
195
- pp '# Find-all method (NB: you must implement this)'
196
- pp_decl :def, "#{@sc.name}.find_all" do
197
- pp "[#{@sc.name}.new]"
196
+ pp ""
197
+
198
+ pp '# Find-all method (NB: you must implement this)'
199
+ pp_decl :def, "#{@sc.name}.find_all" do
200
+ pp "[#{@sc.name}.new]"
201
+ end
198
202
  end
199
203
 
200
204
  ModelClassGenerator.id_registry[fqcn.hash] = fqcn
@@ -223,19 +227,28 @@ module SPQR
223
227
  end
224
228
 
225
229
  def gen_property(property)
226
- pp ""
227
- pp "\# property #{property.name} #{property.kind} #{property.desc}"
228
- pp_decl :def, "#{property.name}" do
229
- pp "log.debug 'Requested property #{property.name}'"
230
- pp "nil"
231
- end
230
+ pp "\# property #{property.name} #{property.kind} #{property.desc}\n"
231
+
232
+ rhubarb_kind = qmf_to_rhubarb(property.kind)
232
233
 
233
- pp ""
234
- pp_decl :def, "#{property.name}=", "(val)" do
235
- pp "log.debug 'Set property #{property.name} to \#\{val\}'"
236
- pp "nil"
234
+ if $DO_RHUBARB and rhubarb_kind
235
+ notnull = (", :not_null" if property.options[:index])
236
+ pp "declare_column :#{property.name}, #{rhubarb_kind}#{notnull}"
237
+ pp "declare_index_on :#{property.name}" if notnull
238
+ else
239
+ pp ""
240
+ pp_decl :def, "#{property.name}" do
241
+ pp "log.debug 'Requested property #{property.name}'"
242
+ pp "nil"
243
+ end
244
+
245
+ pp ""
246
+ pp_decl :def, "#{property.name}=", "(val)" do
247
+ pp "log.debug 'Set property #{property.name} to \#\{val\}'"
248
+ pp "nil"
249
+ end
237
250
  end
238
-
251
+
239
252
  property.options[:desc] = property.desc if property.desc
240
253
 
241
254
  pp ""
@@ -261,14 +274,10 @@ module SPQR
261
274
  pp "\# #{method.name} #{method.desc}"
262
275
  method.args.each do |arg|
263
276
  pp "\# * #{arg.name} (#{arg.kind}/#{arg.dir})"
264
- pp "\# #{arg.desc}"
277
+ pp "\# #{arg.desc}" if arg.desc
265
278
  end
266
279
 
267
- in_params = method.args.select {|arg| ['in', 'i', 'qmf::dir_in'].include? arg.dir.to_s.downcase }
268
- out_params = method.args.select {|arg| ['out', 'o', 'qmf::dir_out'].include? arg.dir.to_s.downcase }
269
- inout_params = method.args.select {|arg| ['inout', 'io', 'qmf::dir_inout'].include? arg.dir.to_s.downcase }
270
-
271
- formals_in = method.args.select {|arg| ['in','i','qmf::dir_in','io','inout','qmf::dir_inout'].include? arg.dir.to_s.downcase}.map{|arg| arg.name}.join(",")
280
+ formals_in = method.args.select {|arg| ['in','i','qmf::dir_in','io','inout','qmf::dir_inout'].include? arg.dir.to_s.downcase}.map{|arg| arg.name}
272
281
  formals_out = method.args.select {|arg| ['out','o','qmf::dir_out','io','inout','qmf::dir_inout'].include? arg.dir.to_s.downcase}.map{|arg| arg.name}
273
282
 
274
283
  actuals_out = case formals_out.size
@@ -282,40 +291,24 @@ module SPQR
282
291
  acc
283
292
  end
284
293
 
285
- pp_decl :def, method.name, "(#{formals_in})" do
294
+ pp_decl :def, method.name, "(#{formals_in.join(",")})" do
286
295
 
287
- if in_params.size + inout_params.size > 0
288
- what = "in"
289
-
290
- if in_params.size > 0 and inout_params.size > 0
291
- what << " and in/out"
292
- elsif inout_params.size > 0
293
- what << "/out"
294
- end
295
-
296
- pp "\# Print values of #{what} parameters"
297
- (in_params + inout_params).each do |arg|
298
- argdisplay = arg.name.to_s
299
- pp('log.debug "' + "#{method.name}: #{arg.name} => " + '#{' + "#{argdisplay}" + '}"')
296
+ if formals_in.size > 0
297
+
298
+ pp "\# Print values of input parameters"
299
+ formals_in.each do |arg|
300
+ pp('log.debug "' + "#{method.name}: #{arg} => " + '#{' + "#{arg}" + '}"')
300
301
  end
301
302
  end
302
303
 
303
- if out_params.size + inout_params.size > 0
304
- what = "out"
305
-
306
- if out_params.size > 0 and inout_params.size > 0
307
- what << " and in/out"
308
- elsif inout_params.size > 0
309
- what = "in/out"
310
- end
304
+ if formals_out.size > 0
311
305
 
312
- pp "\# Assign values to #{what} parameters"
306
+ pp "\# Assign values to output parameters"
313
307
 
314
- (out_params + inout_params).each do |arg|
315
- argdisplay = arg.name.to_s
316
- argtype = param_types[argdisplay]
317
- raise RuntimeError.new("Can't find type for #{argdisplay} in #{param_types.inspect}") unless argtype
318
- pp "#{argdisplay} ||= #{default_val(argtype)}"
308
+ formals_out.each do |arg|
309
+ argtype = param_types[arg]
310
+ raise RuntimeError.new("Can't find type for #{arg} in #{param_types.inspect}") unless argtype
311
+ pp "#{arg} ||= #{default_val(argtype)}"
319
312
  end
320
313
 
321
314
  pp "\# Return value"
@@ -324,9 +317,12 @@ module SPQR
324
317
  end
325
318
  end
326
319
 
327
-
328
320
  pp ""
329
321
  pp_decl :expose, "#{method.name.to_sym.inspect} do |args|" do
322
+ in_params = method.args.select {|arg| ['in', 'i', 'qmf::dir_in'].include? arg.dir.to_s.downcase }
323
+ out_params = method.args.select {|arg| ['out', 'o', 'qmf::dir_out'].include? arg.dir.to_s.downcase }
324
+ inout_params = method.args.select {|arg| ['inout', 'io', 'qmf::dir_inout'].include? arg.dir.to_s.downcase }
325
+
330
326
  {:in => in_params, :inout => inout_params, :out => out_params}.each do |dir,coll|
331
327
  coll.each do |arg|
332
328
  arg_nm = arg.name
@@ -350,6 +346,17 @@ module SPQR
350
346
  else raise RuntimeError.new("Unknown type #{ty.inspect}")
351
347
  end
352
348
  end
349
+
350
+ def qmf_to_rhubarb(ty)
351
+ case ty.downcase
352
+ when 'bool' then ':boolean'
353
+ when 'double', 'float' then ':double'
354
+ when 'absTime', 'deltaTime' then ':timestamp'
355
+ when 'int16', 'int32', 'int64', 'int', 'int8', 'uint16', 'uint32', 'uint64', 'uint', 'uint8' then ':integer'
356
+ when 'lstr', 'string', 'sstr' then ':string'
357
+ else false
358
+ end
359
+ end
353
360
  end
354
361
 
355
362
  class AppBoilerplateGenerator
@@ -247,7 +247,9 @@ module SPQR
247
247
  end
248
248
  end
249
249
 
250
- other.qmf_class_name other.name.to_sym
250
+ name_components = other.name.to_s.split("::")
251
+ other.qmf_class_name name_components.pop
252
+ other.qmf_package_name name_components.join(".").downcase
251
253
  end
252
254
  end
253
255
  end
@@ -12,7 +12,7 @@ require 'spqr/app'
12
12
  require 'rhubarb/rhubarb'
13
13
 
14
14
  module QmfTestHelpers
15
- DEBUG = false
15
+ DEBUG = (::ENV["SPQR_TESTS_DEBUG"] and ::ENV["SPQR_TESTS_DEBUG"].downcase == "yes")
16
16
 
17
17
  class AgentNotifyHandler < Qmf::ConsoleHandler
18
18
  def initialize
@@ -11,7 +11,6 @@
11
11
  #
12
12
  # http://www.apache.org/licenses/LICENSE-2.0
13
13
 
14
- require 'rubygems'
15
14
  require 'rhubarb/rhubarb'
16
15
  require 'test/unit'
17
16
 
@@ -21,6 +20,21 @@ class TestClass
21
20
  declare_column :bar, :string
22
21
  end
23
22
 
23
+ module RhubarbNamespace
24
+ class NMTC
25
+ include Rhubarb::Persisting
26
+ declare_column :foo, :integer
27
+ declare_column :bar, :string
28
+ end
29
+
30
+ class NMTC2
31
+ include Rhubarb::Persisting
32
+ declare_column :foo, :integer
33
+ declare_column :bar, :string
34
+ declare_table_name('namespacetestcase')
35
+ end
36
+ end
37
+
24
38
  class TestClass2
25
39
  include Rhubarb::Persisting
26
40
  declare_column :fred, :integer
@@ -87,6 +101,8 @@ class BackendBasicTests < Test::Unit::TestCase
87
101
  klasses << ToRef
88
102
  klasses << FromRef
89
103
  klasses << FreshTestTable
104
+ klasses << RhubarbNamespace::NMTC
105
+ klasses << RhubarbNamespace::NMTC2
90
106
 
91
107
  klasses.each { |klass| klass.create_table }
92
108
 
@@ -110,6 +126,14 @@ class BackendBasicTests < Test::Unit::TestCase
110
126
  assert(r.managed_ref?, "managed reference should return true for managed_ref?")
111
127
  end
112
128
 
129
+ def test_namespace_table_name
130
+ assert_equal "nmtc", RhubarbNamespace::NMTC.table_name
131
+ end
132
+
133
+ def test_set_table_name
134
+ assert_equal "namespacetestcase", RhubarbNamespace::NMTC2.table_name
135
+ end
136
+
113
137
  def test_reference_ctor_string
114
138
  r = Rhubarb::Reference.new("TestClass")
115
139
  assert(r.referent == "TestClass", "Referent of string-backed reference instance incorrect")
@@ -18,7 +18,6 @@ class TestSpqrIntegerProp < Test::Unit::TestCase
18
18
  expected = objs[(x + 1) % QmfIntegerProp::SIZE]
19
19
  o = objs[x]
20
20
  next_o = o.next.result
21
- puts ("next_o: #{next_o}")
22
21
  actual = $console.object(:object_id=>next_o)
23
22
  assert_equal expected.int_id, actual.int_id
24
23
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spqr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - William Benton
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-12-08 00:00:00 -06:00
12
+ date: 2009-12-16 00:00:00 -06:00
13
13
  default_executable: spqr-gen.rb
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency