datamapper 0.2.4 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
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