lore 0.4.2

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