spqr 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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