momomoto 0.1.19 → 0.2.0

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