momomoto 0.1.19 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,12 +2,7 @@
2
2
  require 'postgres'
3
3
  require 'singleton'
4
4
 
5
- require 'momomoto/information_schema/columns'
6
- require 'momomoto/information_schema/table_constraints'
7
- require 'momomoto/information_schema/key_column_usage'
8
- require 'momomoto/information_schema/routines'
9
- require 'momomoto/information_schema/fetch_procedure_columns'
10
- require 'momomoto/information_schema/fetch_procedure_parameters'
5
+ require 'momomoto/information_schema'
11
6
 
12
7
  ## Momomoto is a database abstraction layer
13
8
  module Momomoto
@@ -109,6 +104,17 @@ module Momomoto
109
104
  pkeys
110
105
  end
111
106
 
107
+
108
+ # get table type from database
109
+ # should work with any SQL2003 compliant DBMS
110
+ def get_table_type( table_name, schema_name )
111
+ begin
112
+ Information_schema::Tables.select_single({:table_name=>table_name,:table_schema=>schema_name}).table_type
113
+ rescue => e
114
+ raise CriticalError, "Table #{table_name} does not exist. #{e.to_s}"
115
+ end
116
+ end
117
+
112
118
  # fetch column definitions from database
113
119
  # should work with any SQL2003 compliant DBMS
114
120
  def fetch_table_columns( table_name, schema_name = nil ) # :nodoc:
@@ -0,0 +1,20 @@
1
+
2
+ module Momomoto::Information_schema
3
+
4
+ def self.const_missing( table )
5
+ raise CriticalError, "Invalid name." unless table.to_s.downcase.match(/^[a-z0-9_]+$/)
6
+ begin
7
+ require "momomoto/information_schema/#{table.to_s.downcase}"
8
+ ::Momomoto::Information_schema::const_get( table )
9
+ rescue LoadError
10
+ klass = Class.new( ::Momomoto::Table )
11
+ klass.schema_name = "information_schema"
12
+ klass.table_type = "VIEW"
13
+ klass.primary_keys( [] )
14
+
15
+ ::Momomoto::Information_schema.const_set(table, klass)
16
+ end
17
+ end
18
+
19
+ end
20
+
@@ -12,6 +12,7 @@ module Momomoto
12
12
  class Columns < Momomoto::Table
13
13
  primary_keys( [] )
14
14
  schema_name( "information_schema" )
15
+ table_type( "VIEW" )
15
16
  columns( { :table_catalog => Momomoto::Datatype::Text.new,
16
17
  :table_schema => Momomoto::Datatype::Text.new,
17
18
  :table_name => Momomoto::Datatype::Text.new,
@@ -17,6 +17,9 @@ module Momomoto
17
17
  # array containing the primary key fields of the table
18
18
  momomoto_attribute :primary_keys
19
19
 
20
+ # the table type of this table
21
+ momomoto_attribute :table_type
22
+
20
23
  class << self
21
24
 
22
25
  # get the full name of table including, if set, schema
@@ -24,6 +27,16 @@ module Momomoto
24
27
  "#{ schema_name ? schema_name + '.' : ''}#{table_name}"
25
28
  end
26
29
 
30
+ # is this a base table
31
+ def base_table?
32
+ table_type == "BASE TABLE"
33
+ end
34
+
35
+ # is this a view
36
+ def view?
37
+ table_type == "VIEW"
38
+ end
39
+
27
40
  # Searches for records and returns an Array containing the records.
28
41
  # There are a bunch of different use cases as this method is the primary way
29
42
  # to access all rows in the database.
@@ -281,6 +294,15 @@ module Momomoto
281
294
  classname.split('::').last.downcase.gsub(/[^a-z_0-9]/, '')
282
295
  end
283
296
 
297
+ # get namespace of current class
298
+ def namespace
299
+ if self.name.split('::').length > 1
300
+ const_get( self.name.split('::')[0..-2].join('::') )
301
+ else
302
+ Object
303
+ end
304
+ end
305
+
284
306
  # initializes a table class
285
307
  def initialize
286
308
  return if initialized
@@ -289,8 +311,7 @@ module Momomoto
289
311
  @table_name ||= construct_table_name( self.name )
290
312
 
291
313
  @columns ||= database.fetch_table_columns( table_name(), schema_name() )
292
- raise CriticalError, "No fields in table #{table_name}" if columns.keys.empty?
293
-
314
+ @table_type ||= database.get_table_type( table_name, schema_name )
294
315
  @primary_keys ||= database.fetch_primary_keys( table_name(), schema_name() )
295
316
  @column_order = @columns.keys
296
317
  @default_order ||= nil
@@ -299,6 +320,89 @@ module Momomoto
299
320
  initialize_row( const_get( :Row ), self )
300
321
  @row_cache = {}
301
322
 
323
+ # define helper methods for foreign key relations
324
+ if base_table?
325
+
326
+ # find all columns that reference other tables and add helper methods for those keys
327
+ Information_schema::Table_constraints.select({:table_name=>table_name,:table_schema=>schema_name,:constraint_type=>'FOREIGN KEY'}).each do | fk |
328
+ referenced_columns = Information_schema::Constraint_column_usage.select({:constraint_name=>fk.constraint_name,:constraint_schema=>fk.constraint_schema})
329
+ raise CriticalError, "Foreign key constraint without referenced columns" if referenced_columns.length == 0
330
+ ref_table = referenced_columns[0].table_name
331
+ # check if there is already a method by that name defined on Row
332
+ if !const_get( :Row ).instance_methods.member?( ref_table )
333
+ begin
334
+ klass = namespace.const_get( ref_table.capitalize )
335
+ rescue
336
+ # if there is no such class yet we create one in the appropriate namespace
337
+ klass = Class.new( Momomoto::Table )
338
+ klass.table_name = ref_table
339
+ klass.schema_name = referenced_columns[0].table_schema
340
+ namespace.const_set( ref_table.capitalize, klass )
341
+ end
342
+ ref_columns = referenced_columns.map(&:column_name).map(&:to_sym)
343
+ fk_helper_single( ref_table, klass, ref_columns )
344
+ end
345
+ end
346
+
347
+ # find other tables that reference this table and add helper methods
348
+ if primary_keys.length == 1
349
+ Information_schema::Constraint_column_usage.select({:table_name=>table_name,:table_schema=>schema_name,:column_name=>primary_keys[0]}).each do | fk |
350
+ constraint = Information_schema::Table_constraints.select({:constraint_name=>fk.constraint_name,:constraint_schema=>fk.constraint_schema,:constraint_type=>'FOREIGN KEY'})[0]
351
+ # check if there is already a method by that name defined on Row
352
+ if constraint && !const_get( :Row ).instance_methods.member?( constraint.table_name )
353
+
354
+ begin
355
+ klass = namespace.const_get( constraint.table_name.capitalize )
356
+ rescue
357
+ # if there is no such class yet we create one in the appropriate namespace
358
+ klass = Class.new( Momomoto::Table )
359
+ klass.table_name = constraint.table_name
360
+ klass.schema_name = constraint.table_schema
361
+ namespace.const_set( constraint.table_name.capitalize, klass )
362
+ end
363
+ fk_helper_multiple( constraint.table_name, klass, primary_keys )
364
+ end
365
+ end
366
+ end
367
+
368
+ end
369
+
370
+ end
371
+
372
+ # Define a helper method +method_name+ for +table_class+
373
+ def fk_helper_single( method_name, table_class, ref_columns )
374
+ var_name = "@#{method_name}".to_sym
375
+ const_set(:Methods, Module.new) if not const_defined?(:Methods)
376
+ const_get(:Methods).send(:define_method, method_name) do
377
+ return instance_variable_get( var_name ) if instance_variable_defined?( var_name )
378
+ conditions = {}
379
+ ref_columns.each do | col | conditions[col] = get_column( col ) end
380
+ begin
381
+ value = table_class.select_single( conditions )
382
+ rescue Momomoto::Nothing_found
383
+ value = nil
384
+ end
385
+ instance_variable_set( var_name, value )
386
+ end
387
+ end
388
+
389
+ # Define a helper method +method_name+ for +table_class+
390
+ def fk_helper_multiple( method_name, table_class, ref_columns )
391
+ var_name = "@#{method_name}".to_sym
392
+ const_set(:Methods, Module.new) if not const_defined?(:Methods)
393
+ const_get(:Methods).send(:define_method, method_name) do | *args |
394
+ conditions = args[0] ||= {}
395
+ options = args[1] ||= {}
396
+ hash = args.hash
397
+ if instance_variable_defined?( var_name )
398
+ return instance_variable_get( var_name )[hash] if instance_variable_get( var_name )[hash]
399
+ else
400
+ instance_variable_set( var_name, Hash.new )
401
+ end
402
+ ref_columns.each do | col | conditions[col] = get_column( col ) end
403
+ value = table_class.select( conditions, options )
404
+ instance_variable_get( var_name )[hash] = value
405
+ end
302
406
  end
303
407
 
304
408
  # Builds the row class for this table when executing #select.
@@ -67,4 +67,4 @@ CREATE TABLE test_timestamp_without_time_zone( id SERIAL, data TIMESTAMP WITHOUT
67
67
  CREATE TABLE test_int_array( id SERIAL, data int[], PRIMARY KEY(id) );
68
68
  CREATE TABLE test_text_array( id SERIAL, data text[], PRIMARY KEY(id) );
69
69
 
70
-
70
+ CREATE VIEW view_person AS SELECT * FROM person;
@@ -54,6 +54,21 @@ class TestDatabase < Test::Unit::TestCase
54
54
  assert_equal( 1, pk.length )
55
55
  end
56
56
 
57
+ def test_get_table_type
58
+ db = Momomoto::Database.instance
59
+ type = db.get_table_type( 'person', 'public' )
60
+ assert_equal( type, 'BASE TABLE' )
61
+ type = db.get_table_type( 'view_person', 'public' )
62
+ assert_equal( type, 'VIEW' )
63
+ end
64
+
65
+ def test_non_existant_table
66
+ db = Momomoto::Database.instance
67
+ assert_raise( Momomoto::CriticalError ) do
68
+ db.get_table_type( 'nonexistant_table', 'public' )
69
+ end
70
+ end
71
+
57
72
  def test_rollback
58
73
  db = Momomoto::Database.instance
59
74
  db.begin
@@ -313,5 +313,85 @@ class TestTable < Test::Unit::TestCase
313
313
  assert_equal( true, r.dirty? )
314
314
  end
315
315
 
316
+ def test_table_type_getter_and_setter
317
+ c1 = Class.new( Momomoto::Table )
318
+ c1.table_name('person')
319
+ c1.table_type = 'BASE TABLE'
320
+ assert_equal( 'BASE TABLE', c1.table_type )
321
+ c1.table_type = 'VIEW'
322
+ assert_equal( 'VIEW', c1.table_type )
323
+ end
324
+
325
+ def test_table_type
326
+ a = Class.new( Momomoto::Table )
327
+ a.table_name('person')
328
+ assert_equal( "BASE TABLE", a.table_type )
329
+ assert_equal( true, a.base_table? )
330
+ assert_equal( false, a.view? )
331
+
332
+ b = Class.new( Momomoto::Table )
333
+ b.table_name('view_person')
334
+ assert_equal( "VIEW", b.table_type )
335
+ assert_equal( false, b.base_table? )
336
+ assert_equal( true, b.view? )
337
+ end
338
+
339
+ def test_non_existant_table
340
+ a = Class.new( Momomoto::Table )
341
+ a.table_name('nonexistant_table')
342
+ assert_raise( Momomoto::CriticalError ) do
343
+ a.new
344
+ end
345
+ end
346
+
347
+ def test_foreign_keys
348
+ c1 = Class.new( Momomoto::Table )
349
+ c1.table_name('event')
350
+ c1.columns
351
+ assert_equal( true, c1::Methods.instance_methods.include?("event_person"))
352
+ c2 = Class.new( Momomoto::Table )
353
+ c2.table_name('person')
354
+ c2.columns
355
+ assert_equal( true, c2::Methods.instance_methods.include?("event_person"))
356
+ c3 = Class.new( Momomoto::Table )
357
+ c3.table_name('event_person')
358
+ c3.columns
359
+ assert_equal( true, c3::Methods.instance_methods.include?("event"))
360
+ assert_equal( true, c3::Methods.instance_methods.include?("person"))
361
+ event = c1.new
362
+ event.title = 'foreign key test'
363
+ event.write
364
+ person = c2.new
365
+ person.first_name = 'frank'
366
+ person.write
367
+ event_person = c3.new
368
+ event_person.event_id = event.event_id
369
+ event_person.person_id = person.person_id
370
+ event_person.description = 'foreign key test'
371
+ event_person.write
372
+
373
+ assert_equal( event.event_id, event_person.event.event_id )
374
+ assert_equal( person.person_id, event_person.person.person_id )
375
+ assert_equal( 1, event.event_person.length )
376
+ assert_equal( event.event_id, event.event_person[0].event_id )
377
+ assert_equal( person.person_id, event.event_person[0].person_id )
378
+ assert_equal( 1, person.event_person.length )
379
+ assert_equal( event.event_id, person.event_person[0].event_id )
380
+ assert_equal( person.person_id, person.event_person[0].person_id )
381
+
382
+ # test caching
383
+ id = event.event_person.object_id
384
+ assert_equal( id, event.event_person.object_id )
385
+ id = event.event_person(:person_id=>7).object_id
386
+ assert_equal( id, event.event_person(:person_id=>7).object_id )
387
+ id = event_person.event.object_id
388
+ assert_equal( id, event_person.event.object_id )
389
+
390
+
391
+ event_person.delete
392
+ event.delete
393
+ person.delete
394
+ end
395
+
316
396
  end
317
397
 
metadata CHANGED
@@ -1,124 +1,130 @@
1
1
  --- !ruby/object:Gem::Specification
2
- rubygems_version: 0.9.4
3
- specification_version: 1
4
2
  name: momomoto
5
3
  version: !ruby/object:Gem::Version
6
- version: 0.1.19
7
- date: 2009-03-02 00:00:00 +01:00
8
- summary: Momomoto is an object relational mapper for PostgreSQL.
9
- require_paths:
10
- - lib
11
- email: sven@c3d2.de
12
- homepage: http://pentabarf.org/Momomoto
13
- rubyforge_project:
14
- description: Momomoto is an object relational mapper for PostgreSQL.
15
- autorequire: momomoto
16
- default_executable:
17
- bindir: bin
18
- has_rdoc: true
19
- required_ruby_version: !ruby/object:Gem::Version::Requirement
20
- requirements:
21
- - - ">="
22
- - !ruby/object:Gem::Version
23
- version: 1.8.2
24
- version:
4
+ version: 0.2.0
25
5
  platform: ruby
26
- signing_key:
27
- cert_chain:
28
- post_install_message:
29
6
  authors:
30
7
  - Sven Klemm
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-03-13 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: postgres
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.7.9.2008.01.09
24
+ version:
25
+ description: Momomoto is an object relational mapper for PostgreSQL.
26
+ email: sven@c3d2.de
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files: []
32
+
31
33
  files:
32
- - lib/timeinterval.rb
33
34
  - lib/momomoto.rb
34
- - lib/momomoto/database.rb
35
35
  - lib/momomoto/momomoto.rb
36
- - lib/momomoto/row.rb
37
- - lib/momomoto/table.rb
38
- - lib/momomoto/procedure.rb
39
- - lib/momomoto/datatype.rb
40
- - lib/momomoto/base.rb
41
- - lib/momomoto/order.rb
42
- - lib/momomoto/information_schema/table_constraints.rb
43
- - lib/momomoto/information_schema/columns.rb
44
- - lib/momomoto/information_schema/fetch_procedure_parameters.rb
45
- - lib/momomoto/information_schema/fetch_procedure_columns.rb
46
- - lib/momomoto/information_schema/routines.rb
47
- - lib/momomoto/information_schema/key_column_usage.rb
48
- - lib/momomoto/datatype/boolean.rb
49
- - lib/momomoto/datatype/date.rb
36
+ - lib/momomoto/datatype/character.rb
37
+ - lib/momomoto/datatype/bytea.rb
38
+ - lib/momomoto/datatype/array/text.rb
39
+ - lib/momomoto/datatype/array/base.rb
40
+ - lib/momomoto/datatype/array/integer.rb
41
+ - lib/momomoto/datatype/interval.rb
50
42
  - lib/momomoto/datatype/array.rb
51
- - lib/momomoto/datatype/smallint.rb
52
43
  - lib/momomoto/datatype/real.rb
53
44
  - lib/momomoto/datatype/text.rb
54
- - lib/momomoto/datatype/interval.rb
55
- - lib/momomoto/datatype/character.rb
56
- - lib/momomoto/datatype/integer.rb
57
- - lib/momomoto/datatype/time_without_time_zone.rb
58
- - lib/momomoto/datatype/character_varying.rb
45
+ - lib/momomoto/datatype/time_with_time_zone.rb
59
46
  - lib/momomoto/datatype/inet.rb
47
+ - lib/momomoto/datatype/character_varying.rb
48
+ - lib/momomoto/datatype/time_without_time_zone.rb
60
49
  - lib/momomoto/datatype/numeric.rb
61
- - lib/momomoto/datatype/bytea.rb
50
+ - lib/momomoto/datatype/smallint.rb
62
51
  - lib/momomoto/datatype/timestamp_without_time_zone.rb
63
- - lib/momomoto/datatype/timestamp_with_time_zone.rb
64
- - lib/momomoto/datatype/time_with_time_zone.rb
65
52
  - lib/momomoto/datatype/base.rb
53
+ - lib/momomoto/datatype/timestamp_with_time_zone.rb
54
+ - lib/momomoto/datatype/date.rb
55
+ - lib/momomoto/datatype/integer.rb
56
+ - lib/momomoto/datatype/boolean.rb
66
57
  - lib/momomoto/datatype/bigint.rb
67
- - lib/momomoto/datatype/array/integer.rb
68
- - lib/momomoto/datatype/array/text.rb
69
- - lib/momomoto/datatype/array/base.rb
58
+ - lib/momomoto/information_schema.rb
59
+ - lib/momomoto/information_schema/fetch_procedure_columns.rb
60
+ - lib/momomoto/information_schema/fetch_procedure_parameters.rb
61
+ - lib/momomoto/information_schema/columns.rb
62
+ - lib/momomoto/database.rb
63
+ - lib/momomoto/datatype.rb
64
+ - lib/momomoto/row.rb
65
+ - lib/momomoto/table.rb
66
+ - lib/momomoto/base.rb
67
+ - lib/momomoto/order.rb
68
+ - lib/momomoto/procedure.rb
69
+ - lib/timeinterval.rb
70
70
  - sql/procedures.sql
71
- - sql/types.sql
72
71
  - sql/install.sql
73
- - test/test_procedure.rb
74
- - test/test_array.rb
75
- - test/test_boolean.rb
72
+ - sql/types.sql
76
73
  - test/test_real.rb
77
- - test/test_smallint.rb
78
- - test/test_interval.rb
79
- - test/test_text.rb
80
- - test/test_table.rb
81
- - test/test_numeric.rb
74
+ - test/test_time_with_time_zone.rb
82
75
  - test/test_inet.rb
83
- - test/test_time_without_time_zone.rb
84
- - test/test_character_varying.rb
85
- - test/test_character.rb
76
+ - test/test_table.rb
86
77
  - test/test_integer.rb
78
+ - test/test_information_schema.rb
79
+ - test/test_timeinterval.rb
80
+ - test/test_text.rb
87
81
  - test/test_datatype.rb
82
+ - test/test_character.rb
83
+ - test/test_boolean.rb
88
84
  - test/test_row.rb
85
+ - test/test_date.rb
86
+ - test/test_array.rb
87
+ - test/test_bigint.rb
88
+ - test/test_base.rb
89
89
  - test/test_timestamp_without_time_zone.rb
90
- - test/test_time_with_time_zone.rb
91
- - test/test_timeinterval.rb
90
+ - test/test_interval.rb
92
91
  - test/test_database.rb
92
+ - test/test_smallint.rb
93
+ - test/test_procedure.rb
93
94
  - test/test_bytea.rb
94
- - test/test_information_schema.rb
95
- - test/test_base.rb
96
- - test/test_bigint.rb
95
+ - test/test_character_varying.rb
97
96
  - test/test_timestamp_with_time_zone.rb
98
- - test/test_date.rb
97
+ - test/test_time_without_time_zone.rb
98
+ - test/test_numeric.rb
99
99
  - test/test_functions.sql
100
100
  - test/test.sql
101
- - LICENSE
102
101
  - Rakefile
103
- test_files: []
104
-
102
+ - LICENSE
103
+ has_rdoc: true
104
+ homepage: http://pentabarf.org/Momomoto
105
+ post_install_message:
105
106
  rdoc_options: []
106
107
 
107
- extra_rdoc_files: []
108
-
109
- executables: []
110
-
111
- extensions: []
112
-
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: 1.8.6
115
+ version:
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: "0"
121
+ version:
113
122
  requirements:
114
123
  - PostgreSQL 8.1.x or greater
115
- dependencies:
116
- - !ruby/object:Gem::Dependency
117
- name: postgres
118
- version_requirement:
119
- version_requirements: !ruby/object:Gem::Version::Requirement
120
- requirements:
121
- - - ">="
122
- - !ruby/object:Gem::Version
123
- version: 0.7.9.2008.01.09
124
- version:
124
+ rubyforge_project: momomoto
125
+ rubygems_version: 1.2.0
126
+ signing_key:
127
+ specification_version: 2
128
+ summary: Momomoto is an object relational mapper for PostgreSQL.
129
+ test_files: []
130
+
@@ -1,14 +0,0 @@
1
-
2
- module Momomoto
3
- module Information_schema
4
-
5
- # internal use only
6
- #
7
- # Represents the corresponding view from Information Schema and is
8
- # used for getting the column names of primary keys of a table.
9
- class Key_column_usage < Momomoto::Table
10
- schema_name( "information_schema" )
11
- end
12
- end
13
- end
14
-
@@ -1,16 +0,0 @@
1
-
2
- module Momomoto
3
-
4
- module Information_schema
5
-
6
- # internal use only
7
- #
8
- # Represents the corresponding view from Information Schema and is
9
- # used for getting all defined functions within a database.
10
- class Routines < Momomoto::Table
11
- schema_name( "information_schema" )
12
- end
13
-
14
- end
15
-
16
- end
@@ -1,14 +0,0 @@
1
-
2
- module Momomoto
3
- module Information_schema
4
-
5
- # internal use only
6
- #
7
- # Represents the corresponding view from Information Schema and is
8
- # used when fetching primary keys.
9
- class Table_constraints < Momomoto::Table
10
- primary_keys( [] )
11
- schema_name( "information_schema" )
12
- end
13
- end
14
- end