datamapper 0.2.5 → 0.3.0

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