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
@@ -1,9 +1,12 @@
1
+ require 'data_mapper/property'
1
2
  require 'data_mapper/support/active_record_impersonation'
2
3
  require 'data_mapper/support/serialization'
3
- require 'data_mapper/validations/validation_helper'
4
+ require 'data_mapper/validations'
4
5
  require 'data_mapper/associations'
5
6
  require 'data_mapper/callbacks'
6
7
  require 'data_mapper/embedded_value'
8
+ require 'data_mapper/auto_migrations'
9
+ require 'data_mapper/dependency_queue'
7
10
 
8
11
  begin
9
12
  require 'ferret'
@@ -11,7 +14,7 @@ rescue LoadError
11
14
  end
12
15
 
13
16
  module DataMapper
14
-
17
+
15
18
  class Base
16
19
 
17
20
  # This probably needs to be protected
@@ -20,7 +23,7 @@ module DataMapper
20
23
  include CallbacksHelper
21
24
  include Support::ActiveRecordImpersonation
22
25
  include Support::Serialization
23
- include Validations::ValidationHelper
26
+ include Validations
24
27
  include Associations
25
28
 
26
29
  # Track classes that inherit from DataMapper::Base.
@@ -34,44 +37,52 @@ module DataMapper
34
37
  end
35
38
  end
36
39
 
40
+ def self.dependencies
41
+ @dependency_queue || (@dependency_queue = DependencyQueue.new)
42
+ end
43
+
37
44
  def self.inherited(klass)
45
+ klass.instance_variable_set('@properties', [])
46
+
47
+ klass.send :extend, AutoMigrations
38
48
  DataMapper::Base::subclasses << klass
39
- klass.send(:undef_method, :id)
49
+ klass.send(:undef_method, :id)
40
50
 
41
51
  # When this class is sub-classed, copy the declared columns.
42
52
  klass.class_eval do
43
-
44
- def self.auto_migrate!
45
- if self::subclasses.empty?
46
- database.schema[self].drop!
47
- database.save(self)
48
- else
49
- schema = database.schema
50
- columns = self::subclasses.inject(schema[self].columns) do |span, subclass|
51
- span + schema[subclass].columns
52
- end
53
-
54
- table_name = schema[self].name.to_s
55
- table = schema[table_name]
56
- columns.each do |column|
57
- table.add_column(column.name, column.type, column.options)
58
- end
59
-
60
- table.drop!
61
- table.create!
62
- end
63
- end
64
-
65
53
  def self.subclasses
66
54
  @subclasses || (@subclasses = [])
67
55
  end
68
56
 
69
57
  def self.inherited(subclass)
58
+
59
+ super_table = database.table(self)
60
+
61
+ if super_table.type_column.nil?
62
+ super_table.add_column(:type, :class, {})
63
+ end
64
+
70
65
  self::subclasses << subclass
71
66
  end
72
67
  end
73
68
  end
74
69
 
70
+ def self.logger
71
+ database.logger
72
+ end
73
+
74
+ def logger
75
+ self.class.logger
76
+ end
77
+
78
+ def self.transaction
79
+ yield
80
+ end
81
+
82
+ def self.properties
83
+ @properties
84
+ end
85
+
75
86
  # Allows you to override the table name for a model.
76
87
  # EXAMPLE:
77
88
  # class WorkItem
@@ -90,9 +101,15 @@ module DataMapper
90
101
  end
91
102
  end
92
103
 
104
+ PROPERTY_OPTIONS = [
105
+ :public, :protected, :private, :accessor, :reader, :writer,
106
+ :lazy, :default, :nullable, :key, :serial, :column
107
+ ]
108
+
93
109
  # Adds property accessors for a field that you'd like to be able to modify. The DataMapper doesn't
94
110
  # use the table schema to infer accessors, you must explicity call #property to add field accessors
95
111
  # to your model.
112
+ #
96
113
  # EXAMPLE:
97
114
  # class CellProvider
98
115
  # property :name, :string
@@ -105,10 +122,34 @@ module DataMapper
105
122
  #
106
123
  # => AT&T
107
124
  # => 3
125
+ #
126
+ # OPTIONS:
127
+ # * <tt>lazy</tt>: Lazy load the specified property (:lazy => true). False by default.
128
+ # * <tt>accessor</tt>: Set method visibility for the property accessors. Affects both
129
+ # reader and writer. Allowable values are :public, :protected, :private. Defaults to
130
+ # :public
131
+ # * <tt>reader</tt>: Like the accessor option but affects only the property reader.
132
+ # * <tt>writer</tt>: Like the accessor option but affects only the property writer.
133
+ # * <tt>protected</tt>: Alias for :reader => :public, :writer => :protected
134
+ # * <tt>private</tt>: Alias for :reader => :public, :writer => :private
108
135
  def self.property(name, type, options = {})
109
- mapping = database.schema[self].add_column(name, type, options)
110
- property_getter(name, mapping)
111
- property_setter(name, mapping)
136
+
137
+ options.each_pair do |k,v|
138
+ raise ArgumentError.new("#{k.inspect} is not a supported option in DataMapper::Base::PROPERTY_OPTIONS") unless PROPERTY_OPTIONS.include?(k)
139
+ end
140
+
141
+ visibility_options = [:public, :protected, :private]
142
+ reader_visibility = options[:reader] || options[:accessor] || :public
143
+ writer_visibility = options[:writer] || options[:accessor] || :public
144
+ writer_visibility = :protected if options[:protected]
145
+ writer_visibility = :private if options[:private]
146
+
147
+ raise(ArgumentError.new, "property visibility must be :public, :protected, or :private") unless visibility_options.include?(reader_visibility) && visibility_options.include?(writer_visibility)
148
+
149
+ mapping = database.schema[self].add_column(name.to_s.sub(/\?$/, '').to_sym, type, options)
150
+
151
+ property_getter(mapping, reader_visibility)
152
+ property_setter(mapping, writer_visibility)
112
153
 
113
154
  if MAGIC_PROPERTIES.has_key?(name)
114
155
  class_eval(&MAGIC_PROPERTIES[name])
@@ -124,35 +165,78 @@ module DataMapper
124
165
  :created_on => lambda { before_create { |x| x.created_on = Date::today } }
125
166
  }
126
167
 
127
- def self.embed(class_or_name, &block)
128
- EmbeddedValue::define(self, class_or_name, &block)
168
+ # An embedded value maps the values of an object to fields in the record of the object's owner.
169
+ # #embed takes a symbol to define the embedded class, options, and an optional block. See
170
+ # examples for use cases.
171
+ #
172
+ # EXAMPLE:
173
+ # class CellPhone < DataMapper::Base
174
+ # property :number, :string
175
+ #
176
+ # embed :owner, :prefix => true do
177
+ # property :name, :string
178
+ # property :address, :string
179
+ # end
180
+ # end
181
+ #
182
+ # my_phone = CellPhone.new
183
+ # my_phone.owner.name = "Nick"
184
+ # puts my_phone.owner.name
185
+ #
186
+ # => Nick
187
+ #
188
+ # OPTIONS:
189
+ # * <tt>prefix</tt>: define a column prefix, so instead of mapping :address to an 'address'
190
+ # column, it would map to 'owner_address' in the example above. If :prefix => true is
191
+ # specified, the prefix will be the name of the symbol given as the first parameter. If the
192
+ # prefix is a string the specified string will be used for the prefix.
193
+ # * <tt>lazy</tt>: lazy-load all embedded values at the same time. :lazy => true to enable.
194
+ # Disabled (false) by default.
195
+ # * <tt>accessor</tt>: Set method visibility for all embedded properties. Affects both
196
+ # reader and writer. Allowable values are :public, :protected, :private. Defaults to :public
197
+ # * <tt>reader</tt>: Like the accessor option but affects only embedded property readers.
198
+ # * <tt>writer</tt>: Like the accessor option but affects only embedded property writers.
199
+ # * <tt>protected</tt>: Alias for :reader => :public, :writer => :protected
200
+ # * <tt>private</tt>: Alias for :reader => :public, :writer => :private
201
+ #
202
+ def self.embed(name, options = {}, &block)
203
+ EmbeddedValue::define(self, name, options, &block)
129
204
  end
130
205
 
131
- def self.property_getter(name, mapping)
206
+ def self.property_getter(mapping, visibility = :public)
132
207
  if mapping.lazy?
133
208
  class_eval <<-EOS
134
- def #{name}
135
- lazy_load!(#{name.inspect})
136
- @#{name}
209
+ #{visibility.to_s}
210
+ def #{mapping.name}
211
+ lazy_load!(#{mapping.name.inspect})
212
+ class << self;
213
+ attr_accessor #{mapping.name.inspect}
214
+ end
215
+ @#{mapping.name}
137
216
  end
138
217
  EOS
139
218
  else
140
- class_eval("def #{name}; #{mapping.instance_variable_name} end")
219
+ class_eval("#{visibility.to_s}; def #{mapping.name}; #{mapping.instance_variable_name} end") unless [ :public, :private, :protected ].include?(mapping.name)
220
+ end
221
+
222
+ if mapping.type == :boolean
223
+ class_eval("#{visibility.to_s}; def #{mapping.name}?; #{mapping.instance_variable_name} end")
141
224
  end
142
225
  end
143
226
 
144
- def self.property_setter(name, mapping)
227
+ def self.property_setter(mapping, visibility = :public)
145
228
  if mapping.lazy?
146
229
  class_eval <<-EOS
147
- def #{name.to_s.sub(/\?$/, '')}=(value)
230
+ #{visibility.to_s}
231
+ def #{mapping.name}=(value)
148
232
  class << self;
149
- attr_accessor #{name.inspect}
233
+ attr_accessor #{mapping.name.inspect}
150
234
  end
151
- @#{name} = value
235
+ @#{mapping.name} = value
152
236
  end
153
237
  EOS
154
238
  else
155
- class_eval("def #{name.to_s.sub(/\?$/, '')}=(value); #{mapping.instance_variable_name} = value end")
239
+ class_eval("#{visibility.to_s}; def #{mapping.name}=(value); #{mapping.instance_variable_name} = value end")
156
240
  end
157
241
  end
158
242
 
@@ -160,19 +244,28 @@ module DataMapper
160
244
  # for the named methods so that the lazy_loading is skipped the second time.
161
245
  def lazy_load!(*names)
162
246
 
247
+ names = names.map { |name| name.to_sym }.reject { |name| lazy_loaded_attributes.include?(name) }
248
+
163
249
  reset_attribute = lambda do |instance|
164
250
  singleton_class = (class << instance; self end)
165
251
  names.each do |name|
252
+ instance.lazy_loaded_attributes << name
166
253
  singleton_class.send(:attr_accessor, name)
167
254
  end
168
255
  end
169
256
 
170
- unless new_record? || loaded_set.nil?
171
- session.all(
257
+ unless names.empty? || new_record? || loaded_set.nil?
258
+
259
+ key = database_context.table(self.class).key.to_sym
260
+ keys_to_select = loaded_set.map do |instance|
261
+ instance.send(key)
262
+ end
263
+
264
+ database_context.all(
172
265
  self.class,
173
- :select => ([:id] + names),
266
+ :select => ([key] + names),
174
267
  :reload => true,
175
- :id => loaded_set.map(&:id)
268
+ key => keys_to_select
176
269
  ).each(&reset_attribute)
177
270
  else
178
271
  reset_attribute[self]
@@ -183,7 +276,12 @@ module DataMapper
183
276
  def new_record?
184
277
  @new_record.nil? || @new_record
185
278
  end
279
+
280
+ def ==(other)
281
+ private_attributes == other.send("private_attributes")
282
+ end
186
283
 
284
+ # Returns the difference between two objects, in terms of their attributes.
187
285
  def ^(other)
188
286
  results = {}
189
287
 
@@ -199,10 +297,14 @@ module DataMapper
199
297
  results
200
298
  end
201
299
 
300
+ def lazy_loaded_attributes
301
+ @lazy_loaded_attributes || @lazy_loaded_attributes = Set.new
302
+ end
303
+
202
304
  def loaded_attributes
203
305
  pairs = {}
204
306
 
205
- session.table(self).columns.each do |column|
307
+ database_context.table(self).columns.each do |column|
206
308
  pairs[column.name] = instance_variable_get(column.instance_variable_name)
207
309
  end
208
310
 
@@ -217,10 +319,12 @@ module DataMapper
217
319
  def attributes
218
320
  pairs = {}
219
321
 
220
- session.table(self).columns.each do |column|
221
- lazy_load!(column.name) if column.lazy?
222
- value = instance_variable_get(column.instance_variable_name)
223
- pairs[column.name] = column.type == :class ? value.to_s : value
322
+ database_context.table(self).columns.each do |column|
323
+ if self.class.public_method_defined?(column.name)
324
+ lazy_load!(column.name) if column.lazy?
325
+ value = instance_variable_get(column.instance_variable_name)
326
+ pairs[column.name] = column.type == :class ? value.to_s : value
327
+ end
224
328
  end
225
329
 
226
330
  pairs
@@ -228,50 +332,42 @@ module DataMapper
228
332
 
229
333
  # Mass-assign mapped fields.
230
334
  def attributes=(values_hash)
231
- table = session.schema[self.class]
335
+ table = database_context.table(self.class)
232
336
 
233
- values_hash.reject do |key, value|
234
- protected_attribute? key
337
+ values_hash.delete_if do |key, value|
338
+ !self.class.public_method_defined?("#{key}=")
235
339
  end.each_pair do |key, value|
236
- if column = table[key]
237
- instance_variable_set(column.instance_variable_name, value)
238
- else
340
+ if respond_to?(key)
239
341
  send("#{key}=", value)
342
+ elsif column = table[key]
343
+ instance_variable_set(column.instance_variable_name, value)
240
344
  end
241
345
  end
242
346
  end
243
347
 
244
- def dirty?(name = nil)
245
- if name.nil?
246
- session.table(self).columns.any? do |column|
348
+ def dirty?
349
+ result = database_context.table(self).columns.any? do |column|
350
+ if column.type == :object
351
+ Marshal.dump(self.instance_variable_get(column.instance_variable_name)) != original_values[column.name]
352
+ else
247
353
  self.instance_variable_get(column.instance_variable_name) != original_values[column.name]
248
- end || loaded_associations.any? do |loaded_association|
249
- if loaded_association.respond_to?(:dirty?)
250
- loaded_association.dirty?
251
- else
252
- false
253
- end
254
354
  end
255
- else
256
- key = name.kind_of?(Symbol) ? name : name.to_sym
257
- self.instance_variable_get("@#{name}") != original_values[key]
355
+ end
356
+
357
+ return true if result
358
+
359
+ loaded_associations.any? do |loaded_association|
360
+ loaded_association.dirty?
258
361
  end
259
362
  end
260
363
 
261
364
  def dirty_attributes
262
365
  pairs = {}
263
366
 
264
- if new_record?
265
- session.table(self).columns.each do |column|
266
- unless (value = instance_variable_get(column.instance_variable_name)).nil?
267
- pairs[column.name] = value
268
- end
269
- end
270
- else
271
- session.table(self).columns.each do |column|
272
- if (value = instance_variable_get(column.instance_variable_name)) != original_values[column.name]
273
- pairs[column.name] = value
274
- end
367
+ database_context.table(self).columns.each do |column|
368
+ value = instance_variable_get(column.instance_variable_name)
369
+ if value != original_values[column.name] && (!new_record? || !column.serial?)
370
+ pairs[column.name] = column.type != :object ? value : YAML.dump(value)
275
371
  end
276
372
  end
277
373
 
@@ -282,14 +378,6 @@ module DataMapper
282
378
  @original_values || (@original_values = {})
283
379
  end
284
380
 
285
- def protected_attribute?(key)
286
- self.class.protected_attributes.include?(key.kind_of?(Symbol) ? key : key.to_sym)
287
- end
288
-
289
- def self.protected_attributes
290
- @protected_attributes ||= []
291
- end
292
-
293
381
  def self.index
294
382
  @index || @index = Ferret::Index::Index.new(:path => "#{database.adapter.index_path}/#{name}")
295
383
  end
@@ -311,14 +399,14 @@ module DataMapper
311
399
  return all(:id => ids)
312
400
  end
313
401
 
314
- def self.protect(*keys)
315
- keys.each { |key| protected_attributes << key.to_sym }
316
- end
317
-
318
402
  def self.foreign_key
319
403
  Inflector.underscore(self.name) + "_id"
320
404
  end
321
405
 
406
+ def self.table
407
+ database.schema[self]
408
+ end
409
+
322
410
  def inspect
323
411
  inspected_attributes = attributes.map { |k,v| "@#{k}=#{v.inspect}" }
324
412
 
@@ -335,27 +423,42 @@ module DataMapper
335
423
  @loaded_associations || @loaded_associations = []
336
424
  end
337
425
 
338
- def session=(value)
339
- @session = value
426
+ def database_context=(value)
427
+ @database_context = value
340
428
  end
341
429
 
342
- def session
343
- @session || ( @session = database )
430
+ def database_context
431
+ @database_context || ( @database_context = database )
344
432
  end
345
433
 
346
434
  def key=(value)
347
- key_column = session.schema[self.class].key
435
+ key_column = database_context.table(self.class).key
348
436
  @__key = key_column.type_cast_value(value)
349
437
  instance_variable_set(key_column.instance_variable_name, @__key)
350
438
  end
351
439
 
352
440
  def key
353
441
  @__key || @__key = begin
354
- key_column = session.schema[self.class].key
442
+ key_column = database_context.table(self.class).key
355
443
  key_column.type_cast_value(instance_variable_get(key_column.instance_variable_name))
356
444
  end
357
445
  end
446
+
447
+ private
448
+
449
+ # return all attributes, regardless of their visibility
450
+ def private_attributes
451
+ pairs = {}
452
+
453
+ database_context.table(self).columns.each do |column|
454
+ lazy_load!(column.name) if column.lazy?
455
+ value = instance_variable_get(column.instance_variable_name)
456
+ pairs[column.name] = column.type == :class ? value.to_s : value
457
+ end
458
+
459
+ pairs
460
+ end
358
461
 
359
462
  end
360
463
 
361
- end
464
+ end
@@ -20,24 +20,26 @@ module DataMapper
20
20
  def first(klass, *args)
21
21
  id = nil
22
22
  options = nil
23
+ table = self.table(klass)
24
+ key = table.key
23
25
 
24
26
  if args.empty? # No id, no options
25
27
  options = { :limit => 1 }
26
28
  elsif args.size == 2 && args.last.kind_of?(Hash) # id AND options
27
- options = args.last.merge(:id => args.first)
29
+ options = args.last.merge(key => args.first)
28
30
  elsif args.size == 1 # id OR options
29
31
  if args.first.kind_of?(Hash)
30
32
  options = args.first.merge(:limit => 1) # no id, add limit
31
33
  else
32
- options = { :id => args.first } # no options, set id
34
+ options = { key => args.first } # no options, set id
33
35
  end
34
36
  else
35
- raise ArgumentError.new('Session#first takes a class, and optional type_or_id and/or options arguments')
37
+ raise ArgumentError.new('Context#first takes a class, and optional type_or_id and/or options arguments')
36
38
  end
37
39
 
38
40
  # Account for undesired behaviour in MySQL that returns the
39
41
  # last inserted row when the WHERE clause contains a "#{primary_key} IS NULL".
40
- return nil if options.has_key?(:id) && options[:id] == nil
42
+ return nil if options.has_key?(key.name) && options[key.name] == nil
41
43
 
42
44
  @adapter.load(self, klass, options).first
43
45
  end
@@ -46,8 +48,8 @@ module DataMapper
46
48
  @adapter.load(self, klass, options)
47
49
  end
48
50
 
49
- def count(klass, options = {})
50
- @adapter.count(klass, options)
51
+ def count(klass, *args)
52
+ table(klass).count(*args)
51
53
  end
52
54
 
53
55
  def save(instance)
@@ -63,19 +65,23 @@ module DataMapper
63
65
  end
64
66
 
65
67
  def truncate(klass)
66
- @adapter.truncate(self, klass)
68
+ table(klass).truncate!
67
69
  end
68
70
 
69
71
  def create_table(klass)
70
- @adapter.create_table(klass)
72
+ table(klass).create!
71
73
  end
72
74
 
73
75
  def drop_table(klass)
74
- @adapter.drop(self, klass)
76
+ table(klass).drop!
75
77
  end
76
78
 
77
79
  def table_exists?(klass)
78
- @adapter.table_exists?(klass)
80
+ table(klass).exists?
81
+ end
82
+
83
+ def column_exists_for_table?(klass, column_name)
84
+ @adapter.column_exists_for_table?(klass, column_name)
79
85
  end
80
86
 
81
87
  def execute(*args)
@@ -27,10 +27,12 @@ module DataMapper
27
27
  unless block_given?
28
28
  Database.context.last || Context.new(Database[name].adapter)
29
29
  else
30
- Database.context.push(Context.new(Database[name].adapter))
31
- result = yield(Database.context.last)
32
- Database.context.pop
33
- result
30
+ begin
31
+ Database.context.push(Context.new(Database[name].adapter))
32
+ return yield(Database.context.last)
33
+ ensure
34
+ Database.context.pop
35
+ end
34
36
  end
35
37
  end
36
38
 
@@ -172,8 +174,13 @@ module DataMapper
172
174
  @log_stream = nil
173
175
  end
174
176
 
175
- attr_reader :name, :adapter
176
- attr_accessor :host, :database, :schema_search_path, :username, :password, :log_stream, :log_level, :index_path, :socket
177
+ attr_reader :name, :adapter, :log_stream
178
+
179
+ attr_accessor :host, :database, :schema_search_path, :username, :password, :log_level, :index_path, :socket
180
+
181
+ def log_stream=(val)
182
+ @log_stream = (val.is_a?(String) && val =~ /STDOUT/ ? STDOUT : val)
183
+ end
177
184
 
178
185
  # Allows us to set the adapter for this database object. It can only be set once, and expects two types of values.
179
186
  #
@@ -206,17 +213,21 @@ module DataMapper
206
213
 
207
214
  # Default Logger from Ruby's logger.rb
208
215
  def logger
209
- @logger = Logger.new(@log_stream, File::WRONLY | File::APPEND | File::CREAT)
210
- @logger.level = @log_level
211
- at_exit { @logger.close }
212
-
216
+ @logger = create_logger
217
+
213
218
  class << self
214
219
  attr_reader :logger
215
220
  end
216
-
221
+
217
222
  return @logger
218
223
  end
219
224
 
225
+ def create_logger
226
+ x = Logger.new(@log_stream, File::WRONLY | File::APPEND | File::CREAT)
227
+ x.level = @log_level
228
+ at_exit { x.close }
229
+ return x
230
+ end
220
231
  end
221
232
 
222
233
  end
@@ -0,0 +1,28 @@
1
+ module DataMapper
2
+ class DependencyQueue
3
+
4
+ def initialize
5
+ @dependencies = Hash.new { |h,k| h[k] = [] }
6
+ end
7
+
8
+ def add(class_name, &b)
9
+ @dependencies[class_name] << b
10
+ resolve!
11
+ end
12
+
13
+ def resolve!
14
+ @dependencies.each_pair do |class_name, callbacks|
15
+ if Object.const_defined?(class_name)
16
+ klass = Object.const_get(class_name)
17
+
18
+ callbacks.each do |b|
19
+ b.call(klass)
20
+ end
21
+
22
+ callbacks.clear
23
+ end
24
+ end
25
+ end
26
+
27
+ end # class DependencyQueue
28
+ end # module DataMapper