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.
- data/CHANGELOG +16 -1
- data/README +10 -8
- data/environment.rb +1 -1
- data/example.rb +13 -5
- data/lib/data_mapper.rb +2 -0
- data/lib/data_mapper/adapters/abstract_adapter.rb +61 -0
- data/lib/data_mapper/adapters/data_object_adapter.rb +33 -6
- data/lib/data_mapper/adapters/mysql_adapter.rb +5 -0
- data/lib/data_mapper/adapters/postgresql_adapter.rb +12 -0
- data/lib/data_mapper/adapters/sql/commands/load_command.rb +6 -14
- data/lib/data_mapper/adapters/sql/mappings/column.rb +37 -26
- data/lib/data_mapper/adapters/sql/mappings/table.rb +50 -8
- data/lib/data_mapper/associations.rb +4 -5
- data/lib/data_mapper/associations/has_and_belongs_to_many_association.rb +2 -2
- data/lib/data_mapper/associations/has_many_association.rb +40 -13
- data/lib/data_mapper/associations/has_n_association.rb +1 -1
- data/lib/data_mapper/base.rb +5 -448
- data/lib/data_mapper/callbacks.rb +12 -2
- data/lib/data_mapper/context.rb +4 -0
- data/lib/data_mapper/database.rb +1 -1
- data/lib/data_mapper/identity_map.rb +2 -2
- data/lib/data_mapper/persistence.rb +538 -0
- data/lib/data_mapper/support/active_record_impersonation.rb +21 -3
- data/lib/data_mapper/support/errors.rb +2 -0
- data/lib/data_mapper/support/serialization.rb +7 -10
- data/lib/data_mapper/support/struct.rb +7 -0
- data/lib/data_mapper/validatable_extensions/validations/validates_uniqueness_of.rb +11 -4
- data/performance.rb +23 -10
- data/rakefile.rb +1 -1
- data/spec/active_record_impersonation_spec.rb +2 -6
- data/spec/acts_as_tree_spec.rb +3 -1
- data/spec/associations_spec.rb +40 -160
- data/spec/attributes_spec.rb +1 -1
- data/spec/base_spec.rb +41 -13
- data/spec/callbacks_spec.rb +32 -0
- data/spec/coersion_spec.rb +1 -1
- data/spec/column_spec.rb +22 -12
- data/spec/dependency_spec.rb +5 -3
- data/spec/embedded_value_spec.rb +33 -17
- data/spec/has_many_association_spec.rb +173 -0
- data/spec/legacy_spec.rb +2 -2
- data/spec/load_command_spec.rb +59 -7
- data/spec/models/animal.rb +6 -2
- data/spec/models/animals_exhibit.rb +3 -1
- data/spec/models/career.rb +2 -1
- data/spec/models/comment.rb +3 -1
- data/spec/models/exhibit.rb +3 -1
- data/spec/models/fruit.rb +3 -1
- data/spec/models/person.rb +10 -1
- data/spec/models/post.rb +3 -1
- data/spec/models/project.rb +3 -1
- data/spec/models/section.rb +3 -1
- data/spec/models/serializer.rb +3 -1
- data/spec/models/user.rb +3 -1
- data/spec/models/zoo.rb +3 -1
- data/spec/paranoia_spec.rb +3 -1
- data/spec/postgres_spec.rb +54 -0
- data/spec/save_command_spec.rb +9 -5
- data/spec/schema_spec.rb +0 -91
- data/spec/single_table_inheritance_spec.rb +8 -0
- data/spec/table_spec.rb +46 -0
- data/spec/validates_uniqueness_of_spec.rb +19 -1
- metadata +8 -10
- data/lib/data_mapper/associations/has_one_association.rb +0 -77
- data/plugins/dataobjects/do_rb +0 -0
@@ -60,7 +60,7 @@ module DataMapper
|
|
60
60
|
def initialize
|
61
61
|
@callbacks = Hash.new do |h,k|
|
62
62
|
raise 'Callback names must be Symbols' unless k.kind_of?(Symbol)
|
63
|
-
h[k] =
|
63
|
+
h[k] = Set.new
|
64
64
|
end
|
65
65
|
end
|
66
66
|
|
@@ -89,9 +89,19 @@ module DataMapper
|
|
89
89
|
# the instance executed against (as a method call).
|
90
90
|
def add(name, block)
|
91
91
|
callback = @callbacks[name]
|
92
|
-
raise ArgumentError.new("You didn't specify a callback in String, Symbol or Proc form.")
|
92
|
+
raise ArgumentError.new("You didn't specify a callback in String, Symbol or Proc form.") unless [String, Proc, Symbol].detect { |type| block.is_a?(type) }
|
93
93
|
callback << block
|
94
94
|
end
|
95
|
+
|
96
|
+
def dup
|
97
|
+
copy = self.class.new
|
98
|
+
@callbacks.each_pair do |name, callbacks|
|
99
|
+
callbacks.each do |callback|
|
100
|
+
copy.add(name, callback)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
return copy
|
104
|
+
end
|
95
105
|
end
|
96
106
|
|
97
107
|
end
|
data/lib/data_mapper/context.rb
CHANGED
data/lib/data_mapper/database.rb
CHANGED
@@ -0,0 +1,538 @@
|
|
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'
|
10
|
+
require 'data_mapper/support/struct'
|
11
|
+
|
12
|
+
module DataMapper
|
13
|
+
# See DataMapper::Persistence::ClassMethods for DataMapper's DSL documentation.
|
14
|
+
module Persistence
|
15
|
+
# This probably needs to be protected
|
16
|
+
attr_accessor :loaded_set
|
17
|
+
|
18
|
+
def self.included(klass)
|
19
|
+
klass.extend(ClassMethods)
|
20
|
+
|
21
|
+
klass.send(:include, Associations)
|
22
|
+
klass.send(:include, Validations)
|
23
|
+
klass.send(:include, CallbacksHelper)
|
24
|
+
klass.send(:include, Support::ActiveRecordImpersonation)
|
25
|
+
klass.send(:include, Support::Serialization)
|
26
|
+
|
27
|
+
prepare_for_persistence(klass)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.prepare_for_persistence(klass)
|
31
|
+
klass.instance_variable_set('@properties', [])
|
32
|
+
|
33
|
+
klass.send :extend, AutoMigrations
|
34
|
+
klass.subclasses
|
35
|
+
DataMapper::Persistence::subclasses << klass unless klass == DataMapper::Base
|
36
|
+
klass.send(:undef_method, :id) if method_defined?(:id)
|
37
|
+
|
38
|
+
return if klass == DataMapper::Base
|
39
|
+
|
40
|
+
# When this class is sub-classed, copy the declared columns.
|
41
|
+
klass.class_eval do
|
42
|
+
def self.subclasses
|
43
|
+
@subclasses || (@subclasses = [])
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.inherited(subclass)
|
47
|
+
super_table = database.table(self)
|
48
|
+
|
49
|
+
if super_table.type_column.nil?
|
50
|
+
super_table.add_column(:type, :class, {})
|
51
|
+
end
|
52
|
+
|
53
|
+
subclass.instance_variable_set("@callbacks", self.callbacks.dup)
|
54
|
+
|
55
|
+
self::subclasses << subclass
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.persistent?
|
59
|
+
true
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.auto_migrate!
|
65
|
+
subclasses.each do |subclass|
|
66
|
+
subclass.auto_migrate!
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Track classes that include this module.
|
71
|
+
def self.subclasses
|
72
|
+
@subclasses || (@subclasses = [])
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.dependencies
|
76
|
+
@dependency_queue || (@dependency_queue = DependencyQueue.new)
|
77
|
+
end
|
78
|
+
|
79
|
+
def initialize(details = nil)
|
80
|
+
case details
|
81
|
+
when Hash then self.attributes = details
|
82
|
+
when details.respond_to?(:persistent?) then self.unsafe_attributes = details.attributes
|
83
|
+
when Struct then self.unsafe_attributes = details.attributes
|
84
|
+
when NilClass then nil
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
module ClassMethods
|
89
|
+
|
90
|
+
# Track classes that include this module.
|
91
|
+
def subclasses
|
92
|
+
@subclasses || (@subclasses = [])
|
93
|
+
end
|
94
|
+
|
95
|
+
def logger
|
96
|
+
database.logger
|
97
|
+
end
|
98
|
+
|
99
|
+
def transaction
|
100
|
+
yield
|
101
|
+
end
|
102
|
+
|
103
|
+
def foreign_key
|
104
|
+
Inflector.underscore(self.name) + "_id"
|
105
|
+
end
|
106
|
+
|
107
|
+
def extended(klass)
|
108
|
+
unless klass == DataMapper::Base
|
109
|
+
klass.class_eval do
|
110
|
+
def persistent?
|
111
|
+
true
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def table
|
118
|
+
database.table(self)
|
119
|
+
end
|
120
|
+
|
121
|
+
# NOTE: check is only for psql, so maybe the postgres adapter should define
|
122
|
+
# its own property options. currently it will produce a warning tho since
|
123
|
+
# PROPERTY_OPTIONS is a constant
|
124
|
+
PROPERTY_OPTIONS = [
|
125
|
+
:public, :protected, :private, :accessor, :reader, :writer,
|
126
|
+
:lazy, :default, :nullable, :key, :serial, :column, :size, :length,
|
127
|
+
:index, :check
|
128
|
+
]
|
129
|
+
|
130
|
+
# Adds property accessors for a field that you'd like to be able to modify. The DataMapper doesn't
|
131
|
+
# use the table schema to infer accessors, you must explicity call #property to add field accessors
|
132
|
+
# to your model.
|
133
|
+
#
|
134
|
+
# EXAMPLE:
|
135
|
+
# class CellProvider
|
136
|
+
# property :name, :string
|
137
|
+
# property :rating, :integer
|
138
|
+
# end
|
139
|
+
#
|
140
|
+
# att = CellProvider.new(:name => 'AT&T')
|
141
|
+
# att.rating = 3
|
142
|
+
# puts att.name, att.rating
|
143
|
+
#
|
144
|
+
# => AT&T
|
145
|
+
# => 3
|
146
|
+
#
|
147
|
+
# OPTIONS:
|
148
|
+
# * <tt>lazy</tt>: Lazy load the specified property (:lazy => true). False by default.
|
149
|
+
# * <tt>accessor</tt>: Set method visibility for the property accessors. Affects both
|
150
|
+
# reader and writer. Allowable values are :public, :protected, :private. Defaults to
|
151
|
+
# :public
|
152
|
+
# * <tt>reader</tt>: Like the accessor option but affects only the property reader.
|
153
|
+
# * <tt>writer</tt>: Like the accessor option but affects only the property writer.
|
154
|
+
# * <tt>protected</tt>: Alias for :reader => :public, :writer => :protected
|
155
|
+
# * <tt>private</tt>: Alias for :reader => :public, :writer => :private
|
156
|
+
def property(name, type, options = {})
|
157
|
+
|
158
|
+
options.each_pair do |k,v|
|
159
|
+
raise ArgumentError.new("#{k.inspect} is not a supported option in DataMapper::Base::PROPERTY_OPTIONS") unless PROPERTY_OPTIONS.include?(k)
|
160
|
+
end
|
161
|
+
|
162
|
+
visibility_options = [:public, :protected, :private]
|
163
|
+
reader_visibility = options[:reader] || options[:accessor] || :public
|
164
|
+
writer_visibility = options[:writer] || options[:accessor] || :public
|
165
|
+
writer_visibility = :protected if options[:protected]
|
166
|
+
writer_visibility = :private if options[:private]
|
167
|
+
|
168
|
+
raise(ArgumentError.new, "property visibility must be :public, :protected, or :private") unless visibility_options.include?(reader_visibility) && visibility_options.include?(writer_visibility)
|
169
|
+
|
170
|
+
mapping = database.schema[self].add_column(name.to_s.sub(/\?$/, '').to_sym, type, options)
|
171
|
+
|
172
|
+
property_getter(mapping, reader_visibility)
|
173
|
+
property_setter(mapping, writer_visibility)
|
174
|
+
|
175
|
+
if MAGIC_PROPERTIES.has_key?(name)
|
176
|
+
class_eval(&MAGIC_PROPERTIES[name])
|
177
|
+
end
|
178
|
+
|
179
|
+
return name
|
180
|
+
end
|
181
|
+
|
182
|
+
MAGIC_PROPERTIES = {
|
183
|
+
:updated_at => lambda { before_save { |x| x.updated_at = Time::now } },
|
184
|
+
:updated_on => lambda { before_save { |x| x.updated_on = Date::today } },
|
185
|
+
:created_at => lambda { before_create { |x| x.created_at = Time::now } },
|
186
|
+
:created_on => lambda { before_create { |x| x.created_on = Date::today } }
|
187
|
+
}
|
188
|
+
|
189
|
+
def property_getter(mapping, visibility = :public)
|
190
|
+
if mapping.lazy?
|
191
|
+
class_eval <<-EOS
|
192
|
+
#{visibility.to_s}
|
193
|
+
def #{mapping.name}
|
194
|
+
lazy_load!(#{mapping.name.inspect})
|
195
|
+
class << self;
|
196
|
+
attr_accessor #{mapping.name.inspect}
|
197
|
+
end
|
198
|
+
@#{mapping.name}
|
199
|
+
end
|
200
|
+
EOS
|
201
|
+
else
|
202
|
+
class_eval("#{visibility.to_s}; def #{mapping.name}; #{mapping.instance_variable_name} end") unless [ :public, :private, :protected ].include?(mapping.name)
|
203
|
+
end
|
204
|
+
|
205
|
+
if mapping.type == :boolean
|
206
|
+
class_eval("#{visibility.to_s}; def #{mapping.name.to_s.ensure_ends_with('?')}; #{mapping.instance_variable_name} end")
|
207
|
+
end
|
208
|
+
|
209
|
+
rescue SyntaxError
|
210
|
+
raise SyntaxError.new(mapping)
|
211
|
+
end
|
212
|
+
|
213
|
+
def property_setter(mapping, visibility = :public)
|
214
|
+
if mapping.lazy?
|
215
|
+
class_eval <<-EOS
|
216
|
+
#{visibility.to_s}
|
217
|
+
def #{mapping.name}=(value)
|
218
|
+
class << self;
|
219
|
+
attr_accessor #{mapping.name.inspect}
|
220
|
+
end
|
221
|
+
@#{mapping.name} = value
|
222
|
+
end
|
223
|
+
EOS
|
224
|
+
else
|
225
|
+
class_eval("#{visibility.to_s}; def #{mapping.name}=(value); #{mapping.instance_variable_name} = value end")
|
226
|
+
end
|
227
|
+
rescue SyntaxError
|
228
|
+
raise SyntaxError.new(mapping)
|
229
|
+
end
|
230
|
+
|
231
|
+
# Allows you to override the table name for a model.
|
232
|
+
# EXAMPLE:
|
233
|
+
# class WorkItem
|
234
|
+
# set_table_name 't_work_item_list'
|
235
|
+
# end
|
236
|
+
def set_table_name(value)
|
237
|
+
database.table(self).name = value
|
238
|
+
end
|
239
|
+
# An embedded value maps the values of an object to fields in the record of the object's owner.
|
240
|
+
# #embed takes a symbol to define the embedded class, options, and an optional block. See
|
241
|
+
# examples for use cases.
|
242
|
+
#
|
243
|
+
# EXAMPLE:
|
244
|
+
# class CellPhone < DataMapper::Base
|
245
|
+
# property :number, :string
|
246
|
+
#
|
247
|
+
# embed :owner, :prefix => true do
|
248
|
+
# property :name, :string
|
249
|
+
# property :address, :string
|
250
|
+
# end
|
251
|
+
# end
|
252
|
+
#
|
253
|
+
# my_phone = CellPhone.new
|
254
|
+
# my_phone.owner.name = "Nick"
|
255
|
+
# puts my_phone.owner.name
|
256
|
+
#
|
257
|
+
# => Nick
|
258
|
+
#
|
259
|
+
# OPTIONS:
|
260
|
+
# * <tt>prefix</tt>: define a column prefix, so instead of mapping :address to an 'address'
|
261
|
+
# column, it would map to 'owner_address' in the example above. If :prefix => true is
|
262
|
+
# specified, the prefix will be the name of the symbol given as the first parameter. If the
|
263
|
+
# prefix is a string the specified string will be used for the prefix.
|
264
|
+
# * <tt>lazy</tt>: lazy-load all embedded values at the same time. :lazy => true to enable.
|
265
|
+
# Disabled (false) by default.
|
266
|
+
# * <tt>accessor</tt>: Set method visibility for all embedded properties. Affects both
|
267
|
+
# reader and writer. Allowable values are :public, :protected, :private. Defaults to :public
|
268
|
+
# * <tt>reader</tt>: Like the accessor option but affects only embedded property readers.
|
269
|
+
# * <tt>writer</tt>: Like the accessor option but affects only embedded property writers.
|
270
|
+
# * <tt>protected</tt>: Alias for :reader => :public, :writer => :protected
|
271
|
+
# * <tt>private</tt>: Alias for :reader => :public, :writer => :private
|
272
|
+
#
|
273
|
+
def embed(name, options = {}, &block)
|
274
|
+
EmbeddedValue::define(self, name, options, &block)
|
275
|
+
end
|
276
|
+
|
277
|
+
def properties
|
278
|
+
@properties
|
279
|
+
end
|
280
|
+
|
281
|
+
# Creates a composite index for an arbitrary number of database columns. Note that
|
282
|
+
# it also is possible to specify single indexes directly for each property.
|
283
|
+
#
|
284
|
+
# === EXAMPLE WITH COMPOSITE INDEX:
|
285
|
+
# class Person < DataMapper::Base
|
286
|
+
# property :server_id, :integer
|
287
|
+
# property :name, :string
|
288
|
+
#
|
289
|
+
# index [:server_id, :name]
|
290
|
+
# end
|
291
|
+
#
|
292
|
+
# === EXAMPLE WITH COMPOSITE UNIQUE INDEX:
|
293
|
+
# class Person < DataMapper::Base
|
294
|
+
# property :server_id, :integer
|
295
|
+
# property :name, :string
|
296
|
+
#
|
297
|
+
# index [:server_id, :name], :unique => true
|
298
|
+
# end
|
299
|
+
#
|
300
|
+
# === SINGLE INDEX EXAMPLES:
|
301
|
+
# * property :name, :index => true
|
302
|
+
# * property :name, :index => :unique
|
303
|
+
def index(indexes, unique = false)
|
304
|
+
if indexes.kind_of?(Array) # if given an index of multiple columns
|
305
|
+
database.schema[self].add_composite_index(indexes, unique)
|
306
|
+
else
|
307
|
+
raise ArgumentError.new("You must supply an array for the composite index")
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
end
|
312
|
+
|
313
|
+
# Lazy-loads the attributes for a loaded_set, then overwrites the accessors
|
314
|
+
# for the named methods so that the lazy_loading is skipped the second time.
|
315
|
+
def lazy_load!(*names)
|
316
|
+
|
317
|
+
names = names.map { |name| name.to_sym }.reject { |name| lazy_loaded_attributes.include?(name) }
|
318
|
+
|
319
|
+
reset_attribute = lambda do |instance|
|
320
|
+
singleton_class = (class << instance; self end)
|
321
|
+
names.each do |name|
|
322
|
+
instance.lazy_loaded_attributes << name
|
323
|
+
singleton_class.send(:attr_accessor, name)
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
unless names.empty? || new_record? || loaded_set.nil?
|
328
|
+
|
329
|
+
key = database_context.table(self.class).key.to_sym
|
330
|
+
keys_to_select = loaded_set.map do |instance|
|
331
|
+
instance.send(key)
|
332
|
+
end
|
333
|
+
|
334
|
+
database_context.all(
|
335
|
+
self.class,
|
336
|
+
:select => ([key] + names),
|
337
|
+
:reload => true,
|
338
|
+
key => keys_to_select
|
339
|
+
).each(&reset_attribute)
|
340
|
+
else
|
341
|
+
reset_attribute[self]
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
# Mass-assign mapped fields.
|
346
|
+
def attributes=(values_hash)
|
347
|
+
table = database_context.table(self.class)
|
348
|
+
|
349
|
+
values_hash.delete_if do |key, value|
|
350
|
+
!self.class.public_method_defined?("#{key}=")
|
351
|
+
end.each_pair do |key, value|
|
352
|
+
if respond_to?(key)
|
353
|
+
send("#{key}=", value)
|
354
|
+
elsif column = table[key]
|
355
|
+
instance_variable_set(column.instance_variable_name, value)
|
356
|
+
end
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
def database_context
|
361
|
+
@database_context || ( @database_context = database )
|
362
|
+
end
|
363
|
+
|
364
|
+
def database_context=(value)
|
365
|
+
@database_context = value
|
366
|
+
end
|
367
|
+
|
368
|
+
def logger
|
369
|
+
self.class.logger
|
370
|
+
end
|
371
|
+
|
372
|
+
def new_record?
|
373
|
+
@new_record.nil? || @new_record
|
374
|
+
end
|
375
|
+
|
376
|
+
def ==(other)
|
377
|
+
other.is_a?(self.class) && private_attributes == other.send(:private_attributes)
|
378
|
+
end
|
379
|
+
|
380
|
+
# Returns the difference between two objects, in terms of their attributes.
|
381
|
+
def ^(other)
|
382
|
+
results = {}
|
383
|
+
|
384
|
+
self_attributes, other_attributes = attributes, other.attributes
|
385
|
+
|
386
|
+
self_attributes.each_pair do |k,v|
|
387
|
+
other_value = other_attributes[k]
|
388
|
+
unless v == other_value
|
389
|
+
results[k] = [v, other_value]
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
results
|
394
|
+
end
|
395
|
+
|
396
|
+
def lazy_loaded_attributes
|
397
|
+
@lazy_loaded_attributes || @lazy_loaded_attributes = Set.new
|
398
|
+
end
|
399
|
+
|
400
|
+
def loaded_attributes
|
401
|
+
pairs = {}
|
402
|
+
|
403
|
+
database_context.table(self).columns.each do |column|
|
404
|
+
pairs[column.name] = instance_variable_get(column.instance_variable_name)
|
405
|
+
end
|
406
|
+
|
407
|
+
pairs
|
408
|
+
end
|
409
|
+
|
410
|
+
def update_attributes(update_hash)
|
411
|
+
self.attributes = update_hash
|
412
|
+
self.save
|
413
|
+
end
|
414
|
+
|
415
|
+
def attributes
|
416
|
+
pairs = {}
|
417
|
+
|
418
|
+
database_context.table(self).columns.each do |column|
|
419
|
+
if self.class.public_method_defined?(column.name)
|
420
|
+
lazy_load!(column.name) if column.lazy?
|
421
|
+
value = instance_variable_get(column.instance_variable_name)
|
422
|
+
pairs[column.name] = column.type == :class ? value.to_s : value
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
pairs
|
427
|
+
end
|
428
|
+
|
429
|
+
def unsafe_attributes=(values_hash)
|
430
|
+
table = database_context.table(self.class)
|
431
|
+
|
432
|
+
values_hash.each_pair do |key, value|
|
433
|
+
if respond_to?(key)
|
434
|
+
send("#{key}=", value)
|
435
|
+
elsif column = table[key]
|
436
|
+
instance_variable_set(column.instance_variable_name, value)
|
437
|
+
end
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
def dirty?
|
442
|
+
result = database_context.table(self).columns.any? do |column|
|
443
|
+
if column.type == :object
|
444
|
+
Marshal.dump(self.instance_variable_get(column.instance_variable_name)) != original_values[column.name]
|
445
|
+
else
|
446
|
+
self.instance_variable_get(column.instance_variable_name) != original_values[column.name]
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
return true if result
|
451
|
+
|
452
|
+
loaded_associations.any? do |loaded_association|
|
453
|
+
loaded_association.dirty?
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
def dirty_attributes
|
458
|
+
pairs = {}
|
459
|
+
|
460
|
+
database_context.table(self).columns.each do |column|
|
461
|
+
value = instance_variable_get(column.instance_variable_name)
|
462
|
+
if value != original_values[column.name] && (!new_record? || !column.serial?)
|
463
|
+
pairs[column.name] = column.type != :object ? value : YAML.dump(value)
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
pairs
|
468
|
+
end
|
469
|
+
|
470
|
+
def original_values=(values)
|
471
|
+
values.each_pair do |k,v|
|
472
|
+
original_values[k] = case v
|
473
|
+
when String, Date, Time then v.dup
|
474
|
+
# when column.type == :object then Marshal.dump(v)
|
475
|
+
else v
|
476
|
+
end
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
480
|
+
def original_values
|
481
|
+
class << self
|
482
|
+
attr_reader :original_values
|
483
|
+
end
|
484
|
+
|
485
|
+
@original_values = {}
|
486
|
+
end
|
487
|
+
|
488
|
+
def loaded_set=(value)
|
489
|
+
value << self
|
490
|
+
@loaded_set = value
|
491
|
+
end
|
492
|
+
|
493
|
+
def inspect
|
494
|
+
inspected_attributes = attributes.map { |k,v| "@#{k}=#{v.inspect}" }
|
495
|
+
|
496
|
+
instance_variables.each do |name|
|
497
|
+
if instance_variable_get(name).kind_of?(Associations::HasManyAssociation)
|
498
|
+
inspected_attributes << "#{name}=#{instance_variable_get(name).inspect}"
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
502
|
+
"#<%s:0x%x @new_record=%s, %s>" % [self.class.name, (object_id * 2), new_record?, inspected_attributes.join(', ')]
|
503
|
+
end
|
504
|
+
|
505
|
+
def loaded_associations
|
506
|
+
@loaded_associations || @loaded_associations = []
|
507
|
+
end
|
508
|
+
|
509
|
+
def key=(value)
|
510
|
+
key_column = database_context.table(self.class).key
|
511
|
+
@__key = key_column.type_cast_value(value)
|
512
|
+
instance_variable_set(key_column.instance_variable_name, @__key)
|
513
|
+
end
|
514
|
+
|
515
|
+
def key
|
516
|
+
@__key || @__key = begin
|
517
|
+
key_column = database_context.table(self.class).key
|
518
|
+
key_column.type_cast_value(instance_variable_get(key_column.instance_variable_name))
|
519
|
+
end
|
520
|
+
end
|
521
|
+
|
522
|
+
private
|
523
|
+
|
524
|
+
# return all attributes, regardless of their visibility
|
525
|
+
def private_attributes
|
526
|
+
pairs = {}
|
527
|
+
|
528
|
+
database_context.table(self).columns.each do |column|
|
529
|
+
lazy_load!(column.name) if column.lazy?
|
530
|
+
value = instance_variable_get(column.instance_variable_name)
|
531
|
+
pairs[column.name] = column.type == :class ? value.to_s : value
|
532
|
+
end
|
533
|
+
|
534
|
+
pairs
|
535
|
+
end
|
536
|
+
|
537
|
+
end
|
538
|
+
end
|