lore 0.4.2

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.
Files changed (90) hide show
  1. data/LICENSE +19 -0
  2. data/README +74 -0
  3. data/aspect.rb +80 -0
  4. data/behaviours/lockable.rb +41 -0
  5. data/behaviours/movable.rb +54 -0
  6. data/behaviours/versioned.rb +24 -0
  7. data/benchmark.rb +193 -0
  8. data/bits.rb +52 -0
  9. data/cache/abstract_entity_cache.rb +82 -0
  10. data/cache/bits.rb +22 -0
  11. data/cache/cacheable.rb +202 -0
  12. data/cache/cached_entities.rb +116 -0
  13. data/cache/file_index.rb +35 -0
  14. data/cache/mmap_entity_cache.rb +67 -0
  15. data/clause.rb +528 -0
  16. data/connection.rb +155 -0
  17. data/custom_functions.sql +14 -0
  18. data/exception/ambiguous_attribute.rb +14 -0
  19. data/exception/cache_exception.rb +30 -0
  20. data/exception/invalid_klass_parameters.rb +63 -0
  21. data/exception/invalid_parameter.rb +42 -0
  22. data/exception/unknown_typecode.rb +19 -0
  23. data/file_index.sql +56 -0
  24. data/gui/erb_template.rb +79 -0
  25. data/gui/erb_template_helpers.rhtml +19 -0
  26. data/gui/form.rb +314 -0
  27. data/gui/form_element.rb +676 -0
  28. data/gui/form_generator.rb +151 -0
  29. data/gui/templates/button.rhtml +2 -0
  30. data/gui/templates/checkbox.rhtml +3 -0
  31. data/gui/templates/checkbox_row.rhtml +1 -0
  32. data/gui/templates/file.rhtml +2 -0
  33. data/gui/templates/file_readonly.rhtml +3 -0
  34. data/gui/templates/form_element.rhtml +5 -0
  35. data/gui/templates/form_element_horizontal.rhtml +3 -0
  36. data/gui/templates/form_element_listed.rhtml +8 -0
  37. data/gui/templates/form_table.rhtml +3 -0
  38. data/gui/templates/form_table_blank.rhtml +3 -0
  39. data/gui/templates/form_table_horizontal.rhtml +8 -0
  40. data/gui/templates/password.rhtml +2 -0
  41. data/gui/templates/password_readonly.rhtml +3 -0
  42. data/gui/templates/radio.rhtml +1 -0
  43. data/gui/templates/radio_row.rhtml +1 -0
  44. data/gui/templates/select.rhtml +23 -0
  45. data/gui/templates/text.rhtml +2 -0
  46. data/gui/templates/text_readonly.rhtml +3 -0
  47. data/gui/templates/textarea.rhtml +3 -0
  48. data/gui/templates/textarea_readonly.rhtml +4 -0
  49. data/lore.gemspec +40 -0
  50. data/lore.rb +94 -0
  51. data/migration.rb +48 -0
  52. data/model.rb +139 -0
  53. data/model_factory.rb +202 -0
  54. data/model_shortcuts.rb +16 -0
  55. data/query_shortcuts.rb +367 -0
  56. data/reserved_methods.txt +3 -0
  57. data/result.rb +100 -0
  58. data/symbol.rb +58 -0
  59. data/table_accessor.rb +1926 -0
  60. data/table_deleter.rb +115 -0
  61. data/table_inserter.rb +168 -0
  62. data/table_instance.rb +384 -0
  63. data/table_selector.rb +314 -0
  64. data/table_updater.rb +155 -0
  65. data/test/README +31 -0
  66. data/test/env.rb +5 -0
  67. data/test/lore_test.log +8218 -0
  68. data/test/model.rb +142 -0
  69. data/test/prepare.rb +37 -0
  70. data/test/tc_aspect.rb +58 -0
  71. data/test/tc_cache.rb +80 -0
  72. data/test/tc_clause.rb +104 -0
  73. data/test/tc_deep_inheritance.rb +49 -0
  74. data/test/tc_factory.rb +57 -0
  75. data/test/tc_filter.rb +37 -0
  76. data/test/tc_form.rb +32 -0
  77. data/test/tc_model.rb +86 -0
  78. data/test/tc_prepare.rb +45 -0
  79. data/test/tc_refined_query.rb +88 -0
  80. data/test/tc_table_accessor.rb +265 -0
  81. data/test/test.log +181 -0
  82. data/test/test_db.sql +400 -0
  83. data/test/ts_lore.rb +49 -0
  84. data/types.rb +55 -0
  85. data/validation/message.rb +60 -0
  86. data/validation/parameter_validator.rb +104 -0
  87. data/validation/reason.rb +54 -0
  88. data/validation/type_validator.rb +91 -0
  89. data/validation.rb +65 -0
  90. metadata +170 -0
data/table_accessor.rb ADDED
@@ -0,0 +1,1926 @@
1
+
2
+ require('logger')
3
+ require('lore/types')
4
+ require('lore/clause')
5
+ require('lore/aspect')
6
+ require('lore/table_instance')
7
+ require('lore/table_selector')
8
+ require('lore/table_inserter')
9
+ require('lore/table_updater')
10
+ require('lore/table_deleter')
11
+ require('lore/query_shortcuts')
12
+ require('lore/model_shortcuts')
13
+
14
+ module Lore
15
+
16
+ class Table_Accessor
17
+ include Table_Instance
18
+ extend Aspect
19
+ extend Query_Shortcuts
20
+ extend Model_Shortcuts
21
+
22
+ attr_reader :constraints, :foreign_keys, :primary_keys, :attributes, :attribute_types
23
+
24
+ @@logger = Logger.new(Lore.logfile)
25
+
26
+ def self.log(message, level=:debug)
27
+ @@logger.debug(message)
28
+ end
29
+ def log(message, level=:debug)
30
+ @@logger.debug(message)
31
+ end
32
+
33
+ # tell dispatchers to validate Table_Accessors
34
+ def self.validate_params?() true end
35
+
36
+ # {{{ FIELDS
37
+ # foreign_keys is a hash that looks like:
38
+ # "foreign_table" => array ("foreign_key1", "foreign_key_2" ... )
39
+ @foreign_keys = Hash.new
40
+
41
+ # primary_keys is a hash holding the own foreign key fields,
42
+ # and the parents ones:
43
+ # "parent_table" => array ("prim_key_1", "prim_key_2", ... )
44
+ # "own_table" => array ("prim_key_1", "prim_key_2", ... )
45
+ @primary_keys = Hash.new
46
+
47
+ # implicit|explicit_attributes = Hash {'table_a' =>
48
+ # { 'attrib_a1', attrib_a2, ... },
49
+ # 'table_b' =>
50
+ # { 'attrib_b1', attrib_b2, ... },
51
+ # ...}
52
+ # implicit_attributes stores attributes that will be resolved implicitly
53
+ # and do not have to be passed on self.create
54
+ @implicit_attributes = Hash.new
55
+ # explicit_attributes stores attributes that have to be passed
56
+ # on self.create and will not be resolved implicitly
57
+ @explicit_attributes = Hash.new
58
+
59
+ # Array storing sequences for primary keys. If no sequence is set,
60
+ # the value for this primary key has to be resolved from parent,
61
+ # or passed directly.
62
+ @sequences = Hash.new
63
+
64
+ # Hash holding own table attribute fields as string
65
+ @attributes = nil
66
+
67
+ # Like @attributes, but listing fields as array
68
+ # Example:
69
+ # [ 'public.the_table.foo', 'public.other_table.bar' ]
70
+ @attribute_array = Array.new
71
+
72
+ # array holding own table attribute types as { 'attrib_name' => int type}
73
+ @attribute_types = Hash.new
74
+
75
+ # Holds own table name, defaults to self.class if not given.
76
+ # If a schema has been set by
77
+ # table :table_name, :schema_name
78
+ # table_name is schema_name.table_name
79
+ @table_name = String.new
80
+
81
+ # has_a, has_n and is_a hold all table names they are referring to:
82
+ # array ("foreign_table_1", "foreign_table_2", ... )
83
+ # The respective foreign keys reside in foreign_keys:
84
+ # foreign_keys["foreign_table_1"] -> array ("foreign_key_1", ... )
85
+ @has_a = Array.new
86
+ @has_n = Array.new
87
+
88
+ # Hash is_a holds the relational derivation tree of this class.
89
+ @is_a = Hash.new
90
+
91
+ # Hash is_a_klasses holds the relational derivation tree of this class.
92
+ @is_a_klasses = Hash.new
93
+ @has_a_klasses = Hash.new
94
+
95
+ @constraints = Hash.new
96
+
97
+ # Hash explicit holds table fields that are not to be updated
98
+ # implicitly, e.g. timestamps.
99
+ @explicit = Hash.new
100
+
101
+ @labels = Array.new
102
+ #}}} END FIELDS
103
+
104
+ @input_filters = Hash.new
105
+ @output_filters = Hash.new
106
+
107
+ @@prepared_statements = Hash.new
108
+
109
+ @@cache_impl = false
110
+
111
+ def self.entity_cache
112
+ @@cache_impl
113
+ end
114
+
115
+ def self.use_entity_cache(entity_cache_class)
116
+ log('Setting cache implementation: ' << entity_cache_class.inspect)
117
+ @@cache_impl = entity_cache_class
118
+ end
119
+
120
+ def self.flush_entity_cache
121
+ @@cache_impl.flush(self) if @@cache_impl
122
+ end
123
+
124
+ def self.create_entity_cache(query_string, result)
125
+ @@cache_impl.create(self, query_string, result) if @@cache_impl
126
+ end
127
+
128
+ def self.read_entity_cache(query_string)
129
+ @@cache_impl.read(self, query_string) if @@cache_impl
130
+ end
131
+
132
+
133
+ ##########################################################################
134
+ # Recursively gets foreign keys from parent, if own
135
+ # foreign keys don't have been set, yet:
136
+ def self.get_foreign_keys
137
+ # {{{
138
+
139
+ if @foreign_keys.nil? then
140
+ if @foreign_keys_klasses.nil? then
141
+ @foreign_keys = Hash.new
142
+ else
143
+ @is_a_klasses.each { |foreign_key, k|
144
+ @foreign_keys[k.table_name] << k.get_foreign_keys
145
+ }
146
+ end
147
+ else
148
+ return @foreign_keys
149
+ end
150
+
151
+ end # }}}
152
+ def self.set_foreign_keys(arg)
153
+ # {{{
154
+ @foreign_keys = arg
155
+ end # }}}
156
+
157
+ ##########################################################################
158
+ # Recursively gets primary keys from parent, if own
159
+ # primary keys don't have been set, yet:
160
+ # Returns all derived primary keys WITHOUT OWN PKEYS.
161
+ def self.get_primary_keys
162
+ # {{{
163
+
164
+ if @primary_keys.nil? then
165
+ if @is_a_klasses.nil? then
166
+ return Hash.new
167
+ else
168
+ plain_base_pkeys = Hash.new
169
+ formatted_base_pkeys = Hash.new
170
+ base_schema = ''
171
+
172
+ get_is_a_klasses.each { |k|
173
+ plain_base_pkeys = k.get_primary_keys
174
+ base_schema = k.get_schema
175
+ if base_schema.nil? then
176
+ formatted_base_pkeys.update(plain_base_pkeys)
177
+ else
178
+ plain_base_pkeys.each_pair { |key, value|
179
+ formatted_base_pkeys[base_schema+'.'+key] = value
180
+ }
181
+ return formatted_base_pkeys
182
+ @primary_keys = formatted_base_pkeys
183
+ end
184
+ }
185
+ return formatted_base_pkeys
186
+ @primary_keys = formatted_base_pkeys
187
+ end
188
+ else
189
+ return @primary_keys
190
+ end
191
+
192
+ end # }}}
193
+
194
+ def self.key_array()
195
+ # {{{
196
+ keys = Array.new
197
+ get_primary_keys.each_pair { |table, attribs|
198
+ attribs.each { |attrib|
199
+ keys.push attrib
200
+ }
201
+ }
202
+ return keys
203
+ end # }}}
204
+
205
+ ##########################################################################
206
+ # Recursively gets sequences from parent, if own
207
+ # sequences don't have been set, yet:
208
+ def self.get_sequences
209
+ # {{{
210
+
211
+ if @sequences.nil? then
212
+ if @is_a_klasses.nil? then
213
+ return Hash.new
214
+ else
215
+ seq_map = Hash.new
216
+ @is_a_klasses.each_pair { |foreign_key,k|
217
+ seq_map[k.table_name] = k.get_sequences
218
+ }
219
+ return seq_map
220
+ end
221
+ else
222
+ return @sequences
223
+ end
224
+
225
+ end # }}}
226
+
227
+ def self.set_sequences(arg) # :nodoc:
228
+ # {{{
229
+ @sequences = arg
230
+ end # }}}
231
+
232
+ ##########################################################################
233
+ # @implicit_attributes getters/setters {{{
234
+
235
+ def self.set_implicit_attributes(arg) # :nodoc:
236
+ @implicit_attributes = arg
237
+ end # def
238
+ def self.get_implicit_attributes # :nodoc:
239
+ if @implicit_attributes.nil? then
240
+ return Hash.new
241
+ else
242
+ return @implicit_attributes
243
+ end
244
+ end # def
245
+ # }}}
246
+
247
+ ##########################################################################
248
+ # @explicit_attributes getters/setters {{{
249
+
250
+ def self.set_explicit_attributes(arg) # :nodoc:
251
+ @explicit_attributes = arg
252
+ end # def
253
+ def self.get_explicit_attributes # :nodoc:
254
+
255
+ if @explicit_attributes.nil? then
256
+ return Hash.new
257
+ else
258
+ # filter entries that are present in @implicit_attributes:
259
+ if !@implicit_attributes.nil? then
260
+
261
+ @explicit_attributes.each_pair { |table, attribs|
262
+ attribs.delete_if { |attrib|
263
+ !@implicit_attributes[table].nil? &&
264
+ @implicit_attributes[table].include?(attrib.to_s)
265
+ }
266
+ }
267
+
268
+ end
269
+
270
+ return @explicit_attributes
271
+
272
+ end
273
+ end # def
274
+ # }}}
275
+
276
+ def self.get_constraints
277
+
278
+ @constraints = Hash.new if @constraints.nil?
279
+ if !@is_a_klasses.nil? then
280
+ @is_a_klasses.each_pair { |foreign_key, klass|
281
+ @constraints.update(klass.get_constraints)
282
+ }
283
+ end
284
+ @constraints
285
+
286
+ end
287
+
288
+ ##########################################################################
289
+ # @table getters/setters {{{
290
+
291
+ def self.get_table_name # :nodoc:
292
+ return @table_name unless @table_name.nil?
293
+ return ''
294
+ end
295
+ def self.table_name # :nodoc:
296
+ get_table_name
297
+ end
298
+ def self.set_table_name(arg) # :nodoc:
299
+ @table_name = arg
300
+ end
301
+
302
+ def self.get_table_names
303
+ return @table_names if @table_names
304
+ @table_names = [@table_name]
305
+ @table_names += get_is_a.keys_flat
306
+ return @table_names
307
+ end
308
+ def self.get_all_table_names
309
+ return @table_names if @table_names
310
+ @table_names = [@table_name]
311
+ @table_names += get_joins.keys_flat
312
+ return @table_names
313
+ end
314
+ # }}}
315
+
316
+
317
+ protected
318
+
319
+ ##########################################################################
320
+ # label setters/getters {{{
321
+
322
+ # Define attribute to use as label for instances of a Table_Accessor,
323
+ # e.g. for select boxes.
324
+ def self.use_label(*attribs)
325
+
326
+ @labels = attribs.map { |e|
327
+
328
+ e = e.to_s # also removes Array wraps like [:attrib_name]
329
+
330
+ if((e.kind_of? Clause) or (e.include?('.'))) then
331
+ e = e.to_s
332
+ else
333
+ e = @table_name + '.' << e.to_s
334
+ end
335
+ }
336
+ @label = attribs[0]
337
+ end
338
+ # Returns full name of attribute set to use as label e.g. for
339
+ # select boxes.
340
+ def self.get_label
341
+ @labels[0]
342
+ end
343
+
344
+ def self.get_labels
345
+ @labels
346
+ end
347
+
348
+ # }}}
349
+
350
+ ##########################################################################
351
+ # @attributes getters/setters {{{
352
+
353
+ def self.get_attributes # :nodoc:
354
+ return @attributes unless @attributes.nil?
355
+ return Hash.new
356
+ end
357
+
358
+ def self.get_attribute_array
359
+ @attribute_array
360
+ end
361
+
362
+ def self.set_attributes(arg) # :nodoc:
363
+ @attribute_array = []
364
+ @attributes = arg
365
+ @attributes.each_pair { |table, attribs|
366
+ attribs.each { |a|
367
+ @attribute_array << "#{table}.#{a}"
368
+ @attribute_array << a
369
+ }
370
+ }
371
+ end
372
+ # }}}
373
+
374
+ def self.get_input_filters
375
+ @input_filters
376
+ end
377
+ def self.get_output_filters
378
+ @output_filters
379
+ end
380
+
381
+ ##########################################################################
382
+ # @attribute_types getters/setters {{{
383
+
384
+ def self.get_attribute_types # :nodoc:
385
+ (@attribute_types.nil?) ? Hash.new : @attribute_types
386
+ end
387
+ def self.set_attribute_types(arg) # :nodoc:
388
+ @attribute_types = arg
389
+ end
390
+ def self.attribute_type(attrib_args={})
391
+ attrib_args[:table] = table_name unless attrib_args[:table]
392
+ @attribute_types[attrib_args[:table]][attrib_args[:attribute]]
393
+ end
394
+ # }}}
395
+
396
+
397
+ ##########################################################################
398
+ # @has_a getters/setters {{{
399
+ def self.get_has_a # :nodoc:
400
+
401
+ if @has_a.nil? then
402
+ if @has_a_klasses.nil? then
403
+ @has_a = Array.new
404
+ else
405
+ @has_a_klasses.each { |k|
406
+ @has_a << k
407
+ }
408
+ end
409
+ else
410
+ return @has_a
411
+ end
412
+
413
+ end
414
+ def self.set_has_a(arg) # :nodoc:
415
+ @has_a = arg
416
+ end
417
+ # }}}
418
+
419
+ ##########################################################################
420
+ # @has_a getters/setters {{{
421
+
422
+ def self.get_has_n # :nodoc:
423
+
424
+ if @has_n.nil? then
425
+ if @has_n_klasses.nil? then
426
+ @has_n = Array.new
427
+ else
428
+ @has_n_klasses.each { |k|
429
+ @has_n << k
430
+ }
431
+ end
432
+ else
433
+ return @has_n
434
+ end
435
+
436
+ end
437
+ def self.set_has_n(arg) # :nodoc:
438
+ @has_n = arg
439
+ end
440
+ # }}}
441
+
442
+ ##########################################################################
443
+ # @is_a getters/setters {{{
444
+ # don't collect is_a from ancestors!
445
+
446
+ def self.get_is_a # :nodoc:
447
+ if @is_a.nil?
448
+ Hash.new
449
+ else
450
+ @is_a
451
+ end
452
+ end
453
+ def self.set_is_a(arg) # :nodoc:
454
+ @is_a = arg
455
+ end
456
+ # }}}
457
+
458
+ ##########################################################################
459
+ # @aggregates getters/setters {{{
460
+ # don't collect aggregates from ancestors!
461
+
462
+ def self.get_aggregates # "nodoc:
463
+ if @aggregates.nil?
464
+ Hash.new
465
+ else
466
+ @aggregates
467
+ end
468
+ end
469
+ def self.set_aggregates(arg) # :nodoc:
470
+ @aggregates = arg
471
+ end
472
+ # }}}
473
+
474
+ def self.get_joins
475
+ # {{{
476
+ @joins = get_is_a.dup.update(get_aggregates) if @joins.nil?
477
+ return @joins
478
+ end # def }}}
479
+
480
+ ##########################################################################
481
+ # @is_a_klasses getters/setters {{{
482
+ # don't collect is_a from ancestors!
483
+
484
+ def self.get_is_a_klasses # :nodoc:
485
+ if @is_a_klasses.nil?
486
+ Hash.new
487
+ else
488
+ @is_a_klasses
489
+ end
490
+ end
491
+ def self.set_is_a_klasses(arg) # :nodoc:
492
+ @is_a_klasses = arg
493
+ end
494
+ # }}}
495
+
496
+ ##########################################################################
497
+ # @aggregate_klasses getters/setters {{{
498
+ # don't collect is_a from ancestors!
499
+
500
+ def self.get_aggregate_klasses # :nodoc:
501
+ if @aggregate_klasses.nil?
502
+ Hash.new
503
+ else
504
+ @aggregate_klasses
505
+ end
506
+ end
507
+
508
+ def self.set_aggregate_klasses(arg) # :nodoc:
509
+ @aggregate_klasses = arg
510
+ end
511
+ # }}}
512
+
513
+ ##########################################################################
514
+ # @has_a_klasses getters/setters {{{
515
+ # don't collect is_a from ancestors!
516
+
517
+ def self.get_has_a_klasses # :nodoc:
518
+ if @has_a_klasses.nil?
519
+ Hash.new
520
+ else
521
+ @has_a_klasses
522
+ end
523
+ end
524
+ def self.set_has_a_klasses(arg) # :nodoc:
525
+ @has_a_klasses = arg
526
+ end
527
+ # }}}
528
+
529
+ ##########################################################################
530
+ # @explicit getters/setters {{{
531
+
532
+ def self.get_explicit # :nodoc:
533
+ if @explicit.nil?
534
+ Hash.new
535
+ else
536
+ @explicit
537
+ end
538
+ end # def
539
+ def self.set_explicit(arg) # :nodoc:
540
+ @explicit = arg
541
+ end # def
542
+ # }}}
543
+
544
+ def self.cache_entities
545
+ @cache_entities = true
546
+ end
547
+ def self.cache_entities?
548
+ return false # Temp defunc
549
+ @cache_entities
550
+ end
551
+
552
+ protected
553
+
554
+ def self.add_input_filter(attr_name, &block)
555
+ @input_filters = Hash.new if @input_filters.nil?
556
+ @input_filters[attr_name] = Proc.new &block
557
+ end
558
+
559
+ def self.add_output_filter(attr_name, &block)
560
+ @output_filters = Hash.new if @output_filters.nil?
561
+ @output_filters[attr_name] = Proc.new &block
562
+ end
563
+
564
+ def self.validates(attrib, constraints)
565
+ # {{{
566
+ if attrib.kind_of? Clause then
567
+ attrib_split = attrib.to_s.split('.')
568
+ table = attrib_split[0..-2]
569
+ attrib = attrib_split[-1]
570
+ else
571
+ table = get_table_name
572
+ end
573
+ attrib = attrib.intern unless attrib.instance_of? Symbol
574
+
575
+ @constraints = Hash.new if @constraints.nil?
576
+ @constraints[table] = Hash.new if @constraints[table].nil?
577
+ @constraints[table][attrib] = Hash.new
578
+
579
+ if constraints[:mandatory] then
580
+ add_explicit_attribute(table, attrib.to_s)
581
+ end
582
+ if constraints[:type] then
583
+ # @attribute_types[table][attrib] = constraints[:type]
584
+ @constraints[table][attrib][:type] = constraints[:type]
585
+ end
586
+ if constraints[:format] then
587
+ @constraints[table][attrib][:format] = constraints[:format]
588
+ end
589
+ if constraints[:length] then
590
+ if constraints[:length].kind_of? Range then
591
+ @constraints[table][attrib][:minlength] = constraints[:length].first
592
+ @constraints[table][attrib][:maxlength] = constraints[:length].last
593
+ else
594
+ @constraints[table][attrib][:minlength] = constraints[:length]
595
+ @constraints[table][attrib][:maxlength] = constraints[:length]
596
+ end
597
+ end
598
+ if constraints[:minlength] then
599
+ @constraints[table][attrib][:minlength] = constraints[:minlength]
600
+ end
601
+ if constraints[:maxlength] then
602
+ @constraints[table][attrib][:maxlength] = constraints[:maxlength]
603
+ end
604
+ end # }}}
605
+
606
+ def self.get_maxlength()
607
+ end
608
+
609
+ private
610
+
611
+ ##########################################################################
612
+ #
613
+ def self.add_foreign_keys(args)
614
+ # {{{
615
+
616
+ other_table = args.at(0).to_s # first index is table name (see usage)
617
+
618
+ new_foreign_keys = Hash.new
619
+ new_foreign_keys.update(get_foreign_keys)
620
+ new_foreign_keys[other_table] = args[1...args.length]
621
+
622
+ set_foreign_keys(new_foreign_keys)
623
+
624
+ end # }}}
625
+
626
+ private
627
+
628
+ ##########################################################################
629
+ # Add single or multiple primary keys to @primary_keys[args.at(0)]:
630
+ def self.add_primary_keys(args)
631
+ # {{{
632
+
633
+ other_table = args.at(0) # first index is table name (see usage)
634
+
635
+ new_primary_keys = Hash.new
636
+ new_primary_keys.update(get_primary_keys)
637
+
638
+ foreign_p_keys = args[1...args.length]
639
+ foreign_p_keys.collect! {|elem| elem.to_s}
640
+
641
+ if(new_primary_keys[other_table].nil?)
642
+ new_primary_keys[other_table] = foreign_p_keys
643
+ else
644
+ new_primary_keys[other_table] += foreign_p_keys
645
+ end
646
+
647
+ @primary_keys = new_primary_keys
648
+
649
+ end # }}}
650
+
651
+ private
652
+
653
+ ##########################################################################
654
+ # Add one sequence to @sequences[table_name]:
655
+ def self.add_sequence(table_name, field_name, sequence_name)
656
+ # {{{
657
+
658
+ new_sequences = Hash.new
659
+ new_sequences.update(get_sequences)
660
+
661
+ new_sequences[table_name] = Hash.new
662
+ if sequence_name != nil
663
+ new_sequences[table_name][field_name] = sequence_name.to_s
664
+ end
665
+
666
+ set_sequences(new_sequences)
667
+
668
+ end # }}}
669
+
670
+ private
671
+
672
+ ##########################################################################
673
+ def self.add_attributes(attrib_hash)
674
+ # {{{
675
+
676
+ new_attributes = Hash.new
677
+ new_attributes.update(get_attributes)
678
+
679
+ new_attributes.update(attrib_hash)
680
+
681
+ set_attributes(new_attributes)
682
+
683
+ end # }}}
684
+
685
+ private
686
+
687
+ ##########################################################################
688
+ def self.add_attribute_types(attrib_hash)
689
+ # {{{
690
+
691
+ new_types = Hash.new
692
+ new_types.update(get_attribute_types)
693
+
694
+ new_types.update(attrib_hash)
695
+
696
+ set_attribute_types(new_types)
697
+
698
+ end # }}}
699
+
700
+ private
701
+
702
+ ##########################################################################
703
+ def self.add_explicit(explicit_hash)
704
+ # {{{
705
+
706
+ new_explicit = explicit_hash
707
+ new_explicit.update(get_explicit)
708
+
709
+ set_explicit(new_explicit)
710
+
711
+ end # }}}
712
+
713
+ private
714
+
715
+ ##########################################################################
716
+ def self.add_explicit_attributes(attrib_hash)
717
+ # {{{
718
+
719
+ # new_explicit_attributes = attrib_hash
720
+ # new_explicit_attributes.update(get_explicit_attributes)
721
+ # set_explicit_attributes(new_explicit_attributes)
722
+
723
+ attrib_hash.each_pair { |table, attribs|
724
+ if @explicit_attributes and @explicit_attributes[table] then
725
+ @explicit_attributes[table] = attrib_hash[table] + @explicit_attributes[table]
726
+ else
727
+ @explicit_attributes = Hash.new unless @explicit_attributes
728
+ @explicit_attributes[table] = attrib_hash[table]
729
+ end
730
+ }
731
+
732
+ end # }}}
733
+
734
+ private
735
+
736
+ ##########################################################################
737
+ def self.add_implicit_attributes(attrib_hash)
738
+ # {{{
739
+
740
+ attrib_hash.each_pair { |table, attribs|
741
+ if @implicit_attributes and @implicit_attributes[table] then
742
+ @implicit_attributes[table] = attrib_hash[table] + @implicit_attributes[table]
743
+ else
744
+ @implicit_attributes = Hash.new unless @implicit_attributes
745
+ @implicit_attributes[table] = attrib_hash[table]
746
+ end
747
+ }
748
+
749
+ # new_implicit_attributes = attrib_hash
750
+ # new_implicit_attributes.update(existing_implicit_attributes)
751
+
752
+ # set_implicit_attributes(new_implicit_attributes)
753
+ end # }}}
754
+
755
+ protected
756
+
757
+ # Demands a value to be set for create and update procedures.
758
+ def self.expects(attrib_name, klass=nil)
759
+ # {{{
760
+
761
+ if klass.nil? then table = get_table_name
762
+ else table = klass.table end
763
+
764
+ add_explicit_attribute(table, attrib_name.to_s)
765
+
766
+ end # }}}
767
+
768
+ def self.hide_attribute(attrib_name)
769
+ # {{{
770
+ @hidden_attributes = Hash.new if @hidden_attributes.nil?
771
+ @hidden_attributes[get_table_name] = Array.new if @hidden_attributes[get_table_name].nil?
772
+ @hidden_attributes[get_table_name] << attrib_name
773
+ end
774
+
775
+ def self.get_hidden_attributes()
776
+ return @hidden_attributes if @hidden_attributes
777
+ Hash.new
778
+ end # }}}
779
+
780
+ private
781
+
782
+ ##########################################################################
783
+ #
784
+ def self.add_explicit_attribute(table_name, attrib_name)
785
+ # {{{
786
+
787
+ table_explicit_attributes = get_explicit_attributes[table_name]
788
+ if(!table_explicit_attributes.nil?)
789
+ then table_explicit_attributes.push(attrib_name)
790
+ else table_explicit_attributes = [attrib_name]
791
+ end
792
+
793
+ new_explicit_attributes =
794
+ get_explicit_attributes.update({table_name => table_explicit_attributes})
795
+
796
+ set_explicit_attributes(new_explicit_attributes)
797
+
798
+ end # }}}
799
+
800
+ private
801
+
802
+ def self.add_implicit_attribute(table_name, attrib_name)
803
+ # {{{
804
+ table_implicit_attributes = get_implicit_attributes[table_name]
805
+ if(!table_implicit_attributes.nil?)
806
+ then table_implicit_attributes.push(attrib_name)
807
+ else table_implicit_attributes = [attrib_name]
808
+ end
809
+
810
+ new_implicit_attributes =
811
+ get_implicit_attributes.update({table_name => table_implicit_attributes})
812
+
813
+ set_implicit_attributes(new_implicit_attributes)
814
+
815
+ end # }}}
816
+
817
+ ##########################################################################
818
+
819
+ public
820
+
821
+ # Returns full attribute name of given attribute
822
+ def self.[](attribute_name)
823
+ # {{{
824
+ return get_table_name+'.'+attribute_name.to_s
825
+ end # }}}
826
+
827
+ # Constructur is private as class is initialized
828
+ # via .create or .load:
829
+
830
+ public
831
+
832
+ ##########################################################################
833
+ # Constructor is usually wrapped by e.g. self.load or
834
+ # self.marshal_load.
835
+ # Constructor just accepts a value hash, and returns a Table_Accessor
836
+ # instance holding it.
837
+ # Note that this method is operating on a Table_Accessor instance, not
838
+ # on class Table_Accessor itself.
839
+ def initialize(instance_attrib_values, cache=nil)
840
+ # {{{
841
+
842
+ @loaded_from_cache = (cache == :cached)
843
+ # set instance variables.
844
+ # class instance variables are made accessible
845
+ # in Table_Instance.setup_instance
846
+
847
+ if @loaded_from_cache then
848
+ @attribute_values = instance_attrib_values
849
+ else
850
+ @attribute_values = Hash.new
851
+ values = instance_attrib_values
852
+ field_index = 0
853
+ self.class.get_all_table_names.each { |table|
854
+ @attribute_values[table] = Hash.new
855
+ field_names = self.class.get_attributes[table]
856
+ for attr_index in 0...field_names.length do
857
+ @attribute_values[table][field_names[attr_index]] = values[field_index]
858
+ field_index += 1
859
+ end
860
+ }
861
+ end
862
+ # @attribute_values.update(instance_attrib_values)
863
+
864
+ setup_instance()
865
+
866
+ end # }}}
867
+
868
+ public
869
+
870
+ ##########################################################################
871
+ # If this model is not to be located in a projects default context,
872
+ # you can tell Cuba which context to use via
873
+ #
874
+ # context :other_context_name
875
+ #
876
+ def self.context(context_name)
877
+ # {{{
878
+ @context = context_name
879
+ end
880
+ def self.get_context
881
+ @context
882
+ end
883
+ # }}}
884
+
885
+ public
886
+
887
+ ##########################################################################
888
+ # use table :tablename if class != table only.
889
+ def self.table(_table, _schema=nil)
890
+ # {{{
891
+
892
+ set_table_name(_table.to_s)
893
+ schema(_schema.to_s) unless _schema.nil?
894
+
895
+ load_attribute_fields();
896
+
897
+ end # }}}
898
+
899
+ private
900
+
901
+ ##########################################################################
902
+ # use schema :schemaname if there is a schema only.
903
+ # @schema getters/setters {{{
904
+ def self.schema(_schema)
905
+ set_table_name( _schema.to_s+'.'+get_table_name )
906
+ end
907
+ def self.get_schema # :nodoc:
908
+ @schema
909
+ end
910
+ # }}}
911
+
912
+ protected
913
+
914
+ ##########################################################################
915
+ # usage in derived classes:
916
+ # primary_key :some_field, :some_field_sequence_name
917
+ # primary_key :some_other_field, :some_other_field_sequence_name
918
+ #
919
+ # don't extend primary_keys of parent!
920
+ def self.primary_key(*prim_key)
921
+ # {{{
922
+
923
+ keys = Array.new(1,get_table_name) + Array.new(1,prim_key.at(0))
924
+ add_primary_keys(keys)
925
+
926
+ add_sequence(get_table_name, prim_key.at(0), prim_key.at(1))
927
+
928
+ # if sequence is given, this primary key is an implicit attribute:
929
+ if(prim_key.at(1).nil?) then
930
+ add_explicit_attribute(get_table_name, prim_key.at(0).to_s)
931
+ else
932
+ add_implicit_attribute(get_table_name, prim_key.at(0).to_s)
933
+ end
934
+
935
+ end # }}}
936
+
937
+ # Called in Cuba.import_imp_model. Defines prepared statements like
938
+ # The_Model.by_id(id), The_Model.latest_entries(id, amount) etc.
939
+ def self.define_default_preps
940
+ return
941
+ unless @@prepared_statements[:default_preps] then
942
+ pkey_attrib_name = table_name + '.' << @primary_keys[table_name].first.to_s
943
+ prepare(:_by_id, Lore::Type.integer) { |e|
944
+ e.where(self.__send__(pkey_attrib_name.to_s.split('.')[-1].intern) == Lore::Clause.new('$1'))
945
+ e.limit(1)
946
+ }
947
+ prepare(:_latest, Lore::Type.integer) { |e|
948
+ e.where(true)
949
+ e.order_by(pkey_attrib_name, :desc)
950
+ e.limit(Lore::Clause.new('$1'))
951
+ }
952
+ @@prepared_statements[:default_preps] = true
953
+ end
954
+ end
955
+
956
+ def self.by_id(entity_id)
957
+ begin
958
+ return _by_id(entity_id).first
959
+ rescue ::Exception => excep
960
+ if @@prepared_statements[:default_preps] then
961
+ raise excep
962
+ else
963
+ raise ::Exception.new(excep.message + ' (call define_default_preps first?)')
964
+ end
965
+ end
966
+ end
967
+
968
+ protected
969
+
970
+ ##########################################################################
971
+ # usage in derived classes:
972
+ # has_a :table_a, :foreign_key_1, foreign_key_2, ...
973
+ # has_a :table_b, :foreign_key_1, foreign_key_2, ...
974
+ #
975
+ # note that *foreign_keys fields in _this_ table,
976
+ # not the foreign one.
977
+ # we want foreign_keys to look like: hash { 'table_a' => array {'id',
978
+ # 'field' }
979
+ # 'table_b' => array {'table_b_id'}
980
+ # thus allowing key tuples and free naming of table fields.
981
+ def self.has_a(*args)
982
+ # {{{
983
+
984
+ if (args.length < 2) then
985
+ raise Lore::Exception::Invalid_Usage.new('has_a expects at least Type, :foreign_key_name');
986
+ end
987
+
988
+ accessor = args.at(0)
989
+
990
+ foreign_table_name = accessor.get_table_name
991
+ # convert schema.table => table:
992
+ foreign_table = foreign_table_name.split('.').at(1)
993
+ # convert Module::Other::Klass_Name => klass_name:
994
+ foreign_name = accessor.to_s.split('::').at(-1).downcase
995
+
996
+ foreign_keys = Array.new(1,foreign_table_name) + args[1..-1]
997
+ add_foreign_keys(foreign_keys)
998
+
999
+ new_has_a = Array.new(1,foreign_table_name)
1000
+
1001
+ new_has_a = get_has_a + new_has_a
1002
+ set_has_a(new_has_a)
1003
+
1004
+ add_has_a_klass(accessor, args[1..-1])
1005
+
1006
+ define_entity_access_methods(accessor, args[1..-1])
1007
+
1008
+ end # }}}
1009
+
1010
+ def self.maps(*accessors)
1011
+ @primary_keys = Hash.new
1012
+ accessors.each { |acc|
1013
+ acc_pkeys = acc.get_primary_keys
1014
+ # Define accessor as aggregation. This also defines
1015
+ # acc's primary keys as own primary keys.
1016
+ aggregates acc, acc_pkeys
1017
+ }
1018
+ end
1019
+
1020
+ private
1021
+
1022
+ def self.define_attribute_clause_methods(accessor)
1023
+
1024
+ if !accessor.get_context.nil? && @context != accessor.get_context then
1025
+ Lore::Context.enter(accessor.get_context)
1026
+ context_switched = true
1027
+ end
1028
+ if accessor.get_attributes[accessor.get_table_name] then
1029
+ accessor.get_attributes[accessor.get_table_name].each { |attribute|
1030
+ # Only define methods on own attributes or on
1031
+ # attributes only a parent Table_Accessor provides:
1032
+ if accessor == self || !(get_attributes[get_table_name].include? attribute.to_s) then
1033
+ attribute_type = accessor.get_attribute_types[accessor.get_table_name][attribute]
1034
+ # log('Expected attribute type for ' << accessor.get_table_name + '.' << attribute.to_s + ': ' << Lore::Type.type_name(attribute_type).to_s + '(' << attribute_type.to_s + ')' )
1035
+ method =
1036
+ "def self.#{attribute}()
1037
+ Clause.new('#{accessor.get_table_name}.#{attribute}', '', '', { :add_types => [ #{attribute_type}], :add_name => '' })
1038
+ end"
1039
+ class_eval(method)
1040
+
1041
+ "def #{attribute}()
1042
+ attr(:#{attribute})
1043
+ end"
1044
+ class_eval(method)
1045
+
1046
+ method =
1047
+ "def set_#{attribute}(value)
1048
+ set_attribute_value(:#{attribute.to_s}, value)
1049
+ end"
1050
+ class_eval(method)
1051
+
1052
+ method =
1053
+ "def #{attribute}=(value)
1054
+ set_attribute_value(:#{attribute.to_s}, value)
1055
+ end"
1056
+ class_eval(method)
1057
+
1058
+ end
1059
+ }
1060
+ end
1061
+ Lore::Context.leave if context_switched
1062
+
1063
+ end
1064
+
1065
+ private
1066
+
1067
+ # Meta-programs class instance methods for accessing types
1068
+ # associated via has_a.
1069
+ def self.define_entity_access_methods(accessor, foreign_keys, type_name=nil)
1070
+
1071
+ type_name = accessor.to_s.split('::').at(-1).downcase unless type_name
1072
+
1073
+ log('defining method '+type_name+'_entity')
1074
+ define_method(type_name+'_entity') {
1075
+
1076
+ has_a_keys = Hash.new
1077
+ foreign_keys.each { |foreign_key|
1078
+ # self.<foreign_key> will return corresponding value
1079
+ # no matter if this klass or a super klass is holding it
1080
+ has_a_keys[accessor[foreign_key]] = self.__send__ foreign_key
1081
+ }
1082
+ return accessor.load(has_a_keys)
1083
+ }
1084
+
1085
+ log('defining method set_'+type_name+'_entity')
1086
+ define_method('set_'+type_name+'_entity') { |other|
1087
+ foreign_keys.each { |foreign_key|
1088
+ # other.<foreign_key> will return corresponding value
1089
+ # no matter if this klass or a super klass is holding it
1090
+ self[foreign_key] = other.__send__ foreign_key
1091
+ }
1092
+ }
1093
+ define_method('set_'+type_name+'_entity!') { |other|
1094
+ self.__send__ 'set_'+type_name+'_entity', other
1095
+ self.commit
1096
+ }
1097
+
1098
+ end
1099
+
1100
+ private
1101
+
1102
+ # Meta-programs class instance methods for accessing types
1103
+ # associated via has_n.
1104
+ def self.define_entities_access_methods(accessor, values)
1105
+
1106
+ type_name = accessor.to_s.split('::').at(-1).downcase unless type_name
1107
+
1108
+ log('defining method add_'+type_name+'_entity')
1109
+ define_method('add_'+type_name+'_entity') { |values|
1110
+ values.update(get_primary_key_values)
1111
+ accessor.create(values)
1112
+ }
1113
+ log('defining method '+type_name+'_entities')
1114
+ define_method(type_name+'_entities') {
1115
+ foreign_key_values = Hash.new
1116
+
1117
+ accessor.get_foreign_keys[self.table_name].each { |key|
1118
+ foreign_key_values[key] = get_attribute_value[key]
1119
+ }
1120
+ accessor.all_with(foreign_key_values)
1121
+ }
1122
+
1123
+ end
1124
+
1125
+ protected
1126
+
1127
+ ##########################################################################
1128
+ # usage in derived classes:
1129
+ # has_a_named :table_a, :a_foo, :foreign_key_1, :foreign_key_2, ...
1130
+ # has_a_named :table_a, :a_bar, :foreign_key_1, :foreign_key_2, ...
1131
+ #
1132
+ # This method allows one type to aggregate the same type twice, referencing
1133
+ # them under different names, such as (see example) get_a_foo(), get_a_bar().
1134
+ def self.has_a_named(*args)
1135
+ # {{{
1136
+
1137
+ if (args.length < 3) then
1138
+ raise Lore::Model_Exception.new(self, 'has_a_named expects at least :Type, :handle_name, :foreign_key_name');
1139
+ end
1140
+
1141
+ foreign_table_name = args.at(0).get_table_name
1142
+ # convert schema.table => table:
1143
+ foreign_table = args.at(0).get_table_name.split('.').at(1)
1144
+ # convert Module::Other::Klass_Name => klass_name:
1145
+ foreign_name = args.at(0).to_s.split('::')[-1].downcase
1146
+
1147
+ foreign_keys = Array.new(1,foreign_table_name) + args[2...args.length]
1148
+ add_foreign_keys(foreign_keys)
1149
+
1150
+ new_has_a = Array.new(1,foreign_table_name)
1151
+ new_has_a = get_has_a + new_has_a
1152
+ set_has_a(new_has_a)
1153
+
1154
+ # TODO: To be wrapped by Table_Instance, feeding this method with
1155
+ # primary keys required to get referenced has_a tuple.
1156
+ if args.at(2).nil? then type_name = foreign_name
1157
+ else type_name = args.at(2).to_s
1158
+ end
1159
+
1160
+ log('defining method get_'+type_name)
1161
+
1162
+ define_method('get_'+type_name) {
1163
+ # see self.has_a
1164
+ }
1165
+
1166
+ log('defining method set_'+type_name)
1167
+ # TODO: To be wrapped by Table_Instance, feeding this method with
1168
+ # primary keys required to set referenced has_a tuple.
1169
+ define_method('set_'+type_name) {|*keys|
1170
+ # see self.has_a
1171
+ }
1172
+
1173
+ end # }}}
1174
+
1175
+ protected
1176
+
1177
+ def self.belongs_to(*args)
1178
+ parent = args[0]
1179
+ foreign_table_name = parent.get_table_name
1180
+ own_foreign_keys = args[1..-1]
1181
+
1182
+ own_foreign_keys = own_foreign_keys.map { |x| x = x.to_s }
1183
+ add_explicit_attributes(get_table_name => own_foreign_keys)
1184
+
1185
+ args[1..-1].each { |a| hide_attribute(a.to_s) }
1186
+ end
1187
+
1188
+ ##########################################################################
1189
+ # usage in derived classes:
1190
+ # has_n :table_a, :foreign_key_1, foreign_key_2, ...
1191
+ # has_n :table_b, :foreign_key_1, foreign_key_2, ...
1192
+ def self.has_n(*args)
1193
+ # {{{
1194
+
1195
+ if (args.length < 2) then
1196
+ raise Lore::Exception::Invalid_Parameter.new('has_n expects at least :Type, :foreign_key_name');
1197
+ end
1198
+
1199
+ foreign_table_name = args.at(0).get_table_name
1200
+ # convert schema.table => table:
1201
+ foreign_table = args.at(0).get_table_name.split('.').at(1)
1202
+ # convert Module::Other::Klass_Name => klass_name:
1203
+ foreign_name = args.at(0).to_s.split('::')[-1].downcase
1204
+
1205
+ foreign_keys = Array.new(1,foreign_table_name) + args[1...args.length]
1206
+ add_foreign_keys(foreign_keys)
1207
+
1208
+ new_has_n = Array.new(1,foreign_table_name)
1209
+ new_has_n = get_has_n + new_has_n
1210
+ set_has_n(new_has_n)
1211
+
1212
+ method_suffix = foreign_name
1213
+
1214
+ log('defining method '+method_suffix+'_entities')
1215
+ # TODO: To be wrapped by Table_Instance, feeding this method with
1216
+ # primary keys required to get referenced has_n tuples.
1217
+ define_method(method_suffix+'_entities') { |*key_values|
1218
+
1219
+ query_args = Hash.new
1220
+ query_args['_table'] = foreign_table_name
1221
+ for arg_counter in 0...key_values.length do
1222
+
1223
+ query_args[@foreign_keys[foreign_table_name][arg_counter]] = key_values[arg_counter]
1224
+
1225
+ end
1226
+ #Table_Selector.perform(query_args)
1227
+ }
1228
+ # attribs is expected to be a Hash { 'field'=>'value', ... }
1229
+ log('defining method add_'+method_suffix)
1230
+ # TODO: To be wrapped by Table_Instance, feeding this method with
1231
+ # primary keys and attribute values required to add referenced
1232
+ # has_n tuples.
1233
+ define_method('add_'+method_suffix) {|*attribs|
1234
+
1235
+ #query_args = Hash.new
1236
+ #query_args['_table'] = foreign_table_name
1237
+ #query_args.update(attribs)
1238
+
1239
+ #Table_Inserter.perform(query_args)
1240
+ }
1241
+
1242
+ log('defining method remove_'+method_suffix)
1243
+ # TODO: To be wrapped by Table_Instance, feeding this method with
1244
+ # primary keys required to remove a referenced has_n tuple.
1245
+ define_method('remove_'+method_suffix) {|*keys|
1246
+
1247
+ query_args = Hash.new
1248
+ query_args['_table'] = foreign_table_name
1249
+ for arg_counter in 0...keys.length do
1250
+
1251
+ #query_args[@foreign_keys[foreign_table_name][arg_counter]] = keys[arg_counter]
1252
+
1253
+ end
1254
+ #Table_Deleter.perform(query_args)
1255
+ }
1256
+
1257
+ end # }}}
1258
+
1259
+ protected
1260
+
1261
+ ##########################################################################
1262
+ # usage in derived classes:
1263
+ # is_a Other::Module::Other_Klass
1264
+ # is_a Another::Module::Another_Klass
1265
+ #
1266
+ # Effects:
1267
+ # If foo.is_a bar then creating a foo will also create a bar instance.
1268
+ # Also, loading a foo will aggregate bar automatically.
1269
+ def self.is_a(*args)
1270
+ # {{{
1271
+
1272
+ parent = args.at(0)
1273
+
1274
+ # before: parent_pkeys = Hash { Parent => [id, name] }
1275
+ # after: @@primary_keys = Hash { Parent => [id, name]
1276
+ # This => [this, key] }
1277
+
1278
+ if @input_filters.nil? then
1279
+ @input_filters = parent.get_input_filters
1280
+ else
1281
+ @input_filters.update parent.get_input_filters if parent.get_input_filters
1282
+ end
1283
+
1284
+ if @output_filters.nil? then
1285
+ @output_filters = parent.get_output_filters
1286
+ else
1287
+ @output_filters.update parent.get_output_filters if parent.get_output_filters
1288
+ end
1289
+
1290
+ @primary_keys.update(parent.get_primary_keys)
1291
+
1292
+ @sequences.update(parent.get_sequences)
1293
+ # only add parent's own attributes:
1294
+ add_attributes(parent.get_attributes)
1295
+ add_attribute_types(parent.get_attribute_types)
1296
+
1297
+ add_is_a(parent.get_table_name, parent.get_is_a)
1298
+ add_is_a_klass(parent, @table_name.to_s+'.' << args.at(1).to_s)
1299
+
1300
+ parent.get_has_a_klasses.each_pair { |foreign_key, klass|
1301
+ has_a klass, foreign_key.split('.')[-1]
1302
+ }
1303
+ set_has_a_klasses(get_has_a_klasses.update(parent.get_has_a_klasses))
1304
+
1305
+ add_explicit(parent.get_explicit)
1306
+
1307
+ add_explicit_attributes(parent.get_explicit_attributes)
1308
+ add_implicit_attributes(parent.get_implicit_attributes)
1309
+
1310
+ @foreign_keys = Hash.new if @foreign_keys.nil?
1311
+ @foreign_keys.update(parent.get_foreign_keys)
1312
+
1313
+ foreign_keys = Array.new(1,parent.get_table_name) + args[1...args.length]
1314
+
1315
+ add_foreign_keys(foreign_keys)
1316
+
1317
+ get_foreign_keys[parent.get_table_name].each { |attrib|
1318
+ # add implicit attribute on *own* table, as it is implicit *here*,
1319
+ # not in the foreign table
1320
+ add_implicit_attribute(get_table_name, attrib.to_s)
1321
+ }
1322
+
1323
+ define_attribute_clause_methods(parent)
1324
+
1325
+ end # }}}
1326
+
1327
+ protected
1328
+
1329
+ ##########################################################################
1330
+ # usage in derived classes:
1331
+ # aggregates Other::Module::Other_Klass
1332
+ # aggregates Another::Module::Another_Klass
1333
+ #
1334
+ # Effects:
1335
+ # Unlike is_a, If foo.aggregates bar then creating/deleting a foo will not create/delete
1336
+ # a bar instance, but loading a foo will aggregate bar automatically, like
1337
+ # is_a.
1338
+ def self.aggregates(*args)
1339
+ # {{{
1340
+
1341
+ parent = args.at(0)
1342
+
1343
+ # before: parent_pkeys = Hash { Parent => [id, name] }
1344
+ # after: @@primary_keys = Hash { Parent => [id, name]
1345
+ # This => [this, key] }
1346
+ @primary_keys.update(parent.get_primary_keys)
1347
+
1348
+ # only add parent's own attributes:
1349
+ add_attributes(parent.get_attributes)
1350
+ add_attribute_types(parent.get_attribute_types)
1351
+
1352
+ add_aggregate(parent.get_table_name, parent.get_is_a)
1353
+ add_aggregate_klass(parent, args[1..-1])
1354
+
1355
+ set_has_a_klasses(get_has_a_klasses.update(parent.get_has_a_klasses))
1356
+
1357
+ # Hide aggregated attributes for create, update and form
1358
+ # generation:
1359
+ add_implicit_attributes(parent.get_attributes)
1360
+ add_implicit_attributes(parent.get_implicit_attributes)
1361
+
1362
+ foreign_keys = Array.new(1,parent.get_table_name) + args[1..-1]
1363
+
1364
+ add_foreign_keys(foreign_keys)
1365
+
1366
+ get_foreign_keys[parent.get_table_name].each { |attrib|
1367
+ # add implicit attribute on *own* table, as it is implicit *here*,
1368
+ # not in the foreign table
1369
+ add_implicit_attribute(get_table_name, attrib.to_s)
1370
+ }
1371
+
1372
+ use_label(parent.get_labels)
1373
+
1374
+ define_entity_access_methods(parent, args[1..-1])
1375
+
1376
+ end # }}}
1377
+
1378
+ private
1379
+
1380
+ def self.add_is_a(table_name, derive_tree)
1381
+ # {{{
1382
+
1383
+ new_is_a = Hash.new
1384
+ new_is_a[table_name] = derive_tree
1385
+ new_is_a.update(get_is_a)
1386
+
1387
+ set_is_a(new_is_a)
1388
+
1389
+ end # }}}
1390
+
1391
+ private
1392
+
1393
+ def self.add_is_a_klass(klass, foreign_key)
1394
+ # {{{
1395
+
1396
+ @is_a_klasses = Hash.new if @is_a_klasses.nil?
1397
+ @is_a_klasses[foreign_key] = klass
1398
+
1399
+ end # }}}
1400
+
1401
+ private
1402
+
1403
+ def self.add_aggregate(table_name, derive_tree)
1404
+ # {{{
1405
+
1406
+ new_aggregates = Hash.new
1407
+ new_aggregates[table_name] = derive_tree
1408
+ new_aggregates.update(get_aggregates)
1409
+
1410
+ set_aggregates(new_aggregates)
1411
+
1412
+ end # }}}
1413
+
1414
+ private
1415
+
1416
+ def self.add_aggregate_klass(klass, attributes)
1417
+ # {{{
1418
+
1419
+ new_aggregate_klasses = get_aggregate_klasses
1420
+ attributes.each { |attribute|
1421
+ new_aggregate_klasses[get_table_name+'.' << attribute.to_s] = klass
1422
+ }
1423
+
1424
+ set_aggregate_klasses(new_aggregate_klasses)
1425
+
1426
+ end # }}}
1427
+
1428
+ private
1429
+
1430
+ def self.add_has_a_klass(klass, attributes)
1431
+ # {{{
1432
+
1433
+ new_has_a_klasses = get_has_a_klasses
1434
+ attributes.each { |attribute|
1435
+ new_has_a_klasses[get_table_name+'.' << attribute.to_s] = klass
1436
+ }
1437
+
1438
+ set_has_a_klasses(new_has_a_klasses)
1439
+
1440
+ end # }}}
1441
+
1442
+ protected
1443
+ # Tell Table_Accessor to ignore attribute in update commands
1444
+ # unless it has a non-empty value.
1445
+ def self.explicit(*args)
1446
+ # {{{
1447
+
1448
+ new_explicit = get_explicit
1449
+ new_explicit[get_table_name] = args.map!{|elem| elem.to_s}
1450
+
1451
+ add_explicit(new_explicit)
1452
+
1453
+ end # }}}
1454
+
1455
+ private
1456
+
1457
+ ##########################################################################
1458
+ # Usage:
1459
+ # Cuba::Admin::Container_Local.select({'primary_key_1'=>'2',
1460
+ # 'primary_key_2'=>'38',
1461
+ # 'additionally_where_field'=>'73'
1462
+ # })
1463
+ def self.select_row_by_key(*keys)
1464
+ # {{{
1465
+
1466
+ # Aggregate klasses this klass is derived from and
1467
+ # those it aggregates explicitly:
1468
+ begin
1469
+ result = Lore::Table_Selector.select_on_keys(self, keys)
1470
+ rescue ::Exception => excep
1471
+ raise excep
1472
+ raise ::Exception.new('Unable to load instance from DB (Reason: ' << excep.message << ')')
1473
+ end
1474
+ field_hash = result.get_row()
1475
+
1476
+ end # }}}
1477
+
1478
+ public
1479
+
1480
+ # Same as select, but with predefined primary key values as
1481
+ # hash.
1482
+ def self.select_by_key(*keys)
1483
+ # {{{
1484
+
1485
+ result = Lore::Table_Selector.select_on_keys(self, keys)
1486
+
1487
+ field_array = result.get_rows()
1488
+
1489
+ end # }}}
1490
+
1491
+ public
1492
+
1493
+ def self.update(&block)
1494
+ # {{{
1495
+
1496
+ query_string = Lore::Table_Updater.block_update(self, &block)
1497
+
1498
+ end # def }}}
1499
+
1500
+ public
1501
+
1502
+ def self.select(clause=nil, &block)
1503
+ # {{{
1504
+ GC.disable
1505
+ if(!clause.nil? && !clause.to_s.include?('*,')) then
1506
+
1507
+ query_string = Lore::Table_Selector.select_query(clause.to_s, self, &block)
1508
+ return Clause.new(query_string)
1509
+
1510
+ else
1511
+
1512
+ what = clause.to_s
1513
+ result = Lore::Table_Selector.select_cached(what, self, &block)
1514
+
1515
+ # result = Array.new
1516
+ # db_result.get_rows.each { |row|
1517
+ # result.push(self.new(self, row))
1518
+ # }
1519
+ GC.enable
1520
+ return result
1521
+
1522
+ end
1523
+
1524
+ end # }}}
1525
+
1526
+ public
1527
+
1528
+ def self.select_query(clause=nil, &block)
1529
+ query_string = Lore::Table_Selector.select_query(clause.to_s, self, &block)
1530
+ end
1531
+
1532
+ # Prepares a query for execution. This offers four advantages:
1533
+ # - The query doesn't have to be interpreted by the DB every time
1534
+ # - The query call is available via direct method call.
1535
+ # - DB validates against types, thus preventing SQL injection
1536
+ # - It doesn't Lore require to compose the query string again. This
1537
+ # effects the most significant performance gain (Up to 60% execution
1538
+ # time in some benchmarks)
1539
+ # Usage:
1540
+ #
1541
+ # Article.prepare(:by_name_and_date, Lore::Type::Integer, Lore::Type::Date) { |a,fields|
1542
+ # a.where((Article.article_id == fields[0] &
1543
+ # (Article.date == fields[1]))
1544
+ # }
1545
+ # Article.by_name_and_date('Merry Christmas', '20081224')
1546
+ #
1547
+ # From the PostgreSQL 7.4 Manual:
1548
+ #
1549
+ # "In some situations, the query plan produced by for a prepared statement may be
1550
+ # inferior to the plan produced if the statement were submitted and executed normally.
1551
+ # This is because when the statement is planned and the planner attempts to determine
1552
+ # the optimal query plan, the actual values of any parameters specified in the
1553
+ # statement are unavailable. PostgreSQL collects statistics on the distribution of
1554
+ # data in the table, and can use constant values in a statement to make guesses about
1555
+ # the likely result of executing the statement. Since this data is unavailable when
1556
+ # planning prepared statements with parameters, the chosen plan may be suboptimal. To
1557
+ # examine the query plan PostgreSQL has chosen for a prepared statement, use
1558
+ # EXPLAIN EXECUTE. "
1559
+ #
1560
+ def self.prepare(plan_name, *args, &block)
1561
+ log('PREPARE: TRYING CLASS METHOD ' << plan_name.to_s)
1562
+ if !@@prepared_statements[plan_name] then
1563
+ Table_Selector.prepare(plan_name, self, args, &block)
1564
+
1565
+ log('PREPARE: CREATE CLASS METHOD ' << plan_name.to_s)
1566
+ instance_eval("
1567
+ def #{plan_name.to_s}(*args)
1568
+ execute_prepared(:#{plan_name}, args)
1569
+ end")
1570
+ @@prepared_statements[plan_name] = true
1571
+ log('PREPARE: CREATED CLASS METHOD ' << plan_name.to_s)
1572
+ end
1573
+ end
1574
+
1575
+ def self.execute_prepared(plan_name, *args)
1576
+ plan_name = "#{table_name.gsub('.','_')}__#{plan_name.to_s}"
1577
+ Table_Selector.select_prepared(plan_name, self, args)
1578
+ end
1579
+
1580
+ public
1581
+
1582
+ # Same as select, but returns
1583
+ def self.select_value(what, &block)
1584
+ # {{{
1585
+ db_result = Lore::Table_Selector.select(what, self, &block)
1586
+ row = db_result.get_row
1587
+ return row[''] if row
1588
+ return {}
1589
+ end # }}}
1590
+
1591
+ public
1592
+
1593
+ def self.select_values(what, &block)
1594
+ # {{{
1595
+ db_result = Lore::Table_Selector.select(what, self, &block)
1596
+ return db_result.get_rows[:values].map { |e|
1597
+ e.first
1598
+ }
1599
+ end # }}}
1600
+
1601
+ public
1602
+
1603
+ ##########################################################################
1604
+ # Wrap explicit select. Example:
1605
+ # SomeModule::SomeAccessor.explicit_insert({
1606
+ # table_name_A =>
1607
+ # {'some_field'=>'2',
1608
+ # 'other_field'=>'3'},
1609
+ # table_name_A =>
1610
+ # {'another_field'=>'5'}
1611
+ # })
1612
+ # Note that field in 'field'=>'value' is exactly the
1613
+ # field name in the table (e.g. table_name_A) it holds.
1614
+ def self.explicit_insert(keys)
1615
+ # {{{
1616
+ Lore::Table_Inserter.perform_insert(self, keys)
1617
+ end # }}}
1618
+
1619
+
1620
+ private
1621
+
1622
+ ##########################################################################
1623
+ # Wrap default select. Example:
1624
+ # SomeModule::SomeAccessor.insert({
1625
+ # 'some_field'=>'2',
1626
+ # 'other_field'=>'3',
1627
+ # 'another_field'=>'5'
1628
+ # })
1629
+ # Note that field in 'field'=>'value' is exactly the
1630
+ # field name in the table it holds.
1631
+ # Table_Accessor.insert basically resolves an explicit
1632
+ # hash and passes it to Table_Accessor.explicit_insert.
1633
+ def self.insert(keys)
1634
+ # {{{
1635
+
1636
+ key_hash = Hash.new
1637
+
1638
+ log('ATTRIBUTES TOTAL: ')
1639
+ log(get_attributes.inspect)
1640
+
1641
+ attribute_name_array = Array.new
1642
+ attribute_table = ''
1643
+ get_attributes.each_pair { |table, fields|
1644
+
1645
+ key_hash[table] = Hash.new
1646
+ keys.each_pair { |attribute, value|
1647
+
1648
+ attribute = attribute.to_s
1649
+ attribute_name_array = attribute.split('.')
1650
+ if attribute_name_array.at(2).nil? then
1651
+ attribute_table = nil
1652
+ else
1653
+ attribute_table = (attribute_name_array.at(0).to_s+'.'+attribute_name_array.at(1).to_s)
1654
+ end
1655
+
1656
+ if fields.include?(attribute) then
1657
+ key_hash[table][attribute] = value unless (value == '' || value.nil?)
1658
+
1659
+ elsif !attribute_table.nil? and
1660
+ fields.include?(attribute_name_array.at(2)) and
1661
+ attribute_table == table then
1662
+
1663
+ key_hash[table][attribute_name_array.at(2)] = value unless (value == '' || value.nil?)
1664
+ end
1665
+ }
1666
+
1667
+ }
1668
+
1669
+ log(key_hash.inspect)
1670
+
1671
+ # sequence values only are known after insert operation,
1672
+ # so we have to retreive the complete key_hash back from
1673
+ # Table_Inserter.perform_insert:
1674
+ key_hash = Lore::Table_Inserter.perform_insert(self, key_hash)
1675
+ # key_hash has been extended by sequence_values now, so we
1676
+ # return it:
1677
+
1678
+ key_hash
1679
+
1680
+ end # }}}
1681
+
1682
+ public
1683
+
1684
+ ##########################################################################
1685
+ # Returns a new Table_Accessor instance by inserting given attribute
1686
+ # values into db and returning an instance for further operations.
1687
+ def self.create(attrib_values)
1688
+ # {{{
1689
+ before_create(attrib_values)
1690
+
1691
+ values = Hash.new
1692
+
1693
+ Lore.log { 'KLASS: ' << attrib_values.class.to_s }
1694
+ Lore.log { 'INSERTING VALUES: ' << attrib_values.inspect }
1695
+ input_filters = get_input_filters
1696
+ attrib_key = ''
1697
+ attrib_name = ''
1698
+
1699
+ attrib_values.each_pair { |attrib_name, attrib_value|
1700
+ if attrib_name.instance_of? Symbol then
1701
+ attrib_key = attrib_name
1702
+ else
1703
+ attrib_key = attrib_name.split('.')[-1].intern
1704
+ end
1705
+
1706
+ if (input_filters && input_filters[attrib_key]) then
1707
+ attrib_values[attrib_name] = input_filters[attrib_key].call(attrib_value)
1708
+ end
1709
+ }
1710
+ log('FILTERED VALUES: ' << attrib_values.inspect)
1711
+ after_filters(attrib_values)
1712
+
1713
+ # Predefine
1714
+ attrib_name_array = Array.new
1715
+ # distribute attrib names to tables:
1716
+ get_attributes().each_pair { |table, attribs|
1717
+ table_values = Hash.new
1718
+
1719
+ attrib_name_array = Array.new
1720
+ log('ALL ATTRIBS: ' << table.to_s + ' <-- ' << attribs.inspect)
1721
+ attrib_values.each_pair { |attrib_name, attrib_value|
1722
+ attrib_name = attrib_name.to_s
1723
+ attrib_name_array = attrib_name.split('.')
1724
+
1725
+ if attribs.include? attrib_name then
1726
+
1727
+ table_values[attrib_name] = attrib_value
1728
+
1729
+ elsif attribs.include? attrib_name_array.at(2) and
1730
+ attrib_name_array.at(0)+'.'+attrib_name_array.at(1) == table then
1731
+ table_values[attrib_name_array.at(2)] = attrib_value
1732
+ end
1733
+ }
1734
+ values[table] = table_values
1735
+ }
1736
+
1737
+ begin
1738
+
1739
+ before_validation(values)
1740
+ Lore::Validation::Parameter_Validator.invalid_params(self,
1741
+ values)
1742
+
1743
+ rescue Lore::Exception::Invalid_Klass_Parameters => ikp
1744
+
1745
+ # log'n'throw
1746
+ ikp.log
1747
+ raise ikp
1748
+
1749
+ end
1750
+
1751
+ before_insert(attrib_values)
1752
+
1753
+ # retreive all final attrib values after insert: (this way, also
1754
+ # sequence values are resolved):
1755
+ #
1756
+ attrib_values = insert(attrib_values)
1757
+
1758
+ select_keys = Hash.new
1759
+ if @primary_keys then
1760
+ @primary_keys[table_name].each { |key|
1761
+ select_keys[table_name + '.' << key] = attrib_values[table_name][key]
1762
+ }
1763
+ end
1764
+
1765
+ # Create klass instance via new:
1766
+ # Cuba::Module.new(self, attrib_values)
1767
+
1768
+ # Create klass instance via load (wrapper of new):
1769
+ obj = self.load(select_keys)
1770
+ after_create(obj)
1771
+
1772
+ return obj
1773
+
1774
+ end # }}}
1775
+
1776
+ public
1777
+
1778
+ ##########################################################################
1779
+ # Return new Table_Accessor instance by loading an existing entry from
1780
+ # table.
1781
+ def self.load(keys)
1782
+ # {{{
1783
+
1784
+ before_load(keys)
1785
+
1786
+ select_keys = Hash.new
1787
+
1788
+ get_primary_keys[table_name].each { |attrib_name|
1789
+ value = keys[attrib_name.intern]
1790
+ value = keys[attrib_name.to_s] if value.nil?
1791
+ value = keys[table_name+'.'+attrib_name.to_s] if value.nil?
1792
+ select_keys[table_name+'.'+attrib_name.to_s] = value unless value.nil?
1793
+ }
1794
+
1795
+ return false if select_keys.empty?
1796
+
1797
+ log('Loading with prim keys: ' << select_keys.inspect)
1798
+ instance_attribs_hash = select_row_by_key(select_keys)
1799
+
1800
+ return false if instance_attribs_hash.nil?
1801
+
1802
+ instance = new(instance_attribs_hash)
1803
+
1804
+ #TODO:
1805
+ # after_load(instance)
1806
+
1807
+ return instance
1808
+ end # }}}
1809
+
1810
+ public
1811
+
1812
+ ##########################################################################
1813
+ # Delete this object and use Table_Deleter to delete its entity tuple
1814
+ # from database.
1815
+ def self.delete(value_keys=nil, &block)
1816
+ # {{{
1817
+
1818
+ if value_keys then
1819
+ before_delete(value_keys)
1820
+ Lore::Table_Deleter.perform_delete(self, value_keys)
1821
+ after_delete(value_keys)
1822
+ else
1823
+ Lore::Table_Deleter.block_delete(self, &block)
1824
+ end
1825
+
1826
+ end # }}}
1827
+
1828
+ private
1829
+
1830
+ ##########################################################################
1831
+ # Send a hollow query to db, thus only field names are returned.
1832
+ # This works (and has to work) on empty tables, too.
1833
+ #
1834
+ # For every attribute, a class method with the attribute's name is defined
1835
+ # in order to retreive the absolute field name (schema.table.field) by calling
1836
+ # Table_Accessor.field
1837
+ def self.load_attribute_fields()
1838
+ # {{{
1839
+ if !@attributes.nil? then
1840
+ return @attributes
1841
+ end
1842
+
1843
+ @attributes = Hash.new
1844
+
1845
+ if(get_table_name != '') then
1846
+
1847
+ Lore::Context.enter(@context) unless @context.nil?
1848
+ begin
1849
+ fields_result = Lore::Connection.perform('SELECT * FROM '+get_table_name+' WHERE false')
1850
+ ensure
1851
+ Lore::Context.leave unless @context.nil?
1852
+ end
1853
+
1854
+ own_attributes = Hash.new
1855
+ own_attributes[get_table_name] = fields_result.get_field_names()
1856
+ own_types = Hash.new
1857
+ own_types[get_table_name] = fields_result.get_field_types()
1858
+ add_attributes(own_attributes)
1859
+ add_attribute_types(own_types)
1860
+
1861
+ Lore.log { 'Defined attributes: ' << @attributes.inspect }
1862
+
1863
+ define_attribute_clause_methods(self)
1864
+
1865
+ end # if
1866
+ return @attributes
1867
+
1868
+ end # }}}
1869
+
1870
+ public
1871
+
1872
+ ##########################################################################
1873
+ # Simulates inheritance: Delegate missing methods to parent Table_Accessor.
1874
+ def self.method_missing(meth)
1875
+ if @is_a_klasses then
1876
+ @is_a_klasses.each_pair { |foreign_key, k|
1877
+ return (k.__send__(meth.to_s)) if k.respond_to? meth
1878
+ }
1879
+ end
1880
+ raise ::Exception.new('Undefined method '<< meth.to_s << ' for ' << self.to_s)
1881
+ end
1882
+
1883
+ public
1884
+
1885
+ ##########################################################################
1886
+ # Inspect method
1887
+ def self.inspect
1888
+ # {{{
1889
+
1890
+ # return self.to_s + ' : Table_Accessor'
1891
+
1892
+ dump = 'Accessor: ' << self.to_s
1893
+ # dump << "\n\t" << 'own table name: '+get_table_name
1894
+ # dump << "\n\t" << 'attributes: '
1895
+ # dump << "\n\t" << @attributes.inspect
1896
+ # dump << "\n\t" << 'explicit attributes: '
1897
+ # dump << "\n\t" << @explicit_attributes.inspect
1898
+ # dump << "\n\t" << 'implicit attributes: '
1899
+ # dump << "\n\t" << @implicit_attributes.inspect
1900
+ # dump << "\n\t" << 'constraints: '
1901
+ # dump << "\n\t" << @constraints.inspect
1902
+ # dump << "\n\t" << 'attribute types: '
1903
+ # dump << "\n\t" << @attribute_types.inspect
1904
+ # dump << "\n\t" << 'primary keys: '
1905
+ # dump << "\n\t" << @primary_keys.inspect
1906
+ # dump << "\n\t" << 'sequences: '
1907
+ # dump << "\n\t" << @sequences.inspect
1908
+ # dump << "\n\t" << 'foreign keys: '
1909
+ # dump << "\n\t" << @foreign_keys.inspect
1910
+ # dump << "\n\t" << 'has_a: '
1911
+ # dump << "\n\t" << @has_a.inspect
1912
+ # dump << "\n\t" << 'has_n: '
1913
+ # dump << "\n\t" << @has_n.inspect
1914
+ # dump << "\n\t" << 'is_a: '
1915
+ # dump << "\n\t" << @is_a.inspect
1916
+ # dump << "\n\t" << 'explicit: '
1917
+ # dump << "\n\t" << @explicit.inspect
1918
+ # dump << "\n"
1919
+
1920
+ end # }}}
1921
+
1922
+ end # class
1923
+
1924
+
1925
+ end # module
1926
+