sam-dm-core 0.9.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (126) hide show
  1. data/.autotest +26 -0
  2. data/CONTRIBUTING +51 -0
  3. data/FAQ +92 -0
  4. data/History.txt +145 -0
  5. data/MIT-LICENSE +22 -0
  6. data/Manifest.txt +125 -0
  7. data/QUICKLINKS +12 -0
  8. data/README.txt +143 -0
  9. data/Rakefile +30 -0
  10. data/SPECS +63 -0
  11. data/TODO +1 -0
  12. data/lib/dm-core.rb +224 -0
  13. data/lib/dm-core/adapters.rb +4 -0
  14. data/lib/dm-core/adapters/abstract_adapter.rb +202 -0
  15. data/lib/dm-core/adapters/data_objects_adapter.rb +707 -0
  16. data/lib/dm-core/adapters/mysql_adapter.rb +136 -0
  17. data/lib/dm-core/adapters/postgres_adapter.rb +188 -0
  18. data/lib/dm-core/adapters/sqlite3_adapter.rb +105 -0
  19. data/lib/dm-core/associations.rb +199 -0
  20. data/lib/dm-core/associations/many_to_many.rb +147 -0
  21. data/lib/dm-core/associations/many_to_one.rb +107 -0
  22. data/lib/dm-core/associations/one_to_many.rb +309 -0
  23. data/lib/dm-core/associations/one_to_one.rb +61 -0
  24. data/lib/dm-core/associations/relationship.rb +218 -0
  25. data/lib/dm-core/associations/relationship_chain.rb +81 -0
  26. data/lib/dm-core/auto_migrations.rb +113 -0
  27. data/lib/dm-core/collection.rb +638 -0
  28. data/lib/dm-core/dependency_queue.rb +31 -0
  29. data/lib/dm-core/hook.rb +11 -0
  30. data/lib/dm-core/identity_map.rb +45 -0
  31. data/lib/dm-core/is.rb +16 -0
  32. data/lib/dm-core/logger.rb +232 -0
  33. data/lib/dm-core/migrations/destructive_migrations.rb +17 -0
  34. data/lib/dm-core/migrator.rb +29 -0
  35. data/lib/dm-core/model.rb +471 -0
  36. data/lib/dm-core/naming_conventions.rb +84 -0
  37. data/lib/dm-core/property.rb +673 -0
  38. data/lib/dm-core/property_set.rb +162 -0
  39. data/lib/dm-core/query.rb +625 -0
  40. data/lib/dm-core/repository.rb +159 -0
  41. data/lib/dm-core/resource.rb +637 -0
  42. data/lib/dm-core/scope.rb +58 -0
  43. data/lib/dm-core/support.rb +7 -0
  44. data/lib/dm-core/support/array.rb +13 -0
  45. data/lib/dm-core/support/assertions.rb +8 -0
  46. data/lib/dm-core/support/errors.rb +23 -0
  47. data/lib/dm-core/support/kernel.rb +7 -0
  48. data/lib/dm-core/support/symbol.rb +41 -0
  49. data/lib/dm-core/transaction.rb +267 -0
  50. data/lib/dm-core/type.rb +160 -0
  51. data/lib/dm-core/type_map.rb +80 -0
  52. data/lib/dm-core/types.rb +19 -0
  53. data/lib/dm-core/types/boolean.rb +7 -0
  54. data/lib/dm-core/types/discriminator.rb +34 -0
  55. data/lib/dm-core/types/object.rb +24 -0
  56. data/lib/dm-core/types/paranoid_boolean.rb +34 -0
  57. data/lib/dm-core/types/paranoid_datetime.rb +33 -0
  58. data/lib/dm-core/types/serial.rb +9 -0
  59. data/lib/dm-core/types/text.rb +10 -0
  60. data/lib/dm-core/version.rb +3 -0
  61. data/script/all +5 -0
  62. data/script/performance.rb +203 -0
  63. data/script/profile.rb +87 -0
  64. data/spec/integration/association_spec.rb +1371 -0
  65. data/spec/integration/association_through_spec.rb +203 -0
  66. data/spec/integration/associations/many_to_many_spec.rb +449 -0
  67. data/spec/integration/associations/many_to_one_spec.rb +163 -0
  68. data/spec/integration/associations/one_to_many_spec.rb +151 -0
  69. data/spec/integration/auto_migrations_spec.rb +398 -0
  70. data/spec/integration/collection_spec.rb +1069 -0
  71. data/spec/integration/data_objects_adapter_spec.rb +32 -0
  72. data/spec/integration/dependency_queue_spec.rb +58 -0
  73. data/spec/integration/model_spec.rb +127 -0
  74. data/spec/integration/mysql_adapter_spec.rb +85 -0
  75. data/spec/integration/postgres_adapter_spec.rb +731 -0
  76. data/spec/integration/property_spec.rb +233 -0
  77. data/spec/integration/query_spec.rb +506 -0
  78. data/spec/integration/repository_spec.rb +57 -0
  79. data/spec/integration/resource_spec.rb +475 -0
  80. data/spec/integration/sqlite3_adapter_spec.rb +352 -0
  81. data/spec/integration/sti_spec.rb +208 -0
  82. data/spec/integration/strategic_eager_loading_spec.rb +138 -0
  83. data/spec/integration/transaction_spec.rb +75 -0
  84. data/spec/integration/type_spec.rb +271 -0
  85. data/spec/lib/logging_helper.rb +18 -0
  86. data/spec/lib/mock_adapter.rb +27 -0
  87. data/spec/lib/model_loader.rb +91 -0
  88. data/spec/lib/publicize_methods.rb +28 -0
  89. data/spec/models/vehicles.rb +34 -0
  90. data/spec/models/zoo.rb +47 -0
  91. data/spec/spec.opts +3 -0
  92. data/spec/spec_helper.rb +86 -0
  93. data/spec/unit/adapters/abstract_adapter_spec.rb +133 -0
  94. data/spec/unit/adapters/adapter_shared_spec.rb +15 -0
  95. data/spec/unit/adapters/data_objects_adapter_spec.rb +628 -0
  96. data/spec/unit/adapters/postgres_adapter_spec.rb +133 -0
  97. data/spec/unit/associations/many_to_many_spec.rb +17 -0
  98. data/spec/unit/associations/many_to_one_spec.rb +152 -0
  99. data/spec/unit/associations/one_to_many_spec.rb +393 -0
  100. data/spec/unit/associations/one_to_one_spec.rb +7 -0
  101. data/spec/unit/associations/relationship_spec.rb +71 -0
  102. data/spec/unit/associations_spec.rb +242 -0
  103. data/spec/unit/auto_migrations_spec.rb +111 -0
  104. data/spec/unit/collection_spec.rb +182 -0
  105. data/spec/unit/data_mapper_spec.rb +35 -0
  106. data/spec/unit/identity_map_spec.rb +126 -0
  107. data/spec/unit/is_spec.rb +80 -0
  108. data/spec/unit/migrator_spec.rb +33 -0
  109. data/spec/unit/model_spec.rb +339 -0
  110. data/spec/unit/naming_conventions_spec.rb +36 -0
  111. data/spec/unit/property_set_spec.rb +83 -0
  112. data/spec/unit/property_spec.rb +753 -0
  113. data/spec/unit/query_spec.rb +530 -0
  114. data/spec/unit/repository_spec.rb +93 -0
  115. data/spec/unit/resource_spec.rb +626 -0
  116. data/spec/unit/scope_spec.rb +142 -0
  117. data/spec/unit/transaction_spec.rb +493 -0
  118. data/spec/unit/type_map_spec.rb +114 -0
  119. data/spec/unit/type_spec.rb +119 -0
  120. data/tasks/ci.rb +68 -0
  121. data/tasks/dm.rb +63 -0
  122. data/tasks/doc.rb +20 -0
  123. data/tasks/gemspec.rb +23 -0
  124. data/tasks/hoe.rb +46 -0
  125. data/tasks/install.rb +20 -0
  126. metadata +216 -0
@@ -0,0 +1,159 @@
1
+ module DataMapper
2
+ class Repository
3
+ include Assertions
4
+
5
+ @adapters = {}
6
+
7
+ ##
8
+ #
9
+ # @return <Adapter> the adapters registered for this repository
10
+ def self.adapters
11
+ @adapters
12
+ end
13
+
14
+ def self.context
15
+ Thread.current[:dm_repository_contexts] ||= []
16
+ end
17
+
18
+ def self.default_name
19
+ :default
20
+ end
21
+
22
+ attr_reader :name
23
+
24
+ def adapter
25
+ # Make adapter instantiation lazy so we can defer repository setup until it's actually
26
+ # needed. Do not remove this code.
27
+ @adapter ||= begin
28
+ raise ArgumentError, "Adapter not set: #{@name}. Did you forget to setup?" \
29
+ unless self.class.adapters.has_key?(@name)
30
+
31
+ self.class.adapters[@name]
32
+ end
33
+ end
34
+
35
+ def identity_map(model)
36
+ @identity_maps[model]
37
+ end
38
+
39
+ # TODO: spec this
40
+ def scope(&block)
41
+ Repository.context << self
42
+
43
+ begin
44
+ return yield(self)
45
+ ensure
46
+ Repository.context.pop
47
+ end
48
+ end
49
+
50
+ def create(resources)
51
+ adapter.create(resources)
52
+ end
53
+
54
+ ##
55
+ # retrieve a collection of results of a query
56
+ #
57
+ # @param <Query> query composition of the query to perform
58
+ # @return <DataMapper::Collection> result set of the query
59
+ # @see DataMapper::Query
60
+ def read_many(query)
61
+ adapter.read_many(query)
62
+ end
63
+
64
+ ##
65
+ # retrieve a resource instance by a query
66
+ #
67
+ # @param <Query> query composition of the query to perform
68
+ # @return <DataMapper::Resource> the first retrieved instance which matches the query
69
+ # @return <NilClass> no object could be found which matches that query
70
+ # @see DataMapper::Query
71
+ def read_one(query)
72
+ adapter.read_one(query)
73
+ end
74
+
75
+ def update(attributes, query)
76
+ adapter.update(attributes, query)
77
+ end
78
+
79
+ def delete(query)
80
+ adapter.delete(query)
81
+ end
82
+
83
+ def eql?(other)
84
+ return true if super
85
+ name == other.name
86
+ end
87
+
88
+ alias == eql?
89
+
90
+ def to_s
91
+ "#<DataMapper::Repository:#{@name}>"
92
+ end
93
+
94
+ private
95
+
96
+ def initialize(name)
97
+ assert_kind_of 'name', name, Symbol
98
+
99
+ @name = name
100
+ @identity_maps = Hash.new { |h,model| h[model] = IdentityMap.new }
101
+ end
102
+
103
+ # TODO: move to dm-more/dm-migrations
104
+ module Migration
105
+ # TODO: move to dm-more/dm-migrations
106
+ def map(*args)
107
+ type_map.map(*args)
108
+ end
109
+
110
+ # TODO: move to dm-more/dm-migrations
111
+ def type_map
112
+ @type_map ||= TypeMap.new(adapter.class.type_map)
113
+ end
114
+
115
+ ##
116
+ #
117
+ # @return <True, False> whether or not the data-store exists for this repo
118
+ #
119
+ # TODO: move to dm-more/dm-migrations
120
+ def storage_exists?(storage_name)
121
+ adapter.storage_exists?(storage_name)
122
+ end
123
+
124
+ # TODO: move to dm-more/dm-migrations
125
+ def migrate!
126
+ Migrator.migrate(name)
127
+ end
128
+
129
+ # TODO: move to dm-more/dm-migrations
130
+ def auto_migrate!
131
+ AutoMigrator.auto_migrate(name)
132
+ end
133
+
134
+ # TODO: move to dm-more/dm-migrations
135
+ def auto_upgrade!
136
+ AutoMigrator.auto_upgrade(name)
137
+ end
138
+ end
139
+
140
+ include Migration
141
+
142
+ # TODO: move to dm-more/dm-transactions
143
+ module Transaction
144
+ ##
145
+ # Produce a new Transaction for this Repository
146
+ #
147
+ #
148
+ # @return <DataMapper::Adapters::Transaction> a new Transaction (in state
149
+ # :none) that can be used to execute code #with_transaction
150
+ #
151
+ # TODO: move to dm-more/dm-transactions
152
+ def transaction
153
+ DataMapper::Transaction.new(self)
154
+ end
155
+ end
156
+
157
+ include Transaction
158
+ end # class Repository
159
+ end # module DataMapper
@@ -0,0 +1,637 @@
1
+ require 'set'
2
+
3
+ module DataMapper
4
+ module Resource
5
+ include Assertions
6
+
7
+ ##
8
+ #
9
+ # Appends a module for inclusion into the model class after
10
+ # DataMapper::Resource.
11
+ #
12
+ # This is a useful way to extend DataMapper::Resource while still retaining
13
+ # a self.included method.
14
+ #
15
+ # @param [Module] inclusion the module that is to be appended to the module
16
+ # after DataMapper::Resource
17
+ #
18
+ # @return [TrueClass, FalseClass] whether or not the inclusions have been
19
+ # successfully appended to the list
20
+ # @return <TrueClass, FalseClass>
21
+ #-
22
+ # @api public
23
+ def self.append_inclusions(*inclusions)
24
+ extra_inclusions.concat inclusions
25
+ true
26
+ end
27
+
28
+ def self.extra_inclusions
29
+ @extra_inclusions ||= []
30
+ end
31
+
32
+ # When Resource is included in a class this method makes sure
33
+ # it gets all the methods
34
+ #
35
+ # -
36
+ # @api private
37
+ def self.included(model)
38
+ model.extend Model
39
+ model.extend ClassMethods if defined?(ClassMethods)
40
+ model.const_set('Resource', self) unless model.const_defined?('Resource')
41
+ extra_inclusions.each { |inclusion| model.send(:include, inclusion) }
42
+ descendants << model
43
+ class << model
44
+ @_valid_model = false
45
+ attr_reader :_valid_model
46
+ end
47
+ end
48
+
49
+ # Return all classes that include the DataMapper::Resource module
50
+ #
51
+ # ==== Returns
52
+ # Set:: a set containing the including classes
53
+ #
54
+ # ==== Example
55
+ #
56
+ # Class Foo
57
+ # include DataMapper::Resource
58
+ # end
59
+ #
60
+ # DataMapper::Resource.descendants.to_a.first == Foo
61
+ #
62
+ # -
63
+ # @api semipublic
64
+ def self.descendants
65
+ @descendants ||= Set.new
66
+ end
67
+
68
+ # +---------------
69
+ # Instance methods
70
+
71
+ attr_writer :collection
72
+
73
+ alias model class
74
+
75
+ # returns the value of the attribute. Do not read from instance variables directly,
76
+ # but use this method. This method handels the lazy loading the attribute and returning
77
+ # of defaults if nessesary.
78
+ #
79
+ # ==== Parameters
80
+ # name<Symbol>:: name attribute to lookup
81
+ #
82
+ # ==== Returns
83
+ # <Types>:: the value stored at that given attribute, nil if none, and default if necessary
84
+ #
85
+ # ==== Example
86
+ #
87
+ # Class Foo
88
+ # include DataMapper::Resource
89
+ #
90
+ # property :first_name, String
91
+ # property :last_name, String
92
+ #
93
+ # def full_name
94
+ # "#{attribute_get(:first_name)} #{attribute_get(:last_name)}"
95
+ # end
96
+ #
97
+ # # using the shorter syntax
98
+ # def name_for_address_book
99
+ # "#{last_name}, #{first_name}"
100
+ # end
101
+ # end
102
+ #
103
+ # -
104
+ # @api semipublic
105
+ def attribute_get(name)
106
+ properties[name].get(self)
107
+ end
108
+
109
+ # sets the value of the attribute and marks the attribute as dirty
110
+ # if it has been changed so that it may be saved. Do not set from
111
+ # instance variables directly, but use this method. This method
112
+ # handels the lazy loading the property and returning of defaults
113
+ # if nessesary.
114
+ #
115
+ # ==== Parameters
116
+ # name<Symbol>:: name attribute to set
117
+ # value<Type>:: value to store at that location
118
+ #
119
+ # ==== Returns
120
+ # <Types>:: the value stored at that given attribute, nil if none, and default if necessary
121
+ #
122
+ # ==== Example
123
+ #
124
+ # Class Foo
125
+ # include DataMapper::Resource
126
+ #
127
+ # property :first_name, String
128
+ # property :last_name, String
129
+ #
130
+ # def full_name(name)
131
+ # name = name.split(' ')
132
+ # attribute_set(:first_name, name[0])
133
+ # attribute_set(:last_name, name[1])
134
+ # end
135
+ #
136
+ # # using the shorter syntax
137
+ # def name_from_address_book(name)
138
+ # name = name.split(', ')
139
+ # first_name = name[1]
140
+ # last_name = name[0]
141
+ # end
142
+ # end
143
+ #
144
+ # -
145
+ # @api semipublic
146
+ def attribute_set(name, value)
147
+ properties[name].set(self, value)
148
+ end
149
+
150
+ # Compares if its the same object or if attributes are equal
151
+ #
152
+ # ==== Parameters
153
+ # other<Object>:: Object to compare to
154
+ #
155
+ # ==== Returns
156
+ # <True>:: the outcome of the comparison as a boolean
157
+ #
158
+ # -
159
+ # @api public
160
+ def eql?(other)
161
+ return true if object_id == other.object_id
162
+ return false unless other.kind_of?(model)
163
+ return true if repository == other.repository && key == other.key
164
+
165
+ properties.each do |property|
166
+ return false if property.get!(self) != property.get!(other)
167
+ end
168
+
169
+ true
170
+ end
171
+
172
+ alias == eql?
173
+
174
+ # Computes a hash for the resource
175
+ #
176
+ # ==== Returns
177
+ # <Integer>:: the hash value of the resource
178
+ #
179
+ # -
180
+ # @api public
181
+ def hash
182
+ model.hash + key.hash
183
+ end
184
+
185
+ # Inspection of the class name and the attributes
186
+ #
187
+ # ==== Returns
188
+ # <String>:: with the class name, attributes with their values
189
+ #
190
+ # ==== Example
191
+ #
192
+ # >> Foo.new
193
+ # => #<Foo name=nil updated_at=nil created_at=nil id=nil>
194
+ #
195
+ # -
196
+ # @api public
197
+ def inspect
198
+ attrs = []
199
+
200
+ properties.each do |property|
201
+ value = if property.lazy? && !attribute_loaded?(property.name) && !new_record?
202
+ '<not loaded>'
203
+ else
204
+ send(property.getter).inspect
205
+ end
206
+
207
+ attrs << "#{property.name}=#{value}"
208
+ end
209
+
210
+ "#<#{model.name} #{attrs * ' '}>"
211
+ end
212
+
213
+ # TODO docs
214
+ def pretty_print(pp)
215
+ pp.group(1, "#<#{model.name}", ">") do
216
+ pp.breakable
217
+ pp.seplist(attributes.to_a) do |k_v|
218
+ pp.text k_v[0].to_s
219
+ pp.text " = "
220
+ pp.pp k_v[1]
221
+ end
222
+ end
223
+ end
224
+
225
+ ##
226
+ #
227
+ # ==== Returns
228
+ # <Repository>:: the respository this resource belongs to in the context of a collection OR in the class's context
229
+ #
230
+ # @api public
231
+ def repository
232
+ @repository || model.repository
233
+ end
234
+
235
+ # default id method to return the resource id when there is a
236
+ # single key, and the model was defined with a primary key named
237
+ # something other than id
238
+ #
239
+ # ==== Returns
240
+ # <Array[Key], Key> key or keys
241
+ #
242
+ # --
243
+ # @api public
244
+ def id
245
+ key = self.key
246
+ key.first if key.size == 1
247
+ end
248
+
249
+ def key
250
+ key_properties.map do |property|
251
+ original_values[property.name] || property.get!(self)
252
+ end
253
+ end
254
+
255
+ def readonly!
256
+ @readonly = true
257
+ end
258
+
259
+ def readonly?
260
+ @readonly == true
261
+ end
262
+
263
+ # save the instance to the data-store
264
+ #
265
+ # ==== Returns
266
+ # <True, False>:: results of the save
267
+ #
268
+ # @see DataMapper::Repository#save
269
+ #
270
+ # --
271
+ # #public
272
+ def save(context = :default)
273
+ # Takes a context, but does nothing with it. This is to maintain the
274
+ # same API through out all of dm-more. dm-validations requires a
275
+ # context to be passed
276
+
277
+ associations_saved = false
278
+ child_associations.each { |a| associations_saved |= a.save }
279
+
280
+ saved = if dirty? || (new_record? && key_properties.any? { |p| p.serial? })
281
+ new_record? ? create : update
282
+ end
283
+
284
+ if saved
285
+ original_values.clear
286
+ end
287
+
288
+ parent_associations.each { |a| associations_saved |= a.save }
289
+
290
+ # We should return true if the model (or any of its associations)
291
+ # were saved.
292
+ (saved | associations_saved) == true
293
+ end
294
+
295
+ # destroy the instance, remove it from the repository
296
+ #
297
+ # ==== Returns
298
+ # <True, False>:: results of the destruction
299
+ #
300
+ # --
301
+ # @api public
302
+ def destroy
303
+ return false if new_record?
304
+ return false unless repository.delete(to_query)
305
+
306
+ @new_record = true
307
+ repository.identity_map(model).delete(key)
308
+ original_values.clear
309
+
310
+ properties.each do |property|
311
+ # We'll set the original value to nil as if we had a new record
312
+ original_values[property.name] = nil if attribute_loaded?(property.name)
313
+ end
314
+
315
+ true
316
+ end
317
+
318
+ # Checks if the attribute has been loaded
319
+ #
320
+ # ==== Example
321
+ #
322
+ # class Foo
323
+ # include DataMapper::Resource
324
+ # property :name, String
325
+ # property :description, Text, :lazy => false
326
+ # end
327
+ #
328
+ # Foo.new.attribute_loaded?(:description) # will return false
329
+ #
330
+ # --
331
+ # @api public
332
+ def attribute_loaded?(name)
333
+ instance_variable_defined?(properties[name].instance_variable_name)
334
+ end
335
+
336
+ # fetches all the names of the attributes that have been loaded,
337
+ # even if they are lazy but have been called
338
+ #
339
+ # ==== Returns
340
+ # Array[<Symbol>]:: names of attributes that have been loaded
341
+ #
342
+ # ==== Example
343
+ #
344
+ # class Foo
345
+ # include DataMapper::Resource
346
+ # property :name, String
347
+ # property :description, Text, :lazy => false
348
+ # end
349
+ #
350
+ # Foo.new.loaded_attributes # returns [:name]
351
+ #
352
+ # --
353
+ # @api public
354
+ def loaded_attributes
355
+ properties.map{|p| p.name if attribute_loaded?(p.name)}.compact
356
+ end
357
+
358
+ # set of original values of properties
359
+ #
360
+ # ==== Returns
361
+ # Hash:: original values of properties
362
+ #
363
+ # --
364
+ # @api public
365
+ def original_values
366
+ @original_values ||= {}
367
+ end
368
+
369
+ # Hash of attributes that have been marked dirty
370
+ #
371
+ # ==== Returns
372
+ # Hash:: attributes that have been marked dirty
373
+ #
374
+ # --
375
+ # @api private
376
+ def dirty_attributes
377
+ dirty_attributes = {}
378
+ properties = self.properties
379
+
380
+ original_values.each do |name, old_value|
381
+ property = properties[name]
382
+ new_value = property.get!(self)
383
+
384
+ dirty = case property.track
385
+ when :hash then old_value != new_value.hash
386
+ else
387
+ property.value(old_value) != property.value(new_value)
388
+ end
389
+
390
+ if dirty
391
+ property.hash
392
+ dirty_attributes[property] = property.value(new_value)
393
+ end
394
+ end
395
+
396
+ dirty_attributes
397
+ end
398
+
399
+ # Checks if the class is dirty
400
+ #
401
+ # ==== Returns
402
+ # True:: returns if class is dirty
403
+ #
404
+ # --
405
+ # @api public
406
+ def dirty?
407
+ dirty_attributes.any?
408
+ end
409
+
410
+ # Checks if the attribute is dirty
411
+ #
412
+ # ==== Parameters
413
+ # name<Symbol>:: name of attribute
414
+ #
415
+ # ==== Returns
416
+ # True:: returns if attribute is dirty
417
+ #
418
+ # --
419
+ # @api public
420
+ def attribute_dirty?(name)
421
+ dirty_attributes.has_key?(properties[name])
422
+ end
423
+
424
+ def collection
425
+ @collection ||= if query = to_query
426
+ Collection.new(query) { |c| c << self }
427
+ end
428
+ end
429
+
430
+ # Reload association and all child association
431
+ #
432
+ # ==== Returns
433
+ # self:: returns the class itself
434
+ #
435
+ # --
436
+ # @api public
437
+ def reload
438
+ unless new_record?
439
+ reload_attributes(*loaded_attributes)
440
+ (parent_associations + child_associations).each { |association| association.reload }
441
+ end
442
+
443
+ self
444
+ end
445
+
446
+ # Reload specific attributes
447
+ #
448
+ # ==== Parameters
449
+ # *attributes<Array[<Symbol>]>:: name of attribute
450
+ #
451
+ # ==== Returns
452
+ # self:: returns the class itself
453
+ #
454
+ # --
455
+ # @api public
456
+ def reload_attributes(*attributes)
457
+ unless attributes.empty? || new_record?
458
+ collection.reload(:fields => attributes)
459
+ end
460
+
461
+ self
462
+ end
463
+
464
+ # Checks if the model has been saved
465
+ #
466
+ # ==== Returns
467
+ # True:: status if the model is new
468
+ #
469
+ # --
470
+ # @api public
471
+ def new_record?
472
+ !defined?(@new_record) || @new_record
473
+ end
474
+
475
+ # all the attributes of the model
476
+ #
477
+ # ==== Returns
478
+ # Hash[<Symbol>]:: All the (non)-lazy attributes
479
+ #
480
+ # --
481
+ # @api public
482
+ def attributes
483
+ properties.map do |p|
484
+ [p.name, send(p.getter)] if p.reader_visibility == :public
485
+ end.compact.to_hash
486
+ end
487
+
488
+ # Mass assign of attributes
489
+ #
490
+ # ==== Parameters
491
+ # value_hash <Hash[<Symbol>]>::
492
+ #
493
+ # --
494
+ # @api public
495
+ def attributes=(values_hash)
496
+ values_hash.each_pair do |k,v|
497
+ setter = "#{k.to_s.sub(/\?\z/, '')}="
498
+
499
+ if respond_to?(setter)
500
+ send(setter, v)
501
+ else
502
+ raise NameError, "#{setter} is not a public property"
503
+ end
504
+ end
505
+ end
506
+
507
+ # Updates attributes and saves model
508
+ #
509
+ # ==== Parameters
510
+ # attributes<Hash> Attributes to be updated
511
+ # keys<Symbol, String, Array> keys of Hash to update (others won't be updated)
512
+ #
513
+ # ==== Returns
514
+ # <TrueClass, FalseClass> if model got saved or not
515
+ #
516
+ #-
517
+ # @api public
518
+ def update_attributes(hash, *update_only)
519
+ unless hash.is_a?(Hash)
520
+ raise ArgumentError, "Expecting the first parameter of " +
521
+ "update_attributes to be a hash; got #{hash.inspect}"
522
+ end
523
+ loop_thru = update_only.empty? ? hash.keys : update_only
524
+ loop_thru.each { |attr| send("#{attr}=", hash[attr]) }
525
+ save
526
+ end
527
+
528
+ # TODO: add docs
529
+ def to_query(query = {})
530
+ model.to_query(repository, key, query) unless new_record?
531
+ end
532
+
533
+ protected
534
+
535
+ def properties
536
+ model.properties(repository.name)
537
+ end
538
+
539
+ def key_properties
540
+ model.key(repository.name)
541
+ end
542
+
543
+ def relationships
544
+ model.relationships(repository.name)
545
+ end
546
+
547
+ # Needs to be a protected method so that it is hookable
548
+ def create
549
+ # set defaults for new resource
550
+ properties.each do |property|
551
+ next if attribute_loaded?(property.name)
552
+ property.set(self, property.default_for(self))
553
+ end
554
+
555
+ return false unless repository.create([ self ]) == 1
556
+
557
+ @repository = repository
558
+ @new_record = false
559
+
560
+ repository.identity_map(model).set(key, self)
561
+
562
+ true
563
+ end
564
+
565
+ # Needs to be a protected method so that it is hookable
566
+ def update
567
+ dirty_attributes = self.dirty_attributes
568
+ return true if dirty_attributes.empty?
569
+ repository.update(dirty_attributes, to_query) == 1
570
+ end
571
+
572
+ private
573
+
574
+ def initialize(attributes = {}) # :nodoc:
575
+ assert_valid_model
576
+ self.attributes = attributes
577
+ end
578
+
579
+ def assert_valid_model # :nodoc:
580
+ return if self.class._valid_model
581
+ properties = self.properties
582
+
583
+ if properties.empty? && relationships.empty?
584
+ raise IncompleteResourceError, "#{model.name} must have at least one property or relationship to be initialized."
585
+ end
586
+
587
+ if properties.key.empty?
588
+ raise IncompleteResourceError, "#{model.name} must have a key."
589
+ end
590
+
591
+ class << self; @_valid_model = true; end
592
+ end
593
+
594
+ # TODO document
595
+ # @api semipublic
596
+ def attribute_get!(name)
597
+ properties[name].get!(self)
598
+ end
599
+
600
+ # TODO document
601
+ # @api semipublic
602
+ def attribute_set!(name, value)
603
+ properties[name].set!(self, value)
604
+ end
605
+
606
+ def lazy_load(name)
607
+ reload_attributes(*properties.lazy_load_context(name) - loaded_attributes)
608
+ end
609
+
610
+ def child_associations
611
+ @child_associations ||= []
612
+ end
613
+
614
+ def parent_associations
615
+ @parent_associations ||= []
616
+ end
617
+
618
+ # TODO: move to dm-more/dm-transactions
619
+ module Transaction
620
+ # Produce a new Transaction for the class of this Resource
621
+ #
622
+ # ==== Returns
623
+ # <DataMapper::Adapters::Transaction>::
624
+ # a new DataMapper::Adapters::Transaction with all DataMapper::Repositories
625
+ # of the class of this DataMapper::Resource added.
626
+ #-
627
+ # @api public
628
+ #
629
+ # TODO: move to dm-more/dm-transactions
630
+ def transaction(&block)
631
+ model.transaction(&block)
632
+ end
633
+ end # module Transaction
634
+
635
+ include Transaction
636
+ end # module Resource
637
+ end # module DataMapper