datamapper 0.2.3 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. data/example.rb +5 -5
  2. data/lib/data_mapper/adapters/abstract_adapter.rb +2 -2
  3. data/lib/data_mapper/adapters/data_object_adapter.rb +141 -147
  4. data/lib/data_mapper/adapters/mysql_adapter.rb +14 -1
  5. data/lib/data_mapper/adapters/postgresql_adapter.rb +123 -18
  6. data/lib/data_mapper/adapters/sql/coersion.rb +21 -9
  7. data/lib/data_mapper/adapters/sql/commands/load_command.rb +36 -19
  8. data/lib/data_mapper/adapters/sql/mappings/column.rb +111 -17
  9. data/lib/data_mapper/adapters/sql/mappings/schema.rb +27 -0
  10. data/lib/data_mapper/adapters/sql/mappings/table.rb +256 -29
  11. data/lib/data_mapper/adapters/sqlite3_adapter.rb +93 -8
  12. data/lib/data_mapper/associations/belongs_to_association.rb +53 -54
  13. data/lib/data_mapper/associations/has_and_belongs_to_many_association.rb +157 -25
  14. data/lib/data_mapper/associations/has_many_association.rb +45 -15
  15. data/lib/data_mapper/associations/has_n_association.rb +79 -20
  16. data/lib/data_mapper/associations/has_one_association.rb +2 -2
  17. data/lib/data_mapper/associations/reference.rb +1 -1
  18. data/lib/data_mapper/auto_migrations.rb +40 -0
  19. data/lib/data_mapper/base.rb +201 -98
  20. data/lib/data_mapper/context.rb +16 -10
  21. data/lib/data_mapper/database.rb +22 -11
  22. data/lib/data_mapper/dependency_queue.rb +28 -0
  23. data/lib/data_mapper/embedded_value.rb +61 -17
  24. data/lib/data_mapper/property.rb +4 -0
  25. data/lib/data_mapper/support/active_record_impersonation.rb +13 -5
  26. data/lib/data_mapper/support/errors.rb +5 -0
  27. data/lib/data_mapper/support/serialization.rb +8 -4
  28. data/lib/data_mapper/validatable_extensions/errors.rb +12 -0
  29. data/lib/data_mapper/validatable_extensions/macros.rb +7 -0
  30. data/lib/data_mapper/validatable_extensions/validatable_instance_methods.rb +62 -0
  31. data/lib/data_mapper/validatable_extensions/validation_base.rb +18 -0
  32. data/lib/data_mapper/validatable_extensions/validations/formats/email.rb +43 -0
  33. data/lib/data_mapper/validatable_extensions/validations/validates_acceptance_of.rb +7 -0
  34. data/lib/data_mapper/validatable_extensions/validations/validates_confirmation_of.rb +7 -0
  35. data/lib/data_mapper/validatable_extensions/validations/validates_each.rb +7 -0
  36. data/lib/data_mapper/validatable_extensions/validations/validates_format_of.rb +28 -0
  37. data/lib/data_mapper/validatable_extensions/validations/validates_length_of.rb +15 -0
  38. data/lib/data_mapper/validatable_extensions/validations/validates_numericality_of.rb +7 -0
  39. data/lib/data_mapper/validatable_extensions/validations/validates_presence_of.rb +7 -0
  40. data/lib/data_mapper/validatable_extensions/validations/validates_true_for.rb +7 -0
  41. data/lib/data_mapper/validatable_extensions/validations/validates_uniqueness_of.rb +33 -0
  42. data/lib/data_mapper/validations.rb +20 -0
  43. data/lib/data_mapper.rb +39 -34
  44. data/performance.rb +24 -18
  45. data/plugins/dataobjects/do_rb +0 -0
  46. data/rakefile.rb +12 -2
  47. data/spec/active_record_impersonation_spec.rb +133 -0
  48. data/spec/acts_as_tree_spec.rb +25 -9
  49. data/spec/associations_spec.rb +124 -4
  50. data/spec/attributes_spec.rb +13 -0
  51. data/spec/auto_migrations_spec.rb +44 -0
  52. data/spec/base_spec.rb +189 -1
  53. data/spec/column_spec.rb +85 -7
  54. data/spec/conditions_spec.rb +2 -2
  55. data/spec/dependency_spec.rb +25 -0
  56. data/spec/embedded_value_spec.rb +123 -3
  57. data/spec/fixtures/animals.yaml +1 -0
  58. data/spec/fixtures/careers.yaml +5 -0
  59. data/spec/fixtures/comments.yaml +1 -0
  60. data/spec/fixtures/people.yaml +14 -9
  61. data/spec/fixtures/projects.yaml +4 -0
  62. data/spec/fixtures/sections.yaml +5 -0
  63. data/spec/fixtures/serializers.yaml +6 -0
  64. data/spec/fixtures/users.yaml +1 -0
  65. data/spec/load_command_spec.rb +5 -4
  66. data/spec/mock_adapter.rb +2 -2
  67. data/spec/models/animal.rb +2 -1
  68. data/spec/models/animals_exhibit.rb +2 -2
  69. data/spec/models/career.rb +6 -0
  70. data/spec/models/comment.rb +4 -0
  71. data/spec/models/exhibit.rb +4 -0
  72. data/spec/models/person.rb +3 -13
  73. data/spec/models/project.rb +1 -1
  74. data/spec/models/serializer.rb +3 -0
  75. data/spec/models/user.rb +4 -0
  76. data/spec/models/zoo.rb +8 -1
  77. data/spec/natural_key_spec.rb +36 -0
  78. data/spec/paranoia_spec.rb +36 -0
  79. data/spec/property_spec.rb +70 -0
  80. data/spec/schema_spec.rb +10 -2
  81. data/spec/serialization_spec.rb +6 -3
  82. data/spec/serialize_spec.rb +19 -0
  83. data/spec/single_table_inheritance_spec.rb +7 -1
  84. data/spec/spec_helper.rb +26 -8
  85. data/spec/table_spec.rb +33 -0
  86. data/spec/validates_confirmation_of_spec.rb +20 -4
  87. data/spec/validates_format_of_spec.rb +22 -8
  88. data/spec/validates_length_of_spec.rb +26 -13
  89. data/spec/validates_uniqueness_of_spec.rb +18 -5
  90. data/spec/validations_spec.rb +55 -10
  91. data/tasks/fixtures.rb +13 -7
  92. metadata +189 -153
  93. data/lib/data_mapper/validations/confirmation_validator.rb +0 -53
  94. data/lib/data_mapper/validations/contextual_validations.rb +0 -50
  95. data/lib/data_mapper/validations/format_validator.rb +0 -85
  96. data/lib/data_mapper/validations/formats/email.rb +0 -78
  97. data/lib/data_mapper/validations/generic_validator.rb +0 -22
  98. data/lib/data_mapper/validations/length_validator.rb +0 -76
  99. data/lib/data_mapper/validations/required_field_validator.rb +0 -41
  100. data/lib/data_mapper/validations/unique_validator.rb +0 -56
  101. data/lib/data_mapper/validations/validation_errors.rb +0 -37
  102. data/lib/data_mapper/validations/validation_helper.rb +0 -77
  103. data/plugins/dataobjects/REVISION +0 -1
  104. data/plugins/dataobjects/Rakefile +0 -9
  105. data/plugins/dataobjects/do.rb +0 -348
  106. data/plugins/dataobjects/do_mysql.rb +0 -212
  107. data/plugins/dataobjects/do_postgres.rb +0 -196
  108. data/plugins/dataobjects/do_sqlite3.rb +0 -157
  109. data/plugins/dataobjects/spec/do_spec.rb +0 -150
  110. data/plugins/dataobjects/spec/spec_helper.rb +0 -81
  111. data/plugins/dataobjects/swig_mysql/extconf.rb +0 -45
  112. data/plugins/dataobjects/swig_mysql/mysql_c.c +0 -16602
  113. data/plugins/dataobjects/swig_mysql/mysql_c.i +0 -67
  114. data/plugins/dataobjects/swig_mysql/mysql_supp.i +0 -46
  115. data/plugins/dataobjects/swig_postgres/extconf.rb +0 -29
  116. data/plugins/dataobjects/swig_postgres/postgres_c.c +0 -8185
  117. data/plugins/dataobjects/swig_postgres/postgres_c.i +0 -73
  118. data/plugins/dataobjects/swig_sqlite/extconf.rb +0 -9
  119. data/plugins/dataobjects/swig_sqlite/sqlite3_c.c +0 -4725
  120. data/plugins/dataobjects/swig_sqlite/sqlite_c.i +0 -168
  121. data/tasks/drivers.rb +0 -20
@@ -17,17 +17,28 @@ module DataMapper
17
17
  @klass_or_name = klass_or_name
18
18
 
19
19
  @adapter = adapter
20
+
21
+ @temporary = false
20
22
  @columns = SortedSet.new
21
23
  @columns_hash = Hash.new { |h,k| h[k] = columns.find { |c| c.name == k } }
22
24
 
23
25
  @associations = AssociationsSet.new
24
26
 
25
27
  @multi_class = false
28
+ @paranoid = false
29
+ @paranoid_column = nil
26
30
 
27
31
  if @klass && @klass.ancestors.include?(DataMapper::Base) && @klass.superclass != DataMapper::Base
28
- @adapter.table(@klass.superclass).columns.each do |column|
32
+
33
+ super_table = @adapter.table(@klass.superclass)
34
+
35
+ super_table.columns.each do |column|
29
36
  self.add_column(column.name, column.type, column.options)
30
37
  end
38
+
39
+ super_table.associations.each do |association|
40
+ @associations << association
41
+ end
31
42
  end
32
43
  end
33
44
 
@@ -35,10 +46,30 @@ module DataMapper
35
46
  @schema || @schema = @adapter.schema
36
47
  end
37
48
 
49
+ def paranoid?
50
+ @paranoid
51
+ end
52
+
53
+ def paranoid_column
54
+ @paranoid_column
55
+ end
56
+
38
57
  def multi_class?
39
58
  @multi_class
40
59
  end
41
60
 
61
+ def type_column
62
+ @type_column
63
+ end
64
+
65
+ def temporary?
66
+ @temporary
67
+ end
68
+
69
+ def temporary=(value)
70
+ @temporary = value
71
+ end
72
+
42
73
  def associations
43
74
  @associations
44
75
  end
@@ -49,35 +80,124 @@ module DataMapper
49
80
 
50
81
  def columns
51
82
  key if @key.nil?
52
- @columns
83
+ class << self
84
+ attr_reader :columns
85
+ end
86
+
87
+ self.columns
53
88
  end
54
89
 
55
90
  def exists?
56
- @adapter.table_exists?(name)
91
+ @adapter.connection do |db|
92
+ command = db.create_command(to_exists_sql)
93
+ command.execute_reader(name, schema.name) do |reader|
94
+ reader.has_rows?
95
+ end
96
+ end
57
97
  end
58
98
 
59
99
  def drop!
60
- @adapter.drop(database, self)
100
+ if exists?
101
+ @adapter.connection do |db|
102
+ result = db.create_command(to_drop_sql).execute_non_query
103
+ database.identity_map.clear!(name)
104
+ schema.delete(self)
105
+ true
106
+ end
107
+ else
108
+ false
109
+ end
61
110
  end
62
111
 
63
112
  def create!(force = false)
64
- unless exists? || force
65
- drop! if force
66
- @adapter.create_table(self)
113
+ if exists?
114
+ if force
115
+ drop!
116
+ create!
117
+ else
118
+ false
119
+ end
120
+ else
121
+ @adapter.connection do |db|
122
+ db.create_command(to_create_sql).execute_non_query
123
+ schema << self
124
+ true
125
+ end
67
126
  end
68
127
  end
69
-
70
- def key
128
+
129
+ def delete_all!
130
+ @adapter.connection do |db|
131
+ db.create_command("DELETE FROM #{to_sql}").execute_non_query
132
+ end
133
+ database.identity_map.clear!(name)
134
+ end
135
+
136
+ def truncate!
137
+ @adapter.connection do |db|
138
+ result = db.create_command("TRUNCATE TABLE #{to_sql}").execute_non_query
139
+ database.identity_map.clear!(name)
140
+ result.to_i > 0
141
+ end
142
+ end
143
+
144
+ def count(*args)
145
+ @adapter.connection do |db|
146
+ sql = "SELECT COUNT(*) AS row_count FROM #{to_sql}"
147
+ sql << "WHERE #{args}" unless args.empty?
148
+
149
+ command = db.create_command(sql)
150
+ command.execute_reader do |reader|
151
+ if reader.has_rows?
152
+ reader.current_row.first.to_i
153
+ else
154
+ 0
155
+ end
156
+ end
157
+ end
158
+ end
159
+
160
+ def insert(hash)
161
+ @adapter.connection do |db|
162
+
163
+ columns_to_insert = []
164
+ values = []
165
+
166
+ hash.each_pair do |k,v|
167
+ column = self[k.to_sym]
168
+ columns_to_insert << (column ? column.to_sql : k)
169
+ values << v
170
+ end
171
+
172
+ command = db.create_command("INSERT INTO #{to_sql} (#{columns_to_insert.join(', ')}) VALUES (#{values.map { '?' }.join(', ')})")
173
+ command.execute_non_query(*values)
174
+ end
175
+ end
176
+
177
+ def key
71
178
  @key || begin
72
179
  @key = @columns.find { |column| column.key? }
73
180
 
74
181
  if @key.nil?
75
- @key = add_column(:id, :integer, :key => true, :ordinal => -1)
76
- @klass.send(:attr_reader, :id) unless @klass.methods.include?(:id)
182
+ @key = add_column(:id, :integer, :serial => true, :ordinal => -1)
183
+ @klass.send(:attr_reader, :id) unless @klass.nil? || @klass.methods.include?(:id)
77
184
  end
78
185
 
79
186
  @key
80
187
  end
188
+
189
+ class << self
190
+ attr_accessor :key
191
+ end
192
+ Base::dependencies.resolve!
193
+
194
+ self.key
195
+ end
196
+
197
+ def keys
198
+ @keys || begin
199
+ @keys = @columns.select { |column| column.key? }
200
+ end
81
201
  end
82
202
 
83
203
  def add_column(column_name, type, options)
@@ -91,8 +211,19 @@ module DataMapper
91
211
  column = @adapter.class::Mappings::Column.new(@adapter, self, column_name, type, column_ordinal, options)
92
212
  @columns << column
93
213
 
94
- @multi_class = true if column_name == :type
95
-
214
+ if column_name == :type
215
+ @multi_class = true
216
+ @type_column = column
217
+ end
218
+
219
+ if column_name.to_s =~ /^deleted\_(at|on)$/
220
+ @paranoid = true
221
+ @paranoid_column = column
222
+ end
223
+
224
+ self.flush_sql_caches!
225
+ @columns_hash.clear
226
+
96
227
  return column
97
228
  end
98
229
 
@@ -101,36 +232,77 @@ module DataMapper
101
232
  end
102
233
 
103
234
  def name
104
- @name || @name = if @klass_or_name.kind_of?(String)
105
- @klass_or_name
106
- elsif @klass_or_name.kind_of?(Class)
107
- if @klass_or_name.superclass != DataMapper::Base \
108
- && @klass_or_name.ancestors.include?(DataMapper::Base)
109
- @adapter.table(@klass_or_name.superclass).name
235
+ @name || @name = begin
236
+ if @custom_name
237
+ @custom_name
238
+ elsif @klass_or_name.kind_of?(String)
239
+ @klass_or_name
240
+ elsif @klass_or_name.kind_of?(Class)
241
+ if @klass_or_name.superclass != DataMapper::Base \
242
+ && @klass_or_name.ancestors.include?(DataMapper::Base)
243
+ @adapter.table(@klass_or_name.superclass).name
244
+ else
245
+ Inflector.tableize(@klass_or_name.name)
246
+ end
110
247
  else
111
- Inflector.tableize(@klass_or_name.name)
248
+ raise "+klass_or_name+ (#{@klass_or_name.inspect}) must be a Class or a string containing the name of a table"
112
249
  end
113
- else
114
- raise "+klass_or_name+ (#{@klass_or_name.inspect}) must be a Class or a string containing the name of a table"
115
250
  end.freeze
116
251
  end
117
252
 
118
253
  def name=(value)
119
- @name = value
254
+ flush_sql_caches!
255
+ @custom_name = value
256
+ self.name
120
257
  end
121
258
 
122
259
  def default_foreign_key
123
- @default_foreign_key || (@default_foreign_key = "#{Inflector.underscore(Inflector.singularize(name))}_id".freeze)
260
+ @default_foreign_key || (@default_foreign_key = "#{Inflector.underscore(Inflector.singularize(name))}_#{key.name}".freeze)
124
261
  end
125
262
 
126
263
  def to_sql
127
264
  @to_sql || @to_sql = quote_table.freeze
128
265
  end
129
266
 
130
- def to_create_table_sql
131
- @to_create_table_sql || @to_create_table_sql = begin
132
- "CREATE TABLE #{to_sql} (#{columns.map { |c| c.to_long_form }.join(', ')})"
267
+ def to_s
268
+ name.to_s
269
+ end
270
+
271
+ def unquote_default(default)
272
+ default
273
+ end
274
+
275
+ def get_database_columns
276
+ columns = []
277
+ @adapter.connection do |db|
278
+ command = db.create_command(to_columns_sql)
279
+ command.execute_reader(name, schema.name) do |reader|
280
+ columns = reader.map {
281
+ @adapter.class::Mappings::Column.new(@adapter, self, reader.item(1),
282
+ @adapter.class::TYPES.index(reader.item(2)),reader.item(0).to_i,
283
+ :nullable => reader.item(3).to_i != 99, :default => unquote_default(reader.item(4)))
284
+ }
285
+ end
133
286
  end
287
+ columns
288
+ end
289
+ alias_method :database_columns, :get_database_columns
290
+
291
+ def to_create_sql
292
+ @to_create_sql || @to_create_sql = begin
293
+ sql = "CREATE"
294
+ sql << " TEMPORARY" if temporary?
295
+ sql << " TABLE #{to_sql} (#{ columns.map { |c| c.to_long_form }.join(",\n") }"
296
+ unless keys.blank? || (keys.size == 1 && keys.first.serial?)
297
+ sql << ", PRIMARY KEY (#{keys.map { |c| c.to_sql }.join(', ') })"
298
+ end
299
+ sql << ")"
300
+ sql.compress_lines
301
+ end
302
+ end
303
+
304
+ def to_drop_sql
305
+ @to_drop_sql || @to_drop_sql = "DROP TABLE #{to_sql}"
134
306
  end
135
307
 
136
308
  def to_exists_sql
@@ -138,7 +310,28 @@ module DataMapper
138
310
  SELECT TABLE_NAME
139
311
  FROM INFORMATION_SCHEMA.TABLES
140
312
  WHERE TABLE_NAME = ?
141
- AND TABLE_SCHEMA = ?
313
+ AND #{@adapter.database_column_name} = ?
314
+ EOS
315
+ end
316
+
317
+ def to_column_exists_sql
318
+ @to_column_exists_sql || @to_column_exists_sql = <<-EOS.compress_lines
319
+ SELECT TABLE_NAME, COLUMN_NAME
320
+ FROM INFORMATION_SCHEMA.COLUMNS
321
+ WHERE TABLE_NAME = ?
322
+ AND COLUMN_NAME = ?
323
+ AND #{@adapter.database_column_name} = ?
324
+ EOS
325
+ end
326
+
327
+ def to_columns_sql
328
+ @to_column_exists_sql || @to_column_exists_sql = <<-EOS.compress_lines
329
+ SELECT ORDINAL_POSITION, COLUMN_NAME, DATA_TYPE,
330
+ (CASE IS_NULLABLE WHEN 'NO' THEN 99 ELSE 0 END),
331
+ COLUMN_DEFAULT
332
+ FROM INFORMATION_SCHEMA.COLUMNS
333
+ WHERE TABLE_NAME = ?
334
+ AND #{@adapter.database_column_name} = ?
142
335
  EOS
143
336
  end
144
337
 
@@ -155,7 +348,41 @@ module DataMapper
155
348
  @columns.inspect
156
349
  ]
157
350
  end
158
-
351
+
352
+ def flush_sql_caches!(flush_columns = true)
353
+ @to_column_exists_sql = nil
354
+ @to_column_exists_sql = nil
355
+ @to_exists_sql = nil
356
+ @to_create_sql = nil
357
+ @to_drop_sql = nil
358
+ @to_sql = nil
359
+ @name = nil
360
+
361
+ if flush_columns
362
+ @columns.each do |column|
363
+ column.send(:flush_sql_caches!)
364
+ end
365
+ end
366
+
367
+ true
368
+ end
369
+
370
+ def activate!
371
+ @activated = true
372
+ @associations.each do |association|
373
+ association.activate!
374
+ end
375
+ end
376
+
377
+ def activated?
378
+ @activated
379
+ end
380
+
381
+ end
382
+
383
+ class Schema
384
+ def to_tables_sql
385
+ end
159
386
  end
160
387
 
161
388
  end
@@ -4,7 +4,7 @@ begin
4
4
  rescue LoadError
5
5
  STDERR.puts <<-EOS
6
6
  You must install the DataObjects::SQLite3 driver.
7
- rake dm:install:sqlite3
7
+ gem install do_sqlite3
8
8
  EOS
9
9
  exit
10
10
  end
@@ -18,7 +18,8 @@ module DataMapper
18
18
  :integer => 'INTEGER'.freeze,
19
19
  :string => 'TEXT'.freeze,
20
20
  :text => 'TEXT'.freeze,
21
- :class => 'TEXT'.freeze
21
+ :class => 'TEXT'.freeze,
22
+ :boolean => 'INTEGER'.freeze
22
23
  })
23
24
 
24
25
  TABLE_QUOTING_CHARACTER = '"'.freeze
@@ -31,22 +32,45 @@ module DataMapper
31
32
  return conn
32
33
  end
33
34
 
34
- def truncate(session, name)
35
- result = execute("DELETE FROM #{table(name).to_sql}")
36
- session.identity_map.clear!(name)
37
- result.to_i > 0
35
+ def batch_insertable?
36
+ false
38
37
  end
39
-
38
+
40
39
  module Mappings
40
+
41
+ class Schema
42
+ def to_tables_sql
43
+ @to_tables_sql || @to_tables_sql = <<-EOS.compress_lines
44
+ SELECT "name"
45
+ FROM sqlite_master
46
+ where "type"= "table"
47
+ and "name" <> "sqlite_sequence"
48
+ EOS
49
+ end
50
+ alias_method :database_tables, :get_database_tables
51
+ end # class Schema
52
+
41
53
  class Table
42
54
  def to_exists_sql
43
55
  @to_exists_sql || @to_exists_sql = <<-EOS.compress_lines
44
56
  SELECT "name"
45
- FROM "sqlite_master"
57
+ FROM "#{temporary? ? 'sqlite_temp_master' : 'sqlite_master'}"
46
58
  WHERE "type" = "table"
47
59
  AND "name" = ?
48
60
  EOS
49
61
  end
62
+
63
+ def to_column_exists_sql
64
+ @to_column_exists_sql || @to_column_exists_sql = <<-EOS.compress_lines
65
+ PRAGMA TABLE_INFO(?)
66
+ EOS
67
+ end
68
+ alias_method :to_columns_sql, :to_column_exists_sql
69
+
70
+ def unquote_default(default)
71
+ default.gsub(/(^'|'$)/, "") rescue default
72
+ end
73
+
50
74
  end # class Table
51
75
 
52
76
  class Column
@@ -57,6 +81,67 @@ module DataMapper
57
81
  def size
58
82
  nil
59
83
  end
84
+
85
+ def alter!
86
+ @adapter.connection do |db|
87
+ flush_sql_caches!
88
+ backup_table = @adapter.table("#{@table.name}_backup")
89
+
90
+ @table.columns.each do |column|
91
+ backup_table.add_column(column.name, column.type, column.options)
92
+ end
93
+
94
+ backup_table.temporary = true
95
+
96
+ backup_table.create!
97
+
98
+ sql = <<-EOS.compress_lines
99
+ INSERT INTO #{backup_table.to_sql} SELECT #{@table.columns.map { |c| c.to_sql }.join(', ')} FROM #{@table.to_sql};
100
+ DROP TABLE #{@table.to_sql};
101
+ #{@table.to_create_sql};
102
+ INSERT INTO #{@table.to_sql} SELECT #{backup_table.columns.map { |c| c.to_sql }.join(', ')} FROM #{backup_table.to_sql};
103
+ EOS
104
+
105
+ sql.split(';').each do |part|
106
+ db.create_command(part).execute_non_query
107
+ end
108
+
109
+ backup_table.drop!
110
+ flush_sql_caches!
111
+ end
112
+ end
113
+
114
+ def drop!
115
+ @adapter.connection do |db|
116
+ @table.columns.delete(self)
117
+ flush_sql_caches!
118
+
119
+ backup_table = @adapter.table("#{@table.name}_backup")
120
+
121
+ @table.columns.each do |column|
122
+ backup_table.add_column(column.name, column.type, column.options)
123
+ end
124
+
125
+ backup_table.temporary = true
126
+
127
+ backup_table.create!
128
+
129
+ sql = <<-EOS.compress_lines
130
+ INSERT INTO #{backup_table.to_sql} SELECT #{@table.columns.map { |c| c.to_sql }.join(', ')} FROM #{@table.to_sql};
131
+ DROP TABLE #{@table.to_sql};
132
+ #{@table.to_create_sql};
133
+ INSERT INTO #{@table.to_sql} SELECT #{backup_table.columns.map { |c| c.to_sql }.join(', ')} FROM #{backup_table.to_sql};
134
+ EOS
135
+
136
+ sql.split(';').each do |part|
137
+ db.create_command(part).execute_non_query
138
+ end
139
+
140
+ backup_table.drop!
141
+ flush_sql_caches!
142
+ end
143
+ end
144
+
60
145
  end # class Column
61
146
  end # module Mappings
62
147
 
@@ -5,9 +5,7 @@ module DataMapper
5
5
 
6
6
  class BelongsToAssociation < HasNAssociation
7
7
 
8
- def define_accessor(klass)
9
- klass.property((@options[:foreign_key] || "#{name}_id").to_sym, :integer)
10
-
8
+ def define_accessor(klass)
11
9
  klass.class_eval <<-EOS
12
10
 
13
11
  def create_#{@association_name}(options = {})
@@ -33,14 +31,47 @@ module DataMapper
33
31
  EOS
34
32
  end
35
33
 
36
- def foreign_key
37
- @foreign_key || @foreign_key = begin
38
- table[@options[:foreign_key] || "#{name}_id".to_sym]
39
- end
34
+ # Reverse the natural order for BelongsToAssociations
35
+ alias constant associated_constant
36
+ def associated_constant
37
+ @constant
40
38
  end
41
-
39
+
40
+ def foreign_key_name
41
+ @foreign_key_name || @foreign_key_name = (@options[:foreign_key] || "#{name}_#{key_table.key.name}".to_sym)
42
+ end
43
+
44
+ def to_sql
45
+ "JOIN #{key_table.to_sql} ON #{foreign_key_column.to_sql(true)} = #{primary_key_column.to_sql(true)}"
46
+ end
47
+
42
48
  class Instance < Associations::Reference
43
-
49
+
50
+ def dirty?
51
+ @associated && @new_member
52
+ end
53
+
54
+ def validate_recursively(event, cleared)
55
+ @associated.nil? || cleared.include?(@associated) || @associated.validate_recursively(event, cleared)
56
+ end
57
+
58
+ def save_without_validation(database_context)
59
+ @new_member = false
60
+ unless @associated.nil?
61
+ @instance.instance_variable_set(
62
+ association.foreign_key_column.instance_variable_name,
63
+ @associated.key
64
+ )
65
+ @instance.database_context.adapter.save_without_validation(database_context, @instance)
66
+ end
67
+ end
68
+
69
+ def reload!
70
+ @new_member = false
71
+ @associated = nil
72
+ instance
73
+ end
74
+
44
75
  def instance
45
76
  @associated || @associated = begin
46
77
  if @instance.loaded_set.nil?
@@ -48,11 +79,11 @@ module DataMapper
48
79
  else
49
80
 
50
81
  # Temp variable for the instance variable name.
51
- fk = association.foreign_key.to_sym
82
+ fk = association.foreign_key_column.to_sym
52
83
 
53
84
  set = @instance.loaded_set.group_by { |instance| instance.send(fk) }
54
85
 
55
- @instance.session.all(association.constant, association.association_table.key.to_sym => set.keys).each do |assoc|
86
+ @instance.database_context.all(association.constant, association.associated_table.key.to_sym => set.keys).each do |assoc|
56
87
  set[assoc.key].each do |primary_instance|
57
88
  primary_instance.send(setter_method, assoc)
58
89
  end
@@ -64,11 +95,11 @@ module DataMapper
64
95
  end
65
96
 
66
97
  def create(options)
67
- @associated = association.constant.create(options)
98
+ @associated = association.associated_constant.create(options)
68
99
  end
69
100
 
70
101
  def build(options)
71
- @associated = association.constant.new(options)
102
+ @associated = association.associated_constant.new(options)
72
103
  end
73
104
 
74
105
  def setter_method
@@ -76,51 +107,19 @@ module DataMapper
76
107
  end
77
108
 
78
109
  def set(val)
79
- @instance.instance_variable_set(association.foreign_key.instance_variable_name, val.key)
110
+ raise "RecursionError" if val == @instance
111
+ @new_member = true
112
+ @instance.instance_variable_set(association.foreign_key_column.instance_variable_name, val.key)
80
113
  @associated = val
81
114
  end
115
+
116
+ def ensure_foreign_key!
117
+ if @associated
118
+ @instance.instance_variable_set(association.foreign_key.instance_variable_name, @associated.key)
119
+ end
120
+ end
82
121
 
83
122
  end # class Instance
84
-
85
-
86
- # def find
87
- # return @result unless @result.nil?
88
- #
89
- # unless @instance.loaded_set.nil?
90
- #
91
- # # Temp variable for the instance variable name.
92
- # setter_method = "#{@association_name}=".to_sym
93
- # instance_variable_name = "@#{foreign_key}".to_sym
94
- #
95
- # set = @instance.loaded_set.group_by { |instance| instance.instance_variable_get(instance_variable_name) }
96
- #
97
- # # Fetch the foreign objects for all instances in the current object's loaded-set.
98
- # @instance.session.all(constant, :id => set.keys).each do |owner|
99
- # set[owner.key].each do |instance|
100
- # instance.send(setter_method, owner)
101
- # end
102
- # end
103
- # end
104
- #
105
- # return @result
106
- # end
107
-
108
- # def create(options = {})
109
- # associated = constant.new(options)
110
- # if associated.save
111
- # @instance.send("#{constant.foreign_key}=", associated.id)
112
- # @result = associated
113
- # end
114
- # end
115
- #
116
- # def build(options = {})
117
- # @result = constant.new(options)
118
- # end
119
- #
120
- # def set(val)
121
- # @result = val
122
- # end
123
-
124
123
  end
125
124
 
126
125
  end