datamapper 0.2.4 → 0.2.5

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 (65) hide show
  1. data/CHANGELOG +16 -1
  2. data/README +10 -8
  3. data/environment.rb +1 -1
  4. data/example.rb +13 -5
  5. data/lib/data_mapper.rb +2 -0
  6. data/lib/data_mapper/adapters/abstract_adapter.rb +61 -0
  7. data/lib/data_mapper/adapters/data_object_adapter.rb +33 -6
  8. data/lib/data_mapper/adapters/mysql_adapter.rb +5 -0
  9. data/lib/data_mapper/adapters/postgresql_adapter.rb +12 -0
  10. data/lib/data_mapper/adapters/sql/commands/load_command.rb +6 -14
  11. data/lib/data_mapper/adapters/sql/mappings/column.rb +37 -26
  12. data/lib/data_mapper/adapters/sql/mappings/table.rb +50 -8
  13. data/lib/data_mapper/associations.rb +4 -5
  14. data/lib/data_mapper/associations/has_and_belongs_to_many_association.rb +2 -2
  15. data/lib/data_mapper/associations/has_many_association.rb +40 -13
  16. data/lib/data_mapper/associations/has_n_association.rb +1 -1
  17. data/lib/data_mapper/base.rb +5 -448
  18. data/lib/data_mapper/callbacks.rb +12 -2
  19. data/lib/data_mapper/context.rb +4 -0
  20. data/lib/data_mapper/database.rb +1 -1
  21. data/lib/data_mapper/identity_map.rb +2 -2
  22. data/lib/data_mapper/persistence.rb +538 -0
  23. data/lib/data_mapper/support/active_record_impersonation.rb +21 -3
  24. data/lib/data_mapper/support/errors.rb +2 -0
  25. data/lib/data_mapper/support/serialization.rb +7 -10
  26. data/lib/data_mapper/support/struct.rb +7 -0
  27. data/lib/data_mapper/validatable_extensions/validations/validates_uniqueness_of.rb +11 -4
  28. data/performance.rb +23 -10
  29. data/rakefile.rb +1 -1
  30. data/spec/active_record_impersonation_spec.rb +2 -6
  31. data/spec/acts_as_tree_spec.rb +3 -1
  32. data/spec/associations_spec.rb +40 -160
  33. data/spec/attributes_spec.rb +1 -1
  34. data/spec/base_spec.rb +41 -13
  35. data/spec/callbacks_spec.rb +32 -0
  36. data/spec/coersion_spec.rb +1 -1
  37. data/spec/column_spec.rb +22 -12
  38. data/spec/dependency_spec.rb +5 -3
  39. data/spec/embedded_value_spec.rb +33 -17
  40. data/spec/has_many_association_spec.rb +173 -0
  41. data/spec/legacy_spec.rb +2 -2
  42. data/spec/load_command_spec.rb +59 -7
  43. data/spec/models/animal.rb +6 -2
  44. data/spec/models/animals_exhibit.rb +3 -1
  45. data/spec/models/career.rb +2 -1
  46. data/spec/models/comment.rb +3 -1
  47. data/spec/models/exhibit.rb +3 -1
  48. data/spec/models/fruit.rb +3 -1
  49. data/spec/models/person.rb +10 -1
  50. data/spec/models/post.rb +3 -1
  51. data/spec/models/project.rb +3 -1
  52. data/spec/models/section.rb +3 -1
  53. data/spec/models/serializer.rb +3 -1
  54. data/spec/models/user.rb +3 -1
  55. data/spec/models/zoo.rb +3 -1
  56. data/spec/paranoia_spec.rb +3 -1
  57. data/spec/postgres_spec.rb +54 -0
  58. data/spec/save_command_spec.rb +9 -5
  59. data/spec/schema_spec.rb +0 -91
  60. data/spec/single_table_inheritance_spec.rb +8 -0
  61. data/spec/table_spec.rb +46 -0
  62. data/spec/validates_uniqueness_of_spec.rb +19 -1
  63. metadata +8 -10
  64. data/lib/data_mapper/associations/has_one_association.rb +0 -77
  65. data/plugins/dataobjects/do_rb +0 -0
@@ -8,7 +8,7 @@ module DataMapper
8
8
 
9
9
  class Table
10
10
 
11
- attr_reader :klass, :name
11
+ attr_reader :klass, :name, :indexes, :composite_indexes
12
12
 
13
13
  def initialize(adapter, klass_or_name)
14
14
  raise "\"klass_or_name\" must not be nil!" if klass_or_name.nil?
@@ -28,8 +28,7 @@ module DataMapper
28
28
  @paranoid = false
29
29
  @paranoid_column = nil
30
30
 
31
- if @klass && @klass.ancestors.include?(DataMapper::Base) && @klass.superclass != DataMapper::Base
32
-
31
+ if @klass && @klass.respond_to?(:persistent?) && @klass.superclass.respond_to?(:persistent?)
33
32
  super_table = @adapter.table(@klass.superclass)
34
33
 
35
34
  super_table.columns.each do |column|
@@ -120,6 +119,8 @@ module DataMapper
120
119
  else
121
120
  @adapter.connection do |db|
122
121
  db.create_command(to_create_sql).execute_non_query
122
+ index_queries = to_create_index_sql + to_create_composite_index_sql
123
+ index_queries.each { |q| db.create_command(q).execute_non_query }
123
124
  schema << self
124
125
  true
125
126
  end
@@ -189,7 +190,7 @@ module DataMapper
189
190
  class << self
190
191
  attr_accessor :key
191
192
  end
192
- Base::dependencies.resolve!
193
+ Persistence::dependencies.resolve!
193
194
 
194
195
  self.key
195
196
  end
@@ -199,8 +200,21 @@ module DataMapper
199
200
  @keys = @columns.select { |column| column.key? }
200
201
  end
201
202
  end
202
-
203
- def add_column(column_name, type, options)
203
+
204
+ def indexes
205
+ @indexes || begin
206
+ @indexes = @columns.select { |column| column.index? }
207
+ end
208
+ end
209
+
210
+ # Add a composite index to the table.
211
+ # +index_columns+ should be an array including each column name.
212
+ def add_composite_index(index_columns = [], unique = false)
213
+ @composite_indexes ||= []
214
+ @composite_indexes << [index_columns, unique] # add paired tuple with the index
215
+ end
216
+
217
+ def add_column(column_name, type, options = {})
204
218
 
205
219
  column_ordinal = if options.is_a?(Hash) && options.has_key?(:ordinal)
206
220
  options.delete(:ordinal)
@@ -238,8 +252,8 @@ module DataMapper
238
252
  elsif @klass_or_name.kind_of?(String)
239
253
  @klass_or_name
240
254
  elsif @klass_or_name.kind_of?(Class)
241
- if @klass_or_name.superclass != DataMapper::Base \
242
- && @klass_or_name.ancestors.include?(DataMapper::Base)
255
+ persistent_ancestor = @klass_or_name.superclass.respond_to?(:persistent?)
256
+ if @klass_or_name.superclass.respond_to?(:persistent?)
243
257
  @adapter.table(@klass_or_name.superclass).name
244
258
  else
245
259
  Inflector.tableize(@klass_or_name.name)
@@ -297,10 +311,38 @@ module DataMapper
297
311
  sql << ", PRIMARY KEY (#{keys.map { |c| c.to_sql }.join(', ') })"
298
312
  end
299
313
  sql << ")"
314
+
300
315
  sql.compress_lines
301
316
  end
302
317
  end
303
318
 
319
+ # Returns an array with each separate CREATE INDEX statement
320
+ def to_create_index_sql
321
+ queries = []
322
+ unless indexes.blank?
323
+ indexes.each do |column|
324
+ sql = "CREATE INDEX #{to_s.downcase}_#{column}_index ON "
325
+ sql << "#{to_sql} (#{column.to_sql})"
326
+ queries << sql.compress_lines
327
+ end
328
+ end
329
+ queries
330
+ end
331
+
332
+ # Returns an array with each separate CREATE INDEX statement
333
+ def to_create_composite_index_sql
334
+ queries = []
335
+ unless composite_indexes.blank?
336
+ composite_indexes.each do |columns, unique|
337
+ sql = "CREATE #{unique ? 'UNIQUE ' : ''}INDEX "
338
+ sql << "#{to_s.downcase}_#{columns.join('_')}_index ON "
339
+ sql << "#{to_sql} (#{columns.join(', ')})"
340
+ queries << sql.compress_lines
341
+ end
342
+ end
343
+ queries
344
+ end
345
+
304
346
  def to_drop_sql
305
347
  @to_drop_sql || @to_drop_sql = "DROP TABLE #{to_sql}"
306
348
  end
@@ -1,7 +1,6 @@
1
1
  require 'data_mapper/associations/reference'
2
2
  require 'data_mapper/associations/has_many_association'
3
3
  require 'data_mapper/associations/belongs_to_association'
4
- require 'data_mapper/associations/has_one_association'
5
4
  require 'data_mapper/associations/has_and_belongs_to_many_association'
6
5
 
7
6
  module DataMapper
@@ -17,6 +16,10 @@ module DataMapper
17
16
  database.schema[self].associations << HasManyAssociation.new(self, association_name, options)
18
17
  end
19
18
 
19
+ def has_one(association_name, options = {})
20
+ database.schema[self].associations << HasManyAssociation.new(self, association_name, options)
21
+ end
22
+
20
23
  def belongs_to(association_name, options = {})
21
24
  database.schema[self].associations << BelongsToAssociation.new(self, association_name, options)
22
25
  end
@@ -25,10 +28,6 @@ module DataMapper
25
28
  database.schema[self].associations << HasAndBelongsToManyAssociation.new(self, association_name, options)
26
29
  end
27
30
 
28
- def has_one(association_name, options = {})
29
- database.schema[self].associations << HasOneAssociation.new(self, association_name, options)
30
- end
31
-
32
31
  end
33
32
 
34
33
  end
@@ -184,7 +184,7 @@ module DataMapper
184
184
  command = db.create_command(association.to_delete_sql)
185
185
  command.execute_non_query(@instance.key)
186
186
  end
187
-
187
+
188
188
  unless @entries.empty?
189
189
  if adapter.batch_insertable?
190
190
  sql = association.to_insert_sql
@@ -224,7 +224,7 @@ module DataMapper
224
224
 
225
225
  def <<(member)
226
226
  @new_members = true
227
- entries << member
227
+ entries << member unless member.nil?
228
228
  end
229
229
 
230
230
  def clear
@@ -22,6 +22,14 @@ module DataMapper
22
22
  "UPDATE #{associated_table.to_sql} SET #{foreign_key_column.to_sql} = NULL WHERE #{foreign_key_column.to_sql} = ?"
23
23
  end
24
24
 
25
+ def instance_variable_name
26
+ class << self
27
+ attr_reader :instance_variable_name
28
+ end
29
+
30
+ @instance_variable_name = "@#{@association_name}"
31
+ end
32
+
25
33
  class Set < Associations::Reference
26
34
 
27
35
  include Enumerable
@@ -60,7 +68,7 @@ module DataMapper
60
68
  end
61
69
 
62
70
  def <<(associated_item)
63
- items << associated_item
71
+ (@items || @items = []) << associated_item
64
72
 
65
73
  # TODO: Optimize!
66
74
  fk = association.foreign_key_column
@@ -85,8 +93,12 @@ module DataMapper
85
93
  item
86
94
  end
87
95
 
88
- def set(items)
89
- @items = items
96
+ def set(value)
97
+ values = value.is_a?(Enumerable) ? value : [value]
98
+ @items = []
99
+ values.each do |item|
100
+ self << item
101
+ end
90
102
  end
91
103
 
92
104
  def method_missing(symbol, *args, &block)
@@ -100,6 +112,8 @@ module DataMapper
100
112
  end
101
113
  end
102
114
  results.flatten
115
+ elsif items.size == 1 && items.first.respond_to?(symbol)
116
+ items.first.send(symbol, *args, &block)
103
117
  else
104
118
  super
105
119
  end
@@ -117,17 +131,12 @@ module DataMapper
117
131
  @items || begin
118
132
  if @instance.loaded_set.nil?
119
133
  @items = []
120
- else
121
- fk = association.foreign_key_column.to_sym
122
-
123
- finder_options = { association.foreign_key_column.to_sym => @instance.loaded_set.map { |item| item.key } }
124
- finder_options.merge!(association.finder_options)
125
-
126
- associated_items = @instance.database_context.all(
127
- association.associated_constant,
128
- finder_options
129
- ).group_by { |entry| entry.send(fk) }
134
+ else
135
+ associated_items = fetch_sets
130
136
 
137
+ # This is where @items is set, by calling association=,
138
+ # which in turn calls HasManyAssociation::Set#set.
139
+ association_ivar_name = association.instance_variable_name
131
140
  setter_method = "#{@association_name}=".to_sym
132
141
  @instance.loaded_set.each do |entry|
133
142
  entry.send(setter_method, associated_items[entry.key])
@@ -141,6 +150,24 @@ module DataMapper
141
150
  def inspect
142
151
  entries.inspect
143
152
  end
153
+
154
+ def ==(other)
155
+ (items.size == 1 ? items.first : items) == other
156
+ end
157
+
158
+ private
159
+ def fetch_sets
160
+ finder_options = { association.foreign_key_column.to_sym => @instance.loaded_set.map { |item| item.key } }
161
+ finder_options.merge!(association.finder_options)
162
+
163
+ foreign_key_ivar_name = association.foreign_key_column.instance_variable_name
164
+
165
+ @instance.database_context.all(
166
+ association.associated_constant,
167
+ finder_options
168
+ ).group_by { |entry| entry.instance_variable_get(foreign_key_ivar_name) }
169
+ end
170
+
144
171
  end
145
172
 
146
173
  end
@@ -23,7 +23,7 @@ module DataMapper
23
23
 
24
24
  define_accessor(klass)
25
25
 
26
- Base::dependencies.add(associated_constant_name) do |klass|
26
+ Persistence::dependencies.add(associated_constant_name) do |klass|
27
27
  @foreign_key_column = associated_table[foreign_key_name]
28
28
 
29
29
  unless @foreign_key_column
@@ -1,12 +1,4 @@
1
- require 'data_mapper/property'
2
- require 'data_mapper/support/active_record_impersonation'
3
- require 'data_mapper/support/serialization'
4
- require 'data_mapper/validations'
5
- require 'data_mapper/associations'
6
- require 'data_mapper/callbacks'
7
- require 'data_mapper/embedded_value'
8
- require 'data_mapper/auto_migrations'
9
- require 'data_mapper/dependency_queue'
1
+ require 'data_mapper/persistence'
10
2
 
11
3
  begin
12
4
  require 'ferret'
@@ -16,449 +8,14 @@ end
16
8
  module DataMapper
17
9
 
18
10
  class Base
19
-
20
- # This probably needs to be protected
21
- attr_accessor :loaded_set
22
-
23
- include CallbacksHelper
24
- include Support::ActiveRecordImpersonation
25
- include Support::Serialization
26
- include Validations
27
- include Associations
28
-
29
- # Track classes that inherit from DataMapper::Base.
30
- def self.subclasses
31
- @subclasses || (@subclasses = [])
32
- end
33
-
34
- def self.auto_migrate!
35
- subclasses.each do |subclass|
36
- subclass.auto_migrate!
37
- end
38
- end
39
-
40
- def self.dependencies
41
- @dependency_queue || (@dependency_queue = DependencyQueue.new)
42
- end
11
+ include DataMapper::Persistence
43
12
 
44
13
  def self.inherited(klass)
45
- klass.instance_variable_set('@properties', [])
46
-
47
- klass.send :extend, AutoMigrations
48
- DataMapper::Base::subclasses << klass
49
- klass.send(:undef_method, :id)
50
-
51
- # When this class is sub-classed, copy the declared columns.
52
- klass.class_eval do
53
- def self.subclasses
54
- @subclasses || (@subclasses = [])
55
- end
56
-
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
-
65
- self::subclasses << subclass
66
- end
67
- end
68
- end
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
-
86
- # Allows you to override the table name for a model.
87
- # EXAMPLE:
88
- # class WorkItem
89
- # set_table_name 't_work_item_list'
90
- # end
91
- def self.set_table_name(value)
92
- database.schema[self].name = value
93
- end
94
-
95
- def initialize(details = nil)
96
-
97
- case details
98
- when Hash then self.attributes = details
99
- when DataMapper::Base then self.attributes = details.attributes
100
- when NilClass then nil
101
- end
102
- end
103
-
104
- PROPERTY_OPTIONS = [
105
- :public, :protected, :private, :accessor, :reader, :writer,
106
- :lazy, :default, :nullable, :key, :serial, :column
107
- ]
108
-
109
- # Adds property accessors for a field that you'd like to be able to modify. The DataMapper doesn't
110
- # use the table schema to infer accessors, you must explicity call #property to add field accessors
111
- # to your model.
112
- #
113
- # EXAMPLE:
114
- # class CellProvider
115
- # property :name, :string
116
- # property :rating, :integer
117
- # end
118
- #
119
- # att = CellProvider.new(:name => 'AT&T')
120
- # att.rating = 3
121
- # puts att.name, att.rating
122
- #
123
- # => AT&T
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
135
- def self.property(name, type, options = {})
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)
153
-
154
- if MAGIC_PROPERTIES.has_key?(name)
155
- class_eval(&MAGIC_PROPERTIES[name])
156
- end
157
-
158
- return name
159
- end
160
-
161
- MAGIC_PROPERTIES = {
162
- :updated_at => lambda { before_save { |x| x.updated_at = Time::now } },
163
- :updated_on => lambda { before_save { |x| x.updated_on = Date::today } },
164
- :created_at => lambda { before_create { |x| x.created_at = Time::now } },
165
- :created_on => lambda { before_create { |x| x.created_on = Date::today } }
166
- }
167
-
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)
204
- end
205
-
206
- def self.property_getter(mapping, visibility = :public)
207
- if mapping.lazy?
208
- class_eval <<-EOS
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}
216
- end
217
- EOS
218
- else
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")
224
- end
225
- end
226
-
227
- def self.property_setter(mapping, visibility = :public)
228
- if mapping.lazy?
229
- class_eval <<-EOS
230
- #{visibility.to_s}
231
- def #{mapping.name}=(value)
232
- class << self;
233
- attr_accessor #{mapping.name.inspect}
234
- end
235
- @#{mapping.name} = value
236
- end
237
- EOS
238
- else
239
- class_eval("#{visibility.to_s}; def #{mapping.name}=(value); #{mapping.instance_variable_name} = value end")
240
- end
241
- end
242
-
243
- # Lazy-loads the attributes for a loaded_set, then overwrites the accessors
244
- # for the named methods so that the lazy_loading is skipped the second time.
245
- def lazy_load!(*names)
246
-
247
- names = names.map { |name| name.to_sym }.reject { |name| lazy_loaded_attributes.include?(name) }
248
-
249
- reset_attribute = lambda do |instance|
250
- singleton_class = (class << instance; self end)
251
- names.each do |name|
252
- instance.lazy_loaded_attributes << name
253
- singleton_class.send(:attr_accessor, name)
254
- end
255
- end
256
-
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(
265
- self.class,
266
- :select => ([key] + names),
267
- :reload => true,
268
- key => keys_to_select
269
- ).each(&reset_attribute)
270
- else
271
- reset_attribute[self]
272
- end
273
-
274
- end
275
-
276
- def new_record?
277
- @new_record.nil? || @new_record
278
- end
279
-
280
- def ==(other)
281
- private_attributes == other.send("private_attributes")
282
- end
283
-
284
- # Returns the difference between two objects, in terms of their attributes.
285
- def ^(other)
286
- results = {}
287
-
288
- self_attributes, other_attributes = attributes, other.attributes
289
-
290
- self_attributes.each_pair do |k,v|
291
- other_value = other_attributes[k]
292
- unless v == other_value
293
- results[k] = [v, other_value]
294
- end
295
- end
296
-
297
- results
298
- end
299
-
300
- def lazy_loaded_attributes
301
- @lazy_loaded_attributes || @lazy_loaded_attributes = Set.new
302
- end
303
-
304
- def loaded_attributes
305
- pairs = {}
306
-
307
- database_context.table(self).columns.each do |column|
308
- pairs[column.name] = instance_variable_get(column.instance_variable_name)
309
- end
310
-
311
- pairs
312
- end
313
-
314
- def update_attributes(update_hash)
315
- self.attributes = update_hash
316
- self.save
317
- end
318
-
319
- def attributes
320
- pairs = {}
321
-
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
328
- end
329
-
330
- pairs
331
- end
332
-
333
- # Mass-assign mapped fields.
334
- def attributes=(values_hash)
335
- table = database_context.table(self.class)
336
-
337
- values_hash.delete_if do |key, value|
338
- !self.class.public_method_defined?("#{key}=")
339
- end.each_pair do |key, value|
340
- if respond_to?(key)
341
- send("#{key}=", value)
342
- elsif column = table[key]
343
- instance_variable_set(column.instance_variable_name, value)
344
- end
345
- end
346
- end
347
-
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
353
- self.instance_variable_get(column.instance_variable_name) != original_values[column.name]
354
- end
355
- end
356
-
357
- return true if result
358
-
359
- loaded_associations.any? do |loaded_association|
360
- loaded_association.dirty?
361
- end
14
+ DataMapper::Persistence::prepare_for_persistence(klass)
362
15
  end
363
16
 
364
- def dirty_attributes
365
- pairs = {}
366
-
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)
371
- end
372
- end
373
-
374
- pairs
375
- end
376
-
377
- def original_values
378
- @original_values || (@original_values = {})
379
- end
380
-
381
- def self.index
382
- @index || @index = Ferret::Index::Index.new(:path => "#{database.adapter.index_path}/#{name}")
383
- end
384
-
385
- def self.reindex!
386
- all.each do |record|
387
- index << record.attributes
388
- end
389
- end
390
-
391
- def self.search(phrase)
392
- ids = []
393
-
394
- query = "#{database.schema[self].columns.map(&:name).join('|')}:\"#{phrase}\""
395
-
396
- index.search_each(query) do |document_id, score|
397
- ids << index[document_id][:id]
398
- end
399
- return all(:id => ids)
400
- end
401
-
402
- def self.foreign_key
403
- Inflector.underscore(self.name) + "_id"
404
- end
405
-
406
- def self.table
407
- database.schema[self]
408
- end
409
-
410
- def inspect
411
- inspected_attributes = attributes.map { |k,v| "@#{k}=#{v.inspect}" }
412
-
413
- instance_variables.each do |name|
414
- if instance_variable_get(name).kind_of?(Associations::HasManyAssociation)
415
- inspected_attributes << "#{name}=#{instance_variable_get(name).inspect}"
416
- end
417
- end
418
-
419
- "#<%s:0x%x @new_record=%s, %s>" % [self.class.name, (object_id * 2), new_record?, inspected_attributes.join(', ')]
420
- end
421
-
422
- def loaded_associations
423
- @loaded_associations || @loaded_associations = []
424
- end
425
-
426
- def database_context=(value)
427
- @database_context = value
428
- end
429
-
430
- def database_context
431
- @database_context || ( @database_context = database )
432
- end
433
-
434
- def key=(value)
435
- key_column = database_context.table(self.class).key
436
- @__key = key_column.type_cast_value(value)
437
- instance_variable_set(key_column.instance_variable_name, @__key)
438
- end
439
-
440
- def key
441
- @__key || @__key = begin
442
- key_column = database_context.table(self.class).key
443
- key_column.type_cast_value(instance_variable_get(key_column.instance_variable_name))
444
- end
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
17
+ def self.subclasses
18
+ []
460
19
  end
461
-
462
20
  end
463
-
464
21
  end