datamapper 0.2.3 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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