datamapper 0.2.5 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. data/CHANGELOG +5 -1
  2. data/FAQ +96 -0
  3. data/QUICKLINKS +12 -0
  4. data/README +57 -155
  5. data/environment.rb +61 -43
  6. data/example.rb +30 -12
  7. data/lib/data_mapper.rb +6 -1
  8. data/lib/data_mapper/adapters/abstract_adapter.rb +0 -57
  9. data/lib/data_mapper/adapters/data_object_adapter.rb +203 -97
  10. data/lib/data_mapper/adapters/mysql_adapter.rb +4 -0
  11. data/lib/data_mapper/adapters/postgresql_adapter.rb +7 -1
  12. data/lib/data_mapper/adapters/sql/coersion.rb +3 -2
  13. data/lib/data_mapper/adapters/sql/commands/load_command.rb +29 -10
  14. data/lib/data_mapper/adapters/sql/mappings/associations_set.rb +4 -0
  15. data/lib/data_mapper/adapters/sql/mappings/column.rb +13 -9
  16. data/lib/data_mapper/adapters/sql/mappings/conditions.rb +172 -0
  17. data/lib/data_mapper/adapters/sql/mappings/table.rb +43 -17
  18. data/lib/data_mapper/adapters/sqlite3_adapter.rb +9 -2
  19. data/lib/data_mapper/associations.rb +75 -3
  20. data/lib/data_mapper/associations/belongs_to_association.rb +70 -36
  21. data/lib/data_mapper/associations/has_and_belongs_to_many_association.rb +195 -86
  22. data/lib/data_mapper/associations/has_many_association.rb +168 -61
  23. data/lib/data_mapper/associations/has_n_association.rb +23 -3
  24. data/lib/data_mapper/attributes.rb +73 -0
  25. data/lib/data_mapper/auto_migrations.rb +2 -6
  26. data/lib/data_mapper/base.rb +5 -9
  27. data/lib/data_mapper/database.rb +4 -3
  28. data/lib/data_mapper/embedded_value.rb +66 -30
  29. data/lib/data_mapper/identity_map.rb +1 -3
  30. data/lib/data_mapper/is/tree.rb +121 -0
  31. data/lib/data_mapper/migration.rb +155 -0
  32. data/lib/data_mapper/persistence.rb +532 -218
  33. data/lib/data_mapper/property.rb +306 -0
  34. data/lib/data_mapper/query.rb +164 -0
  35. data/lib/data_mapper/support/blank.rb +2 -2
  36. data/lib/data_mapper/support/connection_pool.rb +5 -6
  37. data/lib/data_mapper/support/enumerable.rb +3 -3
  38. data/lib/data_mapper/support/errors.rb +10 -1
  39. data/lib/data_mapper/support/inflector.rb +174 -238
  40. data/lib/data_mapper/support/object.rb +54 -0
  41. data/lib/data_mapper/support/serialization.rb +19 -1
  42. data/lib/data_mapper/support/string.rb +7 -16
  43. data/lib/data_mapper/support/symbol.rb +3 -15
  44. data/lib/data_mapper/support/typed_set.rb +68 -0
  45. data/lib/data_mapper/types/base.rb +44 -0
  46. data/lib/data_mapper/types/string.rb +34 -0
  47. data/lib/data_mapper/validations/number_validator.rb +40 -0
  48. data/lib/data_mapper/validations/string_validator.rb +20 -0
  49. data/lib/data_mapper/validations/validator.rb +13 -0
  50. data/performance.rb +26 -1
  51. data/profile_data_mapper.rb +1 -1
  52. data/rakefile.rb +42 -2
  53. data/spec/acts_as_tree_spec.rb +11 -3
  54. data/spec/adapters/data_object_adapter_spec.rb +31 -0
  55. data/spec/associations/belongs_to_association_spec.rb +98 -0
  56. data/spec/associations/has_and_belongs_to_many_association_spec.rb +377 -0
  57. data/spec/associations/has_many_association_spec.rb +337 -0
  58. data/spec/attributes_spec.rb +23 -1
  59. data/spec/auto_migrations_spec.rb +86 -29
  60. data/spec/callbacks_spec.rb +107 -0
  61. data/spec/column_spec.rb +5 -2
  62. data/spec/count_command_spec.rb +33 -1
  63. data/spec/database_spec.rb +18 -0
  64. data/spec/dependency_spec.rb +4 -2
  65. data/spec/embedded_value_spec.rb +8 -8
  66. data/spec/fixtures/people.yaml +1 -1
  67. data/spec/fixtures/projects.yaml +10 -1
  68. data/spec/fixtures/tasks.yaml +6 -0
  69. data/spec/fixtures/tasks_tasks.yaml +2 -0
  70. data/spec/fixtures/tomatoes.yaml +1 -0
  71. data/spec/is_a_tree_spec.rb +149 -0
  72. data/spec/load_command_spec.rb +71 -9
  73. data/spec/magic_columns_spec.rb +17 -2
  74. data/spec/migration_spec.rb +267 -0
  75. data/spec/models/animal.rb +1 -1
  76. data/spec/models/candidate.rb +8 -0
  77. data/spec/models/career.rb +1 -1
  78. data/spec/models/chain.rb +8 -0
  79. data/spec/models/comment.rb +1 -1
  80. data/spec/models/exhibit.rb +1 -1
  81. data/spec/models/fence.rb +7 -0
  82. data/spec/models/fruit.rb +2 -2
  83. data/spec/models/job.rb +8 -0
  84. data/spec/models/person.rb +2 -3
  85. data/spec/models/post.rb +1 -1
  86. data/spec/models/project.rb +21 -1
  87. data/spec/models/section.rb +1 -1
  88. data/spec/models/serializer.rb +1 -1
  89. data/spec/models/task.rb +9 -0
  90. data/spec/models/tomato.rb +27 -0
  91. data/spec/models/user.rb +8 -2
  92. data/spec/models/zoo.rb +2 -7
  93. data/spec/paranoia_spec.rb +1 -1
  94. data/spec/{base_spec.rb → persistence_spec.rb} +207 -18
  95. data/spec/postgres_spec.rb +48 -6
  96. data/spec/property_spec.rb +90 -9
  97. data/spec/query_spec.rb +71 -5
  98. data/spec/save_command_spec.rb +11 -0
  99. data/spec/spec_helper.rb +14 -11
  100. data/spec/support/blank_spec.rb +8 -0
  101. data/spec/support/inflector_spec.rb +41 -0
  102. data/spec/support/object_spec.rb +9 -0
  103. data/spec/{serialization_spec.rb → support/serialization_spec.rb} +1 -1
  104. data/spec/support/silence_spec.rb +15 -0
  105. data/spec/{support_spec.rb → support/string_spec.rb} +3 -3
  106. data/spec/support/struct_spec.rb +12 -0
  107. data/spec/support/typed_set_spec.rb +66 -0
  108. data/spec/table_spec.rb +3 -3
  109. data/spec/types/string.rb +81 -0
  110. data/spec/validates_uniqueness_of_spec.rb +17 -0
  111. data/spec/validations/number_validator.rb +59 -0
  112. data/spec/validations/string_validator.rb +14 -0
  113. metadata +59 -17
  114. data/do_performance.rb +0 -153
  115. data/lib/data_mapper/support/active_record_impersonation.rb +0 -103
  116. data/lib/data_mapper/support/weak_hash.rb +0 -46
  117. data/spec/active_record_impersonation_spec.rb +0 -129
  118. data/spec/associations_spec.rb +0 -232
  119. data/spec/conditions_spec.rb +0 -49
  120. data/spec/has_many_association_spec.rb +0 -173
  121. data/spec/models/animals_exhibit.rb +0 -8
data/example.rb CHANGED
@@ -1,27 +1,45 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- ENV['LOG_NAME'] = 'example'
3
+ ENV['LOG_NAME'] ||= 'example'
4
4
  require 'environment'
5
5
 
6
6
  # Define a fixtures helper method to load up our test data.
7
- def fixtures(name, force = false)
7
+ def fixtures(name)
8
8
  entry = YAML::load_file(File.dirname(__FILE__) + "/spec/fixtures/#{name}.yaml")
9
- klass = Kernel::const_get(Inflector.classify(Inflector.singularize(name)))
10
-
11
- klass.auto_migrate!
12
-
13
- (entry.kind_of?(Array) ? entry : [entry]).each do |hash|
14
- if hash['type']
15
- Object::const_get(hash['type'])::create(hash)
16
- else
17
- klass::create(hash)
9
+ klass = begin
10
+ Kernel::const_get(Inflector.classify(Inflector.singularize(name)))
11
+ rescue
12
+ nil
13
+ end
14
+
15
+ unless klass.nil?
16
+ database.logger.debug { "AUTOMIGRATE: #{klass}" }
17
+ klass.auto_migrate!
18
+
19
+ (entry.kind_of?(Array) ? entry : [entry]).each do |hash|
20
+ if hash['type']
21
+ Object::const_get(hash['type'])::create(hash)
22
+ else
23
+ klass::create(hash)
24
+ end
25
+ end
26
+ else
27
+ table = database.table(name.to_s)
28
+ table.create! true
29
+ table.activate_associations!
30
+
31
+ #pp database.schema
32
+
33
+ (entry.kind_of?(Array) ? entry : [entry]).each do |hash|
34
+ table.insert(hash)
18
35
  end
19
36
  end
20
37
  end
21
38
 
39
+
22
40
  # Pre-fill the database so non-destructive tests don't need to reload fixtures.
23
41
  Dir[File.dirname(__FILE__) + "/spec/fixtures/*.yaml"].each do |path|
24
- fixtures(File::basename(path).sub(/\.yaml$/, ''), true)
42
+ fixtures(File::basename(path).sub(/\.yaml$/, ''))
25
43
  end
26
44
 
27
45
  require 'irb'
@@ -16,11 +16,14 @@ unless defined?(DM_PLUGINS_ROOT)
16
16
  end
17
17
 
18
18
  # Require the basics...
19
+ require 'date'
20
+ require 'time'
19
21
  require 'rubygems'
20
22
  require 'yaml'
21
23
  require 'set'
22
24
  require 'fastthread'
23
25
  require 'validatable'
26
+ require 'data_mapper/support/object'
24
27
  require 'data_mapper/support/blank'
25
28
  require 'data_mapper/support/enumerable'
26
29
  require 'data_mapper/support/symbol'
@@ -28,9 +31,11 @@ require 'data_mapper/support/string'
28
31
  require 'data_mapper/support/silence'
29
32
  require 'data_mapper/support/inflector'
30
33
  require 'data_mapper/support/errors'
34
+ require 'data_mapper/support/typed_set'
31
35
  require 'data_mapper/database'
32
36
  require 'data_mapper/persistence'
33
37
  require 'data_mapper/base'
38
+ require 'data_mapper/types/string'
34
39
 
35
40
 
36
41
  begin
@@ -80,4 +85,4 @@ begin
80
85
  end
81
86
  rescue Exception
82
87
  warn "Could not connect to database specified by database.yml."
83
- end
88
+ end
@@ -37,63 +37,6 @@ module DataMapper
37
37
  @logger || @logger = @configuration.logger
38
38
  end
39
39
 
40
- protected
41
-
42
- def materialize(database_context, klass, values, reload, loaded_set)
43
-
44
- table = self.table(klass)
45
-
46
- instance_id = table.key.type_cast_value(values[table.key.name])
47
-
48
- instance_type = if table.multi_class? && table.type_column
49
- values[table.type_column.name].blank? ? klass :
50
- table.type_column.type_cast_value(values[table.type_column.name])
51
- else
52
- klass
53
- end
54
-
55
- instance = create_instance(database_context, instance_type, instance_id, reload)
56
-
57
- instance_type.callbacks.execute(:before_materialize, instance)
58
-
59
- type_casted_values = {}
60
-
61
- values.each_pair do |k,v|
62
- column = table[k]
63
- type_cast_value = column.type_cast_value(v)
64
- type_casted_values[k] = type_cast_value
65
- instance.instance_variable_set(column.instance_variable_name, type_cast_value)
66
- end
67
-
68
- instance.original_values = type_casted_values
69
- instance.loaded_set = loaded_set
70
-
71
- instance_type.callbacks.execute(:after_materialize, instance)
72
-
73
- return instance
74
-
75
- #rescue => e
76
- # raise MaterializationError.new("Failed to materialize row: #{values.inspect}\n#{e.to_yaml}")
77
- end
78
-
79
- def create_instance(database_context, instance_type, instance_id, reload)
80
- instance = database_context.identity_map.get(instance_type, instance_id)
81
-
82
- if instance.nil? || reload
83
- instance = instance_type.new() if instance.nil?
84
- instance.instance_variable_set(:@__key, instance_id)
85
- instance.instance_variable_set(:@new_record, false)
86
- database_context.identity_map.set(instance)
87
- elsif instance.new_record?
88
- instance.instance_variable_set(:@__key, instance_id)
89
- instance.instance_variable_set(:@new_record, false)
90
- end
91
-
92
- instance.database_context = database_context
93
-
94
- return instance
95
- end
96
-
97
40
  end # class AbstractAdapter
98
41
 
99
42
  end # module Adapters
@@ -4,12 +4,13 @@ require 'data_mapper/adapters/sql/coersion'
4
4
  require 'data_mapper/adapters/sql/quoting'
5
5
  require 'data_mapper/adapters/sql/mappings/schema'
6
6
  require 'data_mapper/support/connection_pool'
7
+ require 'data_mapper/query'
7
8
 
8
9
  module DataMapper
9
-
10
+
10
11
  # An Adapter is really a Factory for three types of object,
11
12
  # so they can be selectively sub-classed where needed.
12
- #
13
+ #
13
14
  # The first type is a Query. The Query is an object describing
14
15
  # the database-specific operations we wish to perform, in an
15
16
  # abstract manner. For example: While most if not all databases
@@ -29,7 +30,7 @@ module DataMapper
29
30
  # If the library being adapted does not provide such functionality,
30
31
  # DataMapper::Support::ConnectionPool can be used.
31
32
  module Adapters
32
-
33
+
33
34
  # You must inherit from the DoAdapter, and implement the
34
35
  # required methods to adapt a database library for use with the DataMapper.
35
36
  #
@@ -37,38 +38,42 @@ module DataMapper
37
38
  # standard sub-modules (Quoting, Coersion and Queries) in your own Adapter.
38
39
  # You can extend and overwrite these copies without affecting the originals.
39
40
  class DataObjectAdapter < AbstractAdapter
40
-
41
+
41
42
  $LOAD_PATH << (DM_PLUGINS_ROOT + '/dataobjects')
42
-
43
+
43
44
  FIND_OPTIONS = [
44
45
  :select, :offset, :limit, :class, :include, :shallow_include, :reload, :conditions, :order, :intercept_load
45
46
  ]
46
-
47
+
47
48
  TABLE_QUOTING_CHARACTER = '`'.freeze
48
49
  COLUMN_QUOTING_CHARACTER = '`'.freeze
49
-
50
+
51
+ SYNTAX = {
52
+ :now => 'NOW()'.freeze
53
+ }
54
+
50
55
  def initialize(configuration)
51
56
  super
52
57
  @connection_pool = Support::ConnectionPool.new { create_connection }
53
58
  end
54
-
59
+
55
60
  def activated?
56
61
  @activated
57
62
  end
58
-
63
+
59
64
  def activate!
60
65
  @activated = true
61
66
  schema.activate!
62
67
  end
63
-
68
+
64
69
  def create_connection
65
70
  raise NotImplementedError.new
66
71
  end
67
-
72
+
68
73
  def batch_insertable?
69
74
  true
70
75
  end
71
-
76
+
72
77
  # Yields an available connection. Flushes the connection-pool and reconnects
73
78
  # if the connection returns an error.
74
79
  def connection
@@ -78,48 +83,49 @@ module DataMapper
78
83
  rescue => execution_error
79
84
  # Log error on failure
80
85
  logger.error { execution_error }
81
-
86
+
82
87
  # Close all open connections, assuming that if one
83
88
  # had an error, it's likely due to a lost connection,
84
89
  # in which case all connections are likely broken.
85
90
  flush_connections!
86
-
91
+
87
92
  raise execution_error
88
93
  end
89
94
  end
90
-
95
+
91
96
  # Close any open connections.
92
- def flush_connections!
93
- begin
94
- @connection_pool.available_connections.each do |active_connection|
97
+ def flush_connections!
98
+ @connection_pool.available_connections.each do |active_connection|
99
+ begin
95
100
  active_connection.close
101
+ rescue => close_connection_error
102
+ # An error on closing the connection is almost expected
103
+ # if the socket is broken.
104
+ logger.warn { close_connection_error }
96
105
  end
97
- rescue => close_connection_error
98
- # An error on closing the connection is almost expected
99
- # if the socket is broken.
100
- logger.warn { close_connection_error }
101
106
  end
102
-
107
+
103
108
  # Reopen fresh connections.
109
+ @connection_pool.instance_variable_set('@created_count', 0)
104
110
  @connection_pool.available_connections.clear
105
111
  end
106
-
112
+
107
113
  def transaction(&block)
108
114
  raise NotImplementedError.new
109
115
  end
110
-
116
+
111
117
  def query(*args)
112
118
  db = create_connection
113
-
119
+
114
120
  command = db.create_command(args.shift)
115
-
121
+
116
122
  reader = command.execute_reader(*args)
117
123
  fields = reader.fields.map { |field| Inflector.underscore(field).to_sym }
118
124
  results = []
119
125
 
120
126
  if fields.size > 1
121
127
  struct = Struct.new(*fields)
122
-
128
+
123
129
  reader.each do
124
130
  results << struct.new(*reader.current_row)
125
131
  end
@@ -136,8 +142,8 @@ module DataMapper
136
142
  ensure
137
143
  reader.close if reader
138
144
  db.close
139
- end
140
-
145
+ end
146
+
141
147
  def execute(*args)
142
148
  db = create_connection
143
149
  command = db.create_command(args.shift)
@@ -148,15 +154,15 @@ module DataMapper
148
154
  ensure
149
155
  db.close
150
156
  end
151
-
157
+
152
158
  def handle_error(error)
153
159
  raise error
154
160
  end
155
-
161
+
156
162
  def schema
157
163
  @schema || ( @schema = self.class::Mappings::Schema.new(self, @configuration.database) )
158
164
  end
159
-
165
+
160
166
  def column_exists_for_table?(table_name, column_name)
161
167
  connection do |db|
162
168
  table = self.table(table_name)
@@ -166,15 +172,19 @@ module DataMapper
166
172
  end
167
173
  end
168
174
  end
169
-
175
+
170
176
  def delete(database_context, instance)
171
177
  table = self.table(instance)
172
-
178
+
173
179
  if instance.is_a?(Class)
174
180
  table.delete_all!
175
181
  else
176
182
  callback(instance, :before_destroy)
177
-
183
+
184
+ table.associations.each do |association|
185
+ instance.send(association.name).deactivate unless association.is_a?(::DataMapper::Associations::BelongsToAssociation)
186
+ end
187
+
178
188
  if table.paranoid?
179
189
  instance.instance_variable_set(table.paranoid_column.instance_variable_name, Time::now)
180
190
  instance.save
@@ -189,34 +199,37 @@ module DataMapper
189
199
  database_context.identity_map.delete(instance)
190
200
  callback(instance, :after_destroy)
191
201
  end
192
- end
202
+ end
193
203
  end
194
204
  end
195
-
196
- def save(database_context, instance, validate = true)
205
+
206
+ def save(database_context, instance, validate = true, cleared = Set.new)
197
207
  case instance
198
- when Class then table(instance).create!
208
+ when Class then
209
+ table(instance).create!
210
+ table(instance).activate_associations!
199
211
  when Mappings::Table then instance.create!
200
- when DataMapper::Persistence then
212
+ when DataMapper::Persistence then
201
213
  event = instance.new_record? ? :create : :update
202
-
203
- return false if validate && !instance.validate_recursively(event, Set.new)
204
-
214
+
215
+ return false if (validate && !instance.validate_recursively(event, Set.new)) || cleared.include?(instance)
216
+ cleared << instance
217
+
205
218
  callback(instance, :before_save)
206
-
207
- return false unless instance.new_record? || instance.dirty?
208
-
219
+
220
+ return true unless instance.new_record? || instance.dirty?
221
+
209
222
  result = send(event, database_context, instance)
210
-
223
+
211
224
  instance.database_context = database_context
212
225
  instance.attributes.each_pair do |name, value|
213
226
  instance.original_values[name] = value
214
227
  end
215
-
228
+
216
229
  instance.loaded_associations.each do |association|
217
- association.save_without_validation(database_context) if association.dirty?
230
+ association.save_without_validation(database_context, cleared) if association.dirty?
218
231
  end
219
-
232
+
220
233
  callback(instance, :after_save)
221
234
  result
222
235
  end
@@ -224,41 +237,56 @@ module DataMapper
224
237
  logger.error(error)
225
238
  raise error
226
239
  end
227
-
228
- def save_without_validation(database_context, instance)
229
- save(database_context, instance, false)
240
+
241
+ def save_without_validation(database_context, instance, cleared = Set.new)
242
+ save(database_context, instance, false, cleared)
230
243
  end
231
-
244
+
232
245
  def update(database_context, instance)
233
246
  callback(instance, :before_update)
234
-
247
+
248
+ instance = update_magic_properties(database_context, instance)
249
+
235
250
  table = self.table(instance)
236
251
  attributes = instance.dirty_attributes
237
252
  parameters = []
238
-
253
+
239
254
  unless attributes.empty?
240
255
  sql = "UPDATE " << table.to_sql << " SET "
241
-
256
+
242
257
  sql << attributes.map do |key, value|
243
258
  parameters << value
244
259
  "#{table[key].to_sql} = ?"
245
260
  end.join(', ')
246
-
261
+
247
262
  sql << " WHERE #{table.key.to_sql} = ?"
248
263
  parameters << instance.key
249
-
250
- connection do |db|
251
- db.create_command(sql).execute_non_query(*parameters).to_i > 0 \
252
- && callback(instance, :after_update)
264
+
265
+ result = connection do |db|
266
+ db.create_command(sql).execute_non_query(*parameters)
267
+ end
268
+
269
+ # BUG: do_mysql returns inaccurate affected row counts for UPDATE statements.
270
+ if true || result.to_i > 0
271
+ callback(instance, :after_update)
272
+ return true
273
+ else
274
+ return false
253
275
  end
254
276
  else
255
277
  true
256
278
  end
257
279
  end
258
-
280
+
281
+ def empty_insert_sql
282
+ "DEFAULT VALUES"
283
+ end
284
+
259
285
  def create(database_context, instance)
260
286
  callback(instance, :before_create)
261
287
 
288
+ instance = update_magic_properties(database_context, instance)
289
+
262
290
  table = self.table(instance)
263
291
  attributes = instance.dirty_attributes
264
292
 
@@ -268,10 +296,12 @@ module DataMapper
268
296
  attributes[:type] = instance.class.name
269
297
  )
270
298
  end
271
-
299
+
272
300
  keys = []
273
301
  values = []
274
302
  attributes.each_pair do |key, value|
303
+ raise ArgumentError.new("#{value.inspect} is not a valid value for #{key.inspect}") if value.is_a?(Array)
304
+
275
305
  keys << table[key].to_sql
276
306
  values << value
277
307
  end
@@ -279,37 +309,112 @@ module DataMapper
279
309
  sql = if keys.size > 0
280
310
  "INSERT INTO #{table.to_sql} (#{keys.join(', ')}) VALUES ?"
281
311
  else
282
- "INSERT INTO #{table.to_sql}"
312
+ "INSERT INTO #{table.to_sql} #{self.empty_insert_sql}"
313
+ end
314
+
315
+ result = connection do |db|
316
+ db.create_command(sql).execute_non_query(values)
317
+ end
318
+
319
+ if result.to_i > 0
320
+ instance.instance_variable_set(:@new_record, false)
321
+ instance.key = result.last_insert_row if table.key.serial? && !attributes.include?(table.key.name)
322
+ database_context.identity_map.set(instance)
323
+ callback(instance, :after_create)
324
+ return true
325
+ else
326
+ return false
283
327
  end
284
-
285
- insert_id = connection do |db|
286
- db.create_command(sql).execute_non_query(values).last_insert_row
328
+ end
329
+
330
+ MAGIC_PROPERTIES = {
331
+ :updated_at => lambda { self.updated_at = Time::now },
332
+ :updated_on => lambda { self.updated_on = Date::today },
333
+ :created_at => lambda { self.created_at ||= Time::now },
334
+ :created_on => lambda { self.created_on ||= Date::today }
335
+ }
336
+
337
+ def update_magic_properties(database_context, instance)
338
+ instance.class.properties.find_all { |property| MAGIC_PROPERTIES.has_key?(property.name) }.each do |property|
339
+ instance.instance_eval(&MAGIC_PROPERTIES[property.name])
287
340
  end
288
- instance.instance_variable_set(:@new_record, false)
289
- instance.key = insert_id if table.key.serial? && !attributes.include?(table.key.name)
290
- database_context.identity_map.set(instance)
291
- callback(instance, :after_create)
341
+ instance
292
342
  end
293
-
343
+
294
344
  def load(database_context, klass, options)
295
345
  self.class::Commands::LoadCommand.new(self, database_context, klass, options).call
296
346
  end
297
-
347
+
298
348
  def get(database_context, klass, keys)
299
349
  table = self.table(klass)
300
- sql = "SELECT #{table.columns.map { |column| column.to_sql }.join(', ')} FROM #{table.to_sql} WHERE #{table.keys.map { |key| "#{key.to_sql} = ?" }.join(' AND ')}"
301
-
350
+ instance_id = table.key.type_cast_value(keys.first)
351
+ instance = database_context.identity_map.get(klass, instance_id)
352
+
353
+ return instance if instance
354
+
355
+ column_indexes = {}
356
+ select_columns = []
357
+
358
+ table.columns.each_with_index do |column, i|
359
+ column_indexes[column] = i
360
+ select_columns << column.to_sql
361
+ end
362
+
363
+ sql = "SELECT #{select_columns.join(', ')} FROM #{table.to_sql} WHERE #{table.keys.map { |key| "#{key.to_sql} = ?" }.join(' AND ')}"
364
+
302
365
  connection do |db|
303
- db.create_command(sql).execute_reader(*keys) do |reader|
304
- values = {}
305
- table.columns.each_with_index do |column, i|
306
- values[column.name] = reader.item(i)
307
- end
308
- materialize(database_context, klass, values, false, [])
366
+ reader = nil
367
+ begin
368
+ reader = db.create_command(sql).execute_reader(*keys)
369
+
370
+ if reader.has_rows?
371
+
372
+ instance_type = klass
373
+
374
+ if table.multi_class? && table.type_column
375
+ value = reader.item(column_indexes[table.type_column])
376
+ instance_type = table.type_column.type_cast_value(value) unless value.blank?
377
+ end
378
+
379
+ if instance.nil?
380
+ instance = instance_type.allocate()
381
+ instance.instance_variable_set(:@__key, instance_id)
382
+ instance.instance_variable_set(:@new_record, false)
383
+ database_context.identity_map.set(instance)
384
+ elsif instance.new_record?
385
+ instance.instance_variable_set(:@__key, instance_id)
386
+ instance.instance_variable_set(:@new_record, false)
387
+ database_context.identity_map.set(instance)
388
+ end
389
+
390
+ instance.database_context = database_context
391
+
392
+ instance_type.callbacks.execute(:before_materialize, instance)
393
+
394
+ originals = instance.original_values
395
+
396
+ column_indexes.each_pair do |column, i|
397
+ value = column.type_cast_value(reader.item(i))
398
+ instance.instance_variable_set(column.instance_variable_name, value)
399
+
400
+ case value
401
+ when String, Date, Time then originals[column.name] = value.dup
402
+ else originals[column.name] = value
403
+ end
404
+ end
405
+
406
+ instance.loaded_set = [instance]
407
+
408
+ instance_type.callbacks.execute(:after_materialize, instance)
409
+ end # if reader.has_rows?
410
+ ensure
411
+ reader.close if reader && reader.open?
309
412
  end
310
- end
413
+ end # connection
414
+
415
+ return instance
311
416
  end
312
-
417
+
313
418
  def table(instance)
314
419
  case instance
315
420
  when DataMapper::Adapters::Sql::Mappings::Table then instance
@@ -318,39 +423,40 @@ module DataMapper
318
423
  else raise "Don't know how to map #{instance.inspect} to a table."
319
424
  end
320
425
  end
321
-
426
+
322
427
  def callback(instance, callback_name)
323
428
  instance.class.callbacks.execute(callback_name, instance)
324
429
  end
325
-
430
+
326
431
  # This callback copies and sub-classes modules and classes
327
432
  # in the DoAdapter to the inherited class so you don't
328
433
  # have to copy and paste large blocks of code from the
329
434
  # DoAdapter.
330
- #
435
+ #
331
436
  # Basically, when inheriting from the DoAdapter, you
332
437
  # aren't just inheriting a single class, you're inheriting
333
438
  # a whole graph of Types. For convenience.
334
439
  def self.inherited(base)
335
-
440
+
336
441
  commands = base.const_set('Commands', Module.new)
337
442
 
338
443
  Sql::Commands.constants.each do |name|
339
444
  commands.const_set(name, Class.new(Sql::Commands.const_get(name)))
340
445
  end
341
-
446
+
342
447
  mappings = base.const_set('Mappings', Module.new)
343
-
448
+
344
449
  Sql::Mappings.constants.each do |name|
345
450
  mappings.const_set(name, Class.new(Sql::Mappings.const_get(name)))
346
451
  end
347
-
452
+
348
453
  base.const_set('TYPES', TYPES.dup)
349
454
  base.const_set('FIND_OPTIONS', FIND_OPTIONS.dup)
350
-
455
+ base.const_set('SYNTAX', SYNTAX.dup)
456
+
351
457
  super
352
458
  end
353
-
459
+
354
460
  TYPES = {
355
461
  :integer => 'int'.freeze,
356
462
  :string => 'varchar'.freeze,
@@ -367,8 +473,8 @@ module DataMapper
367
473
  include Sql
368
474
  include Quoting
369
475
  include Coersion
370
-
476
+
371
477
  end # class DoAdapter
372
-
478
+
373
479
  end # module Adapters
374
- end # module DataMapper
480
+ end # module DataMapper